commit 55661742a24eb46ff5dd317c1c75bef20f73a186 Author: Alexander Batalov Date: Thu May 19 11:51:26 2022 +0300 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..fccbbe7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: WebKit +AllowShortIfStatementsOnASingleLine: WithoutElse diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..febb7ae --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_white_space = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9445739 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# 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 diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml new file mode 100644 index 0000000..53b9d20 --- /dev/null +++ b/.github/workflows/Build.yml @@ -0,0 +1,42 @@ +name: Build + +on: + push: + paths: + - '.github/workflows/Build.yml' + - 'src/**.c' + - 'src/**.h' + - '**/CMakeLists.txt' + pull_request: + paths: + - '.github/workflows/Build.yml' + - 'src/**.c' + - 'src/**.h' + - '**/CMakeLists.txt' + +defaults: + run: + shell: bash + +jobs: + + Build: + runs-on: windows-latest + steps: + + - name: Clone + uses: actions/checkout@v2 + + - name: Prepare + run: cmake -B Build -A Win32 + + - name: Release build + run: cmake --build Build --config Release + + - name: Artifact + uses: actions/upload-artifact@v3 + with: + name: fallout2-ce + path: | + Build/*/fallout2-ce.exe + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ba36fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,392 @@ +## 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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ce24a78 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,239 @@ +cmake_minimum_required(VERSION 3.13) + +project(fallout2-ce LANGUAGES C) + +add_executable(fallout2-ce WIN32 + "src/actions.c" + "src/actions.h" + "src/animation.c" + "src/animation.h" + "src/args.c" + "src/args.h" + "src/art.c" + "src/art.h" + "src/audio_file.c" + "src/audio_file.h" + "src/audio.c" + "src/audio.h" + "src/automap.c" + "src/automap.h" + "src/autorun.c" + "src/autorun.h" + "src/cache.c" + "src/cache.h" + "src/character_editor.c" + "src/character_editor.h" + "src/character_selector.c" + "src/character_selector.h" + "src/color.c" + "src/color.h" + "src/combat_ai_defs.h" + "src/combat_ai.c" + "src/combat_ai.h" + "src/combat_defs.h" + "src/combat.c" + "src/combat.h" + "src/config.c" + "src/config.h" + "src/core.c" + "src/core.h" + "src/credits.c" + "src/credits.h" + "src/critter.c" + "src/critter.h" + "src/cycle.c" + "src/cycle.h" + "src/datafile.c" + "src/datafile.h" + "src/db.c" + "src/db.h" + "src/dbox.c" + "src/dbox.h" + "src/debug.c" + "src/debug.h" + "src/dfile.c" + "src/dfile.h" + "src/dialog.c" + "src/dialog.h" + "src/dictionary.c" + "src/dictionary.h" + "src/dinput.c" + "src/dinput.h" + "src/display_monitor.c" + "src/display_monitor.h" + "src/draw.c" + "src/draw.h" + "src/electronic_registration.c" + "src/electronic_registration.h" + "src/elevator.c" + "src/elevator.h" + "src/endgame.c" + "src/endgame.h" + "src/export.c" + "src/export.h" + "src/file_find.c" + "src/file_find.h" + "src/file_utils.c" + "src/file_utils.h" + "src/font_manager.c" + "src/font_manager.h" + "src/game_config.c" + "src/game_config.h" + "src/game_dialog.c" + "src/game_dialog.h" + "src/game_memory.c" + "src/game_memory.h" + "src/game_mouse.c" + "src/game_mouse.h" + "src/game_movie.c" + "src/game_movie.h" + "src/game_palette.c" + "src/game_palette.h" + "src/game_sound.c" + "src/game_sound.h" + "src/game_vars.h" + "src/game.c" + "src/game.h" + "src/geometry.c" + "src/geometry.h" + "src/graph_lib.c" + "src/graph_lib.h" + "src/grayscale.c" + "src/grayscale.h" + "src/heap.c" + "src/heap.h" + "src/interface.c" + "src/interface.h" + "src/interpreter_extra.c" + "src/interpreter_extra.h" + "src/interpreter_lib.c" + "src/interpreter_lib.h" + "src/interpreter.c" + "src/interpreter.h" + "src/inventory.c" + "src/inventory.h" + "src/item.c" + "src/item.h" + "src/light.c" + "src/light.h" + "src/lips.c" + "src/lips.h" + "src/loadsave.c" + "src/loadsave.h" + "src/main.c" + "src/main.h" + "src/map_defs.h" + "src/map.c" + "src/map.h" + "src/memory_defs.h" + "src/memory_manager.c" + "src/memory_manager.h" + "src/memory.c" + "src/memory.h" + "src/message.c" + "src/message.h" + "src/mmx.c" + "src/mmx.h" + "src/mouse_manager.c" + "src/mouse_manager.h" + "src/movie_effect.c" + "src/movie_effect.h" + "src/movie_lib.c" + "src/movie_lib.h" + "src/movie.c" + "src/movie.h" + "src/nevs.c" + "src/nevs.h" + "src/obj_types.h" + "src/object.c" + "src/object.h" + "src/options.c" + "src/options.h" + "src/palette.c" + "src/palette.h" + "src/party_member.c" + "src/party_member.h" + "src/perk_defs.h" + "src/perk.c" + "src/perk.h" + "src/pipboy.c" + "src/pipboy.h" + "src/proto_instance.c" + "src/proto_instance.h" + "src/proto_types.h" + "src/proto.c" + "src/proto.h" + "src/queue.c" + "src/queue.h" + "src/random.c" + "src/random.h" + "src/reaction.c" + "src/reaction.h" + "src/region.c" + "src/region.h" + "src/scripts.c" + "src/scripts.h" + "src/select_file_list.c" + "src/select_file_list.h" + "src/selfrun.c" + "src/selfrun.h" + "src/skill_defs.h" + "src/skill.c" + "src/skill.h" + "src/skilldex.c" + "src/skilldex.h" + "src/sound_decoder.c" + "src/sound_decoder.h" + "src/sound_effects_cache.c" + "src/sound_effects_cache.h" + "src/sound_effects_list.c" + "src/sound_effects_list.h" + "src/sound.c" + "src/sound.h" + "src/stat_defs.h" + "src/stat.c" + "src/stat.h" + "src/string_parsers.c" + "src/string_parsers.h" + "src/text_font.c" + "src/text_font.h" + "src/text_object.c" + "src/text_object.h" + "src/tile.c" + "src/tile.h" + "src/trait_defs.h" + "src/trait.c" + "src/trait.h" + "src/trap.c" + "src/trap.h" + "src/version.c" + "src/version.h" + "src/widget.c" + "src/widget.h" + "src/win32.c" + "src/win32.h" + "src/window_manager_private.c" + "src/window_manager_private.h" + "src/window_manager.c" + "src/window_manager.h" + "src/window.c" + "src/window.h" + "src/word_wrap.c" + "src/word_wrap.h" + "src/world_map.c" + "src/world_map.h" + "src/xfile.c" + "src/xfile.h" +) + +target_compile_definitions(fallout2-ce PUBLIC + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_WARNINGS +) + +target_link_libraries(fallout2-ce + winmm +) + +add_subdirectory("third_party/fpattern") +add_subdirectory("third_party/zlib") diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..448cd97 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,26 @@ +{ + "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": "" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..01388e8 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Fallout 2 Community Edition + +## 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-ce.exe` serves as a drop-in replacement for `fallout2.exe`. Copy it to your Fallout 2 directory and run. + +## Contributing + +For now there are three major areas. + +### Intergrating Sfall + +There are literally hundreds if not thousands of fixes and features in sfall. I guess not all of them are needed in Community Edition, but for the sake of compatibility with big mods out there, let's integrate them all. + +### SDL + +Migrate DirectX stuff to SDL. This is the shortest path to native Linux version. + +### Prepare to 64-bit + +Modern macOS requires apps to be 64-bit, so even if we have SDL, the scripting part of the game will not work, because of builtin SSL interpreter. It stores pointers (both functions and variables) as 32-bit integers, so 64-bit pointers will not fit into stack. Since the stack is shared for both instructions and data, it needs some attention. + + +## Legal & License + +See [Fallout 2 Reference Edition](https://github.com/alexbatalov/fallout2-re). Same conditions apply until the source code in this repository is changed significantly. diff --git a/src/actions.c b/src/actions.c new file mode 100644 index 0000000..c07995d --- /dev/null +++ b/src/actions.c @@ -0,0 +1,2028 @@ +#include "actions.h" + +#include "animation.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "config.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_config.h" +#include "game_sound.h" +#include "geometry.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_object.h" +#include "tile.h" +#include "trait.h" + +#include +#include + +// 0x5106D0 +int _action_in_explode = 0; + +// 0x5106E0 +const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = { + ANIM_DANCING_AUTOFIRE, + ANIM_SLICED_IN_HALF, + ANIM_CHUNKS_OF_FLESH, + ANIM_CHUNKS_OF_FLESH, + ANIM_ELECTRIFY, + ANIM_FALL_BACK, + ANIM_BIG_HOLE, +}; + +// 0x5106FC +const int gMaximumBloodDeathAnimations[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, +}; + +// 0x410468 +int actionKnockdown(Object* obj, int* anim, int maxDistance, int rotation, int delay) +{ + if (_critter_flag_check(obj->pid, 0x4000)) { + return -1; + } + + if (*anim == ANIM_FALL_FRONT) { + int fid = buildFid(1, obj->fid & 0xFFF, *anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (!artExists(fid)) { + *anim = ANIM_FALL_BACK; + } + } + + int distance; + int tile; + for (distance = 1; distance <= maxDistance; distance++) { + tile = tileGetTileInDirection(obj->tile, rotation, distance); + if (_obj_blocking_at(obj, tile, obj->elevation) != NULL) { + distance--; + break; + } + } + + const char* soundEffectName = sfxBuildCharName(obj, *anim, CHARACTER_SOUND_EFFECT_KNOCKDOWN); + reg_anim_play_sfx(obj, soundEffectName, delay); + + // TODO: Check, probably step back because we've started with 1? + distance--; + + if (distance <= 0) { + tile = obj->tile; + reg_anim_animate(obj, *anim, 0); + } else { + tile = tileGetTileInDirection(obj->tile, rotation, distance); + reg_anim_knockdown(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; + configGetInt(&gGameConfig, 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 = buildFid(1, obj->fid & 0xFFF, bloodyAnim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artExists(fid)) { + reg_anim_animate(obj, bloodyAnim, delay); + } else { + bloodyAnim = anim; + } + + return bloodyAnim; +} + +// 0x41060C +int _pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack) +{ + int normalViolenceLevelDamageThreshold = 15; + int maximumBloodViolenceLevelDamageThreshold = 45; + + int damageType = weaponGetDamageType(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 == gDude && perkHasRank(attacker, PERK_PYROMANIAC) && damageType == DAMAGE_TYPE_FIRE) { + normalViolenceLevelDamageThreshold = 1; + maximumBloodViolenceLevelDamageThreshold = 1; + } + + if (weapon != NULL && weaponGetPerk(weapon) == PERK_WEAPON_FLAMEBOY) { + normalViolenceLevelDamageThreshold /= 3; + maximumBloodViolenceLevelDamageThreshold /= 3; + } + + int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); + + if (_critter_flag_check(defender->pid, 0x1000)) { + return _check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_NORMAL, isFallingBack); + } + + bool hasBloodyMess = false; + if (attacker == gDude && traitIsSelected(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 = gMaximumBloodDeathAnimations[damageType]; + if (_check_death(defender, deathAnim, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack) != deathAnim) { + deathAnim = gNormalDeathAnimations[damageType]; + } + } else { + deathAnim = gNormalDeathAnimations[damageType]; + } + } + } + } + + if (!isFallingBack && deathAnim == ANIM_FALL_BACK) { + deathAnim = ANIM_FALL_FRONT; + } + + return _check_death(defender, deathAnim, VIOLENCE_LEVEL_NONE, isFallingBack); +} + +// 0x410814 +int _check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack) +{ + int fid; + + int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); + if (violenceLevel >= minViolenceLevel) { + fid = buildFid(1, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artExists(fid)) { + return anim; + } + } + + if (isFallingBack) { + return ANIM_FALL_BACK; + } + + fid = buildFid(1, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artExists(fid)) { + return ANIM_FALL_BACK; + } + + return ANIM_FALL_FRONT; +} + +// 0x4108C8 +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, 0x4000)) { + knockbackDistance = 0; + } + + anim = (a1->fid & 0xFF0000) >> 16; + if (!_critter_is_prone(a1)) { + if ((flags & DAM_DEAD) != 0) { + fid = buildFid(5, 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)) { + actionKnockdown(a1, &anim, knockbackDistance, knockbackRotation, a10); + anim = _action_blood(a1, anim, -1); + } else { + sfx_name = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_DIE); + reg_anim_play_sfx(a1, sfx_name, a10); + + anim = _pick_fall(a1, anim); + reg_anim_animate(a1, anim, 0); + + if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) { + anim = _action_blood(a1, anim, -1); + } + } + } else { + fid = buildFid(1, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1); + if (artExists(fid)) { + sfx_name = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, a10); + + reg_anim_animate(a1, anim, 0); + + int randomDistance = randomBetween(2, 5); + int randomRotation = randomBetween(0, 5); + + while (randomDistance > 0) { + int tile = tileGetTileInDirection(a1->tile, randomRotation, randomDistance); + Object* v35 = NULL; + _make_straight_path(a1, a1->tile, tile, NULL, &v35, 4); + if (v35 == NULL) { + reg_anim_set_rotation_to_tile(a1, tile); + reg_anim_2(a1, tile, a1->elevation, anim, 0); + break; + } + randomDistance--; + } + } + + anim = ANIM_BURNED_TO_NOTHING; + sfx_name = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, -1); + reg_anim_animate(a1, anim, 0); + } + } else { + if ((flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { + anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT; + sfx_name = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, a10); + if (knockbackDistance != 0) { + actionKnockdown(a1, &anim, knockbackDistance, knockbackRotation, 0); + } else { + anim = _pick_fall(a1, anim); + reg_anim_animate(a1, anim, 0); + } + } else if ((flags & DAM_ON_FIRE) != 0 && artExists(buildFid(1, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1))) { + reg_anim_animate(a1, ANIM_FIRE_DANCE, a10); + + fid = buildFid(1, a1->fid & 0xFFF, ANIM_STAND, (a1->fid & 0xF000) >> 12, a1->rotation + 1); + reg_anim_17(a1, fid, -1); + } else { + if (knockbackDistance != 0) { + anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT; + actionKnockdown(a1, &anim, knockbackDistance, knockbackRotation, a10); + if (anim == ANIM_FALL_BACK) { + reg_anim_animate(a1, ANIM_BACK_TO_STANDING, -1); + } else { + reg_anim_animate(a1, ANIM_PRONE_TO_STANDING, -1); + } + } else { + if (isFallingBack || !artExists(buildFid(1, 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 = sfxBuildCharName(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, a10); + + reg_anim_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) { + reg_anim_11_1(a1, weapon, _obj_drop, -1); + fid = buildFid(5, 10, 0, 0, 0); + reg_anim_17(weapon, fid, 0); + reg_anim_6(weapon, ANIM_STAND, 0); + + sfx_name = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a1); + reg_anim_play_sfx(weapon, sfx_name, 0); + + reg_anim_hide(weapon); + } else if ((flags & DAM_DESTROY) != 0) { + reg_anim_11_1(a1, weapon, _internal_destroy, -1); + } else if ((flags & DAM_DROP) != 0) { + reg_anim_11_1(a1, weapon, _obj_drop, -1); + } + } + + if ((flags & DAM_DEAD) != 0) { + // TODO: Get rid of casts. + reg_anim_11_1(a1, (Object*)anim, (AnimationProc*)_show_death, -1); + } +} + +// 0x410E24 +int _show_death(Object* obj, int anim) +{ + Rect v7; + Rect v8; + int fid; + + objectGetRect(obj, &v8); + if (anim < 48 && anim > 63) { + fid = buildFid(1, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (objectSetFid(obj, fid, &v7) == 0) { + rectUnion(&v8, &v7, &v8); + } + + if (objectSetFrame(obj, 0, &v7) == 0) { + rectUnion(&v8, &v7, &v8); + } + } + + if (_critter_flag_check(obj->pid, 2048) == 0) { + obj->flags |= OBJECT_NO_BLOCK; + if (_obj_toggle_flat(obj, &v7) == 0) { + rectUnion(&v8, &v7, &v8); + } + } + + if (objectDisableOutline(obj, &v7) == 0) { + rectUnion(&v8, &v7, &v8); + } + + if (anim >= 30 && anim <= 31 && _critter_flag_check(obj->pid, 4096) == 0 && _critter_flag_check(obj->pid, 64) == 0) { + _item_drop_all(obj, obj->tile); + } + + tileWindowRefreshRect(&v8, obj->elevation); + + 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 ((obj->fid & 0xF000000) >> 24 == 1) { + int delta = attack->attacker->rotation - obj->rotation; + if (delta < 0) { + delta = -delta; + } + + v6 = delta != 0 && delta != 1 && delta != 5; + reg_anim_begin(2); + _register_priority(1); + v8 = critterGetAnimationForHitMode(attack->attacker, attack->hitMode); + v9 = tileGetRotationTo(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); + reg_anim_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 ((object->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + reg_anim_26(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 ((attack->defender->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + if (attack->attacker->fid == 33554933) { + v14 = tileGetRotationTo(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 = critterGetAnimationForHitMode(attack->attacker, attack->hitMode); + v14 = tileGetRotationTo(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 { + tileGetRotationTo(attack->attacker->tile, attack->defender->tile); + critterGetAnimationForHitMode(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 (reg_anim_clear(attack->attacker) == -2) { + return -1; + } + + if (reg_anim_clear(attack->defender) == -2) { + return -1; + } + + for (int index = 0; index < attack->extrasLength; index++) { + if (reg_anim_clear(attack->extras[index]) == -2) { + return -1; + } + } + + int anim = critterGetAnimationForHitMode(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 +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]; + + reg_anim_begin(2); + _register_priority(1); + + fid = buildFid(1, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1); + art = artLock(fid, &cache_entry); + if (art != NULL) { + v17 = artGetActionFrame(art); + } else { + v17 = 0; + } + artUnlock(cache_entry); + + tileGetTileInDirection(attack->attacker->tile, attack->attacker->rotation, 1); + reg_anim_set_rotation_to_tile(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 = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_ATTACK, attack->weapon, attack->hitMode, attack->defender); + } else { + sfx_name = sfxBuildCharName(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) { + reg_anim_play_sfx(attack->attacker, sfx_name_temp, 0); + if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) { + sfx_name = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, attack->weapon, attack->hitMode, attack->defender); + } else { + sfx_name = sfxBuildCharName(attack->attacker, anim, CHARACTER_SOUND_EFFECT_CONTACT); + } + + strcpy(sfx_name_temp, sfx_name); + + reg_anim_animate(attack->attacker, anim, 0); + reg_anim_play_sfx(attack->attacker, sfx_name_temp, v17); + _show_damage(attack, anim, 0); + } else { + if (attack->defender->data.critter.combat.results & 0x03) { + reg_anim_play_sfx(attack->attacker, sfx_name_temp, -1); + reg_anim_animate(attack->attacker, anim, 0); + } else { + fid = buildFid(1, attack->defender->fid & 0xFFF, ANIM_DODGE_ANIM, (attack->defender->fid & 0xF000) >> 12, attack->defender->rotation + 1); + art = artLock(fid, &cache_entry); + if (art != NULL) { + v18 = artGetActionFrame(art); + artUnlock(cache_entry); + + if (v18 <= v17) { + reg_anim_play_sfx(attack->attacker, sfx_name_temp, -1); + reg_anim_animate(attack->attacker, anim, 0); + + sfx_name = sfxBuildCharName(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(attack->defender, sfx_name, v17 - v18); + reg_anim_animate(attack->defender, 13, 0); + } else { + sfx_name = sfxBuildCharName(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(attack->defender, sfx_name, -1); + reg_anim_animate(attack->defender, 13, 0); + reg_anim_play_sfx(attack->attacker, sfx_name_temp, v18 - v17); + reg_anim_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 (reg_anim_end() == -1) { + return -1; + } + + _show_damage_extras(attack); + + return 0; +} + +// 0x411600 +int _action_ranged(Attack* attack, int anim) +{ + Object* neighboors[6]; + memset(neighboors, 0, sizeof(neighboors)); + + reg_anim_begin(2); + _register_priority(1); + + Object* projectile = NULL; + Object* v50 = NULL; + int weaponFid = -1; + + Proto* weaponProto; + Object* weapon = attack->weapon; + protoGetProto(weapon->pid, &weaponProto); + + int fid = buildFid(1, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1); + CacheEntry* artHandle; + Art* art = artLock(fid, &artHandle); + int actionFrame = (art != NULL) ? artGetActionFrame(art) : 0; + artUnlock(artHandle); + + _item_w_range(attack->attacker, attack->hitMode); + + int damageType = weaponGetDamageType(attack->attacker, attack->weapon); + + tileGetTileInDirection(attack->attacker->tile, attack->attacker->rotation, 1); + + reg_anim_set_rotation_to_tile(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 { + reg_anim_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 = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_ATTACK, weapon, attack->hitMode, attack->defender); + } else { + sfx = sfxBuildCharName(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED); + } + reg_anim_play_sfx(attack->attacker, sfx, -1); + + reg_anim_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 = weaponGetProjectilePid(weapon); + Proto* projectileProto; + if (protoGetProto(projectilePid, &projectileProto) != -1 && projectileProto->fid != -1) { + if (anim == ANIM_THROW_ANIM) { + projectile = weapon; + weaponFid = weapon->fid; + int weaponFlags = weapon->flags; + + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + + itemRemove(attack->attacker, weapon, 1); + v50 = _item_replace(attack->attacker, weapon, weaponFlags & OBJECT_IN_ANY_HAND); + objectSetFid(projectile, projectileProto->fid, NULL); + _cAIPrepWeaponItem(attack->attacker, weapon); + + if (attack->attacker == gDude) { + 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; + } + } + interfaceUpdateItems(false, leftItemAction, rightItemAction); + } + + _obj_connect(weapon, attack->attacker->tile, attack->attacker->elevation, NULL); + } else { + objectCreateWithFidPid(&projectile, projectileProto->fid, -1); + } + + objectHide(projectile, NULL); + + objectSetLight(projectile, 9, projectile->lightIntensity, NULL); + + int projectileOrigin = _combat_bullet_start(attack->attacker, attack->defender); + objectSetLocation(projectile, projectileOrigin, attack->attacker->elevation, NULL); + + int projectileRotation = tileGetRotationTo(attack->attacker->tile, attack->defender->tile); + objectSetRotation(projectile, projectileRotation, NULL); + + reg_anim_15(projectile, 1, actionFrame); + + const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_AMMO_FLYING, weapon, attack->hitMode, attack->defender); + reg_anim_play_sfx(projectile, sfx, 0); + + int v24; + if ((attack->attackerFlags & DAM_HIT) != 0) { + reg_anim_2(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 { + reg_anim_2(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) { + reg_anim_17(projectile, weaponFid, -1); + } + + int explosionFid = buildFid(5, explosionFrmId, 0, 0, 0); + reg_anim_17(projectile, explosionFid, -1); + + const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender); + reg_anim_play_sfx(projectile, sfx, 0); + + reg_anim_6(projectile, ANIM_STAND, 0); + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + if (objectCreateWithFidPid(&(neighboors[rotation]), explosionFid, -1) != -1) { + objectHide(neighboors[rotation], NULL); + + int v31 = tileGetTileInDirection(v24, rotation, 1); + objectSetLocation(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; + } + } + + reg_anim_15(neighboors[rotation], 1, delay); + reg_anim_6(neighboors[rotation], ANIM_STAND, 0); + } + } + + l56 = true; + } + } else { + if (anim != ANIM_THROW_ANIM) { + reg_anim_hide(projectile); + } + } + + if (!l56) { + const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender); + reg_anim_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) { + reg_anim_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)) { + reg_anim_hide(projectile); + } else if (anim == ANIM_THROW_ANIM && projectile != NULL) { + reg_anim_17(projectile, weaponFid, -1); + } + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + if (neighboors[rotation] != NULL) { + reg_anim_hide(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 = weaponGetAnimationCode(v50); + if (v38 != 0) { + reg_anim_18(attack->attacker, v38, -1); + l9 = true; + } + } + + if (!l9) { + int fid = buildFid(1, attack->attacker->fid & 0xFFF, ANIM_STAND, 0, attack->attacker->rotation + 1); + reg_anim_17(attack->attacker, fid, -1); + } + } else { + reg_anim_animate(attack->attacker, ANIM_UNPOINT, -1); + } + } + + if (reg_anim_end() == -1) { + debugPrint("Something went wrong with a ranged attack sequence!\n"); + if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION || anim != ANIM_THROW_ANIM)) { + objectDestroy(projectile, NULL); + } + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + if (neighboors[rotation] != NULL) { + objectDestroy(neighboors[rotation], NULL); + } + } + + return -1; + } + + _show_damage_extras(attack); + + return 0; +} + +// 0x411D68 +int _is_next_to(Object* a1, Object* a2) +{ + if (objectGetDistanceBetween(a1, a2) > 1) { + if (a2 == gDude) { + MessageListItem messageListItem; + // You cannot get there. + messageListItem.num = 2000; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + return 0; +} + +// 0x411DB4 +int _action_climb_ladder(Object* a1, Object* a2) +{ + int anim; + int v5; + int v6; + int tile_num; + int v11; + const char* sfx_name; + + if (a1 == gDude) { + anim = (gDude->fid & 0xFF0000) >> 16; + if (anim == ANIM_WALK || anim == ANIM_RUNNING) { + reg_anim_clear(gDude); + } + } + + if (isInCombat()) { + v5 = 2; + v6 = a1->data.critter.combat.ap; + } else { + v5 = 1; + v6 = -1; + } + + if (a1 == gDude) { + v5 = 2; + } + + v5 |= 4; + reg_anim_begin(v5); + + tile_num = tileGetTileInDirection(a2->tile, 2, 1); + if (v6 != -1 || objectGetDistanceBetween(a1, a2) < 5) { + reg_anim_obj_move_to_tile(a1, tile_num, a2->elevation, v6, 0); + } else { + reg_anim_obj_run_to_tile(a1, tile_num, a2->elevation, v6, 0); + } + + reg_anim_11_1(a1, a2, _is_next_to, -1); + reg_anim_set_rotation_to_tile(a1, a2->tile); + reg_anim_11_1(a1, a2, _check_scenery_ap_cost, -1); + + v11 = (a1->fid & 0xF000) >> 12; + if (v11 != 0) { + sfx_name = sfxBuildCharName(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, -1); + reg_anim_animate(a1, 39, 0); + } + + sfx_name = sfxBuildCharName(a1, ANIM_CLIMB_LADDER, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx_name, -1); + reg_anim_animate(a1, 4, 0); + reg_anim_11_0(a1, a2, _obj_use, -1); + + if (v11 != 0) { + reg_anim_18(a1, v11, -1); + } + + return reg_anim_end(); +} + +// 0x411F2C +int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3) +{ + Proto* proto = NULL; + int type = (a2->fid & 0xF000000) >> 24; + int sceneryType = -1; + if (type == OBJ_TYPE_SCENERY) { + if (protoGetProto(a2->pid, &proto) == -1) { + return -1; + } + + sceneryType = proto->scenery.type; + } + + if (sceneryType != SCENERY_TYPE_LADDER_UP || a3 != NULL) { + if (a1 == gDude) { + int anim = (gDude->fid & 0xFF0000) >> 16; + if (anim == ANIM_WALK || anim == ANIM_RUNNING) { + reg_anim_clear(gDude); + } + } + + int v9; + int actionPoints; + if (isInCombat()) { + v9 = 2; + actionPoints = a1->data.critter.combat.ap; + } else { + v9 = 1; + actionPoints = -1; + } + + if (a1 == gDude) { + v9 = 2; + } + + reg_anim_begin(v9); + + if (actionPoints != -1 || objectGetDistanceBetween(a1, a2) < 5) { + reg_anim_obj_move_to_obj(a1, a2, actionPoints, 0); + } else { + reg_anim_obj_run_to_obj(a1, a2, -1, 0); + } + + reg_anim_11_1(a1, a2, _is_next_to, -1); + + if (a3 == NULL) { + reg_anim_11_0(a1, a2, _check_scenery_ap_cost, -1); + } + + int a2a = (a1->fid & 0xF000) >> 12; + if (a2a != 0) { + const char* sfx = sfxBuildCharName(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(a1, sfx, -1); + reg_anim_animate(a1, ANIM_PUT_AWAY, 0); + } + + int v13; + int v12 = (a2->fid & 0xF000000) >> 24; + if (v12 == OBJ_TYPE_CRITTER && _critter_is_prone(a2)) { + v13 = ANIM_MAGIC_HANDS_GROUND; + } else if (v12 == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & 0x01) != 0) { + v13 = ANIM_MAGIC_HANDS_GROUND; + } else { + v13 = ANIM_MAGIC_HANDS_MIDDLE; + } + + if (sceneryType != SCENERY_TYPE_STAIRS && a3 == NULL) { + reg_anim_animate(a1, v13, -1); + } + + if (a3 != NULL) { + // TODO: Get rid of cast. + reg_anim_12(a1, a2, a3, (AnimationProc2*)_obj_use_item_on, -1); + } else { + reg_anim_11_0(a1, a2, _obj_use, -1); + } + + if (a2a != 0) { + reg_anim_18(a1, a2a, -1); + } + + return reg_anim_end(); + } + + return _action_climb_ladder(a1, a2); +} + +// 0x412114 +int _action_use_an_object(Object* a1, Object* a2) +{ + return _action_use_an_item_on_object(a1, a2, NULL); +} + +// 0x412134 +int actionPickUp(Object* critter, Object* item) +{ + if (((item->fid & 0xF000000) >> 24) != OBJ_TYPE_ITEM) { + return -1; + } + + if (critter == gDude) { + int animationCode = (gDude->fid & 0xFF0000) >> 16; + if (animationCode == ANIM_WALK || animationCode == ANIM_RUNNING) { + reg_anim_clear(gDude); + } + } + + if (isInCombat()) { + reg_anim_begin(2); + reg_anim_obj_move_to_obj(critter, item, critter->data.critter.combat.ap, 0); + } else { + int flags = (critter == gDude) ? 2 : 1; + reg_anim_begin(flags); + if (objectGetDistanceBetween(critter, item) >= 5) { + reg_anim_obj_run_to_obj(critter, item, -1, 0); + } else { + reg_anim_obj_move_to_obj(critter, item, -1, 0); + } + } + + reg_anim_11_1(critter, item, _is_next_to, -1); + reg_anim_11_0(critter, item, _check_scenery_ap_cost, -1); + + Proto* itemProto; + protoGetProto(item->pid, &itemProto); + + if (itemProto->item.type != ITEM_TYPE_CONTAINER || _proto_action_can_pickup(item->pid)) { + reg_anim_animate(critter, ANIM_MAGIC_HANDS_GROUND, 0); + + int fid = buildFid(1, critter->fid & 0xFFF, ANIM_MAGIC_HANDS_GROUND, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + + int actionFrame; + CacheEntry* cacheEntry; + Art* art = artLock(fid, &cacheEntry); + if (art != NULL) { + actionFrame = artGetActionFrame(art); + } else { + actionFrame = -1; + } + + char sfx[16]; + if (artCopyFileName((item->fid & 0xF000000) >> 24, item->fid & 0xFFF, sfx) == 0) { + // NOTE: looks like they copy sfx one more time, what for? + reg_anim_play_sfx(item, sfx, actionFrame); + } + + reg_anim_11_0(critter, item, _obj_pickup, actionFrame); + } else { + int v27 = (critter->fid & 0xF000) >> 12; + if (v27 != 0) { + const char* sfx = sfxBuildCharName(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(critter, sfx, -1); + reg_anim_animate(critter, ANIM_PUT_AWAY, -1); + } + + // ground vs middle animation + int animationCode = 10 + ((itemProto->item.data.container.openFlags & 0x01) == 0); + reg_anim_animate(critter, animationCode, 0); + + int fid = buildFid(1, critter->fid & 0xFFF, animationCode, 0, critter->rotation + 1); + + int actionFrame; + CacheEntry* cacheEntry; + Art* art = artLock(fid, &cacheEntry); + if (art == NULL) { + actionFrame = artGetActionFrame(art); + artUnlock(cacheEntry); + } else { + actionFrame = -1; + } + + if (item->frame != 1) { + reg_anim_11_0(critter, item, _obj_use_container, actionFrame); + } + + if (v27 != 0) { + reg_anim_18(critter, v27, -1); + } + + if (item->frame == 0 || item->frame == 1) { + reg_anim_11_0(critter, item, scriptsRequestLooting, -1); + } + } + + return reg_anim_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 ((container->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return -1; + } + + if (critter == gDude) { + int anim = (gDude->fid & 0xFF0000) >> 16; + if (anim == ANIM_WALK || anim == ANIM_RUNNING) { + reg_anim_clear(gDude); + } + } + + if (isInCombat()) { + reg_anim_begin(2); + reg_anim_obj_move_to_obj(critter, container, critter->data.critter.combat.ap, 0); + } else { + reg_anim_begin(critter == gDude ? 2 : 1); + + if (objectGetDistanceBetween(critter, container) < 5) { + reg_anim_obj_move_to_obj(critter, container, -1, 0); + } else { + reg_anim_obj_run_to_obj(critter, container, -1, 0); + } + } + + reg_anim_11_1(critter, container, _is_next_to, -1); + reg_anim_11_0(critter, container, _check_scenery_ap_cost, -1); + reg_anim_11_0(critter, container, scriptsRequestLooting, -1); + return reg_anim_end(); +} + +// 0x4124E0 +int _action_skill_use(int skill) +{ + if (skill == SKILL_SNEAK) { + reg_anim_clear(gDude); + dudeToggleState(DUDE_STATE_SNEAKING); + return 0; + } + + return -1; +} + +// skill_use +// 0x41255C +int actionUseSkill(Object* a1, Object* a2, int skill) +{ + MessageListItem messageListItem; + + switch (skill) { + case SKILL_FIRST_AID: + case SKILL_DOCTOR: + if (isInCombat()) { + if (a1 == gDude) { + messageListItem.num = 902; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if ((a2->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + break; + case SKILL_LOCKPICK: + if (isInCombat()) { + if (a1 == gDude) { + messageListItem.num = 902; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if ((a2->pid >> 24) != OBJ_TYPE_ITEM && (a2->pid >> 24) != OBJ_TYPE_SCENERY) { + return -1; + } + + break; + case SKILL_STEAL: + if (isInCombat()) { + if (a1 == gDude) { + messageListItem.num = 902; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if ((a2->pid >> 24) != OBJ_TYPE_ITEM && (a2->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + if (a2 == a1) { + return -1; + } + + break; + case SKILL_TRAPS: + if (isInCombat()) { + if (a1 == gDude) { + messageListItem.num = 902; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if ((a2->pid >> 24) == OBJ_TYPE_CRITTER) { + return -1; + } + + break; + case SKILL_SCIENCE: + case SKILL_REPAIR: + if (isInCombat()) { + if (a1 == gDude) { + messageListItem.num = 902; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if ((a2->pid >> 24) != 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: + dudeToggleState(0); + return 0; + default: + debugPrint("\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 = gDude; + + if (a1 == gDude) { + Object* partyMember = partyMemberGetBestInSkill(skill); + + if (partyMember == gDude) { + partyMember = NULL; + } + + // Only dude can perform stealing. + if (skill == SKILL_STEAL) { + partyMember = NULL; + } + + if (partyMember != NULL) { + if (partyMemberGetBestSkill(partyMember) != skill) { + partyMember = NULL; + } + } + + if (partyMember != NULL) { + performer = partyMember; + int anim = (partyMember->fid & 0xFF0000) >> 16; + if (anim != ANIM_WALK && anim != ANIM_RUNNING) { + if (anim != ANIM_STAND) { + performer = gDude; + partyMember = NULL; + } + } else { + reg_anim_clear(partyMember); + } + } + + if (partyMember != NULL) { + bool v32 = false; + if (objectGetDistanceBetween(gDude, a2) <= 1) { + v32 = true; + } + + char* msg = skillsGetGenericResponse(partyMember, v32); + + Rect rect; + if (textObjectAdd(partyMember, msg, 101, _colorTable[32747], _colorTable[0], &rect) == 0) { + tileWindowRefreshRect(&rect, gElevation); + } + + if (v32) { + performer = gDude; + partyMember = NULL; + } + } + + if (partyMember == NULL) { + int anim = (performer->fid & 0xFF0000) >> 16; + if (anim == ANIM_WALK || anim == ANIM_RUNNING) { + reg_anim_clear(performer); + } + } + } + + if (isInCombat()) { + reg_anim_begin(2); + reg_anim_obj_move_to_obj(performer, a2, performer->data.critter.combat.ap, 0); + } else { + reg_anim_begin(a1 == gDude ? 2 : 1); + if (a2 != gDude) { + if (objectGetDistanceBetween(performer, a2) >= 5) { + reg_anim_obj_run_to_obj(performer, a2, -1, 0); + } else { + reg_anim_obj_move_to_obj(performer, a2, -1, 0); + } + } + } + + reg_anim_11_1(performer, a2, _is_next_to, -1); + + int anim = (((a2->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER && _critter_is_prone(a2)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE; + int fid = buildFid(1, performer->fid & 0xFFF, anim, 0, performer->rotation + 1); + + CacheEntry* artHandle; + Art* art = artLock(fid, &artHandle); + if (art != NULL) { + artGetActionFrame(art); + artUnlock(artHandle); + } + + reg_anim_animate(performer, anim, -1); + // TODO: Get rid of casts. + reg_anim_12(performer, a2, (void*)skill, (AnimationProc2*)_obj_use_skill_on, -1); + return reg_anim_end(); +} + +// 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 - tileGetRotationTo(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 = tileGetTileInDirection(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) % 6u; + for (i = 1; i < 3; i++) { + tile_num = tileGetTileInDirection(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 = buildFid(1, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (!artExists(fid)) { + anim = ANIM_FALL_BACK; + } + } + + return anim; +} + +// 0x412CE4 +bool _action_explode_running() +{ + return _action_in_explode != 0; +} + +// action_explode +// 0x412CF4 +int actionExplode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6) +{ + if (a6 && _action_in_explode) { + return -2; + } + + Attack* attack = internal_malloc(sizeof(*attack)); + if (attack == NULL) { + return -1; + } + + Object* explosion; + int fid = buildFid(5, 10, 0, 0, 0); + if (objectCreateWithFidPid(&explosion, fid, -1) == -1) { + internal_free(attack); + return -1; + } + + objectHide(explosion, NULL); + explosion->flags |= OBJECT_TEMPORARY; + + objectSetLocation(explosion, tile, elevation, NULL); + + Object* adjacentExplosions[ROTATION_COUNT]; + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + int fid = buildFid(5, 10, 0, 0, 0); + if (objectCreateWithFidPid(&(adjacentExplosions[rotation]), fid, -1) == -1) { + while (--rotation >= 0) { + objectDestroy(adjacentExplosions[rotation], NULL); + } + + objectDestroy(explosion, NULL); + internal_free(attack); + return -1; + } + + objectHide(adjacentExplosions[rotation], NULL); + adjacentExplosions[rotation]->flags |= OBJECT_TEMPORARY; + + int adjacentTile = tileGetTileInDirection(tile, rotation, 1); + objectSetLocation(adjacentExplosions[rotation], adjacentTile, elevation, NULL); + } + + Object* critter = _obj_blocking_at(NULL, tile, elevation); + if (critter != NULL) { + if ((critter->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER || (critter->data.critter.combat.results & DAM_DEAD) != 0) { + critter = NULL; + } + } + + attackInit(attack, explosion, critter, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); + + attack->tile = tile; + attack->attackerFlags = DAM_HIT; + + gameUiDisable(1); + + if (critter != NULL) { + if (reg_anim_clear(critter) == -2) { + debugPrint("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 (reg_anim_clear(critter) == -2) { + debugPrint("Cannot clear extra's animation for action_explode!\n"); + } + + attack->extrasDamage[index] = _compute_explosion_damage(minDamage, maxDamage, critter, &(attack->extrasKnockback[index])); + } + + attackComputeDeathFlags(attack); + + if (a6) { + _action_in_explode = 1; + + reg_anim_begin(2); + _register_priority(1); + reg_anim_play_sfx(explosion, "whn1xxx1", 0); + reg_anim_15(explosion, 1, 0); + reg_anim_6(explosion, ANIM_STAND, 0); + _show_damage(attack, 0, 1); + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + reg_anim_15(adjacentExplosions[rotation], 1, 0); + reg_anim_6(adjacentExplosions[rotation], ANIM_STAND, 0); + } + + reg_anim_11_1(explosion, 0, _combat_explode_scenery, -1); + reg_anim_hide(explosion); + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + reg_anim_hide(adjacentExplosions[rotation]); + } + + // TODO: Get rid of casts. + reg_anim_11_1((Object*)attack, a5, (AnimationProc*)_report_explosion, -1); + reg_anim_11_1(NULL, NULL, _finished_explosion, -1); + if (reg_anim_end() == -1) { + _action_in_explode = 0; + + objectDestroy(explosion, NULL); + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + objectDestroy(adjacentExplosions[rotation], NULL); + } + + internal_free(attack); + + gameUiEnable(); + return -1; + } + + _show_damage_extras(attack); + } else { + if (critter != NULL) { + if ((attack->defenderFlags & DAM_DEAD) != 0) { + critterKill(critter, -1, false); + } + } + + for (int index = 0; index < attack->extrasLength; index++) { + if ((attack->extrasFlags[index] & DAM_DEAD) != 0) { + critterKill(attack->extras[index], -1, false); + } + } + + _report_explosion(attack, a5); + + _combat_explode_scenery(explosion, NULL); + + objectDestroy(explosion, NULL); + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + objectDestroy(adjacentExplosions[rotation], NULL); + } + } + + return 0; +} + +// 0x413144 +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; + } + + attackComputeDeathFlags(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 == gDude && !mainTargetWasDead) { + xp += critterGetExp(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 == gDude && !extrasWasDead[index]) { + xp += critterGetExp(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; + scriptsRequestCombat(&combat); + } + } + } + + internal_free(attack); + gameUiEnable(); + + if (a2 == gDude) { + _combat_give_exps(xp); + } + + return 0; +} + +// 0x4132C0 +int _finished_explosion(Object* a1, Object* a2) +{ + _action_in_explode = 0; + return 0; +} + +// calculate explosion damage applying threshold and resistances +// 0x4132CC +int _compute_explosion_damage(int min, int max, Object* a3, int* a4) +{ + int v5 = randomBetween(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_FLAG_0x800) == 0) { + *a4 = v7 / 10; + } + } + + return v7; +} + +// 0x413330 +int actionTalk(Object* a1, Object* a2) +{ + if (a1 != gDude) { + return -1; + } + + if ((a2->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return -1; + } + + int anim = (gDude->fid & 0xFF0000) >> 16; + if (anim == ANIM_WALK || anim == ANIM_RUNNING) { + reg_anim_clear(gDude); + } + + if (isInCombat()) { + reg_anim_begin(2); + reg_anim_obj_move_to_obj(a1, a2, a1->data.critter.combat.ap, 0); + } else { + reg_anim_begin((a1 == gDude) ? 2 : 1); + + if (objectGetDistanceBetween(a1, a2) >= 9 || _combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL)) { + reg_anim_obj_run_to_obj(a1, a2, -1, 0); + } + } + + reg_anim_11_1(a1, a2, _can_talk_to, -1); + reg_anim_11_0(a1, a2, _talk_to, -1); + return reg_anim_end(); +} + +// 0x413420 +int _can_talk_to(Object* a1, Object* a2) +{ + if (_combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL) || objectGetDistanceBetween(a1, a2) >= 9) { + if (a1 == gDude) { + // You cannot get there. (used in actions.c) + MessageListItem messageListItem; + messageListItem.num = 2000; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + + return -1; + } + + return 0; +} + +// 0x413488 +int _talk_to(Object* a1, Object* a2) +{ + scriptsRequestDialog(a2); + return 0; +} + +// 0x413494 +void _action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor) +{ + Attack* attack = internal_malloc(sizeof(*attack)); + if (attack == NULL) { + return; + } + + Object* obj; + if (objectCreateWithFidPid(&obj, FID_0x20001F5, -1) == -1) { + internal_free(attack); + return; + } + + objectHide(obj, NULL); + + obj->flags |= OBJECT_TEMPORARY; + + objectSetLocation(obj, tile, elevation, NULL); + + Object* v9 = _obj_blocking_at(NULL, tile, elevation); + attackInit(attack, obj, v9, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); + attack->tile = tile; + attack->attackerFlags = DAM_HIT; + gameUiDisable(1); + + if (v9 != NULL) { + reg_anim_clear(v9); + + int damage; + if (bypassArmor) { + damage = maxDamage; + } else { + damage = _compute_dmg_damage(minDamage, maxDamage, v9, &(attack->defenderKnockback), damageType); + } + + attack->defenderDamage = damage; + } + + attackComputeDeathFlags(attack); + + if (animated) { + reg_anim_begin(2); + reg_anim_play_sfx(obj, "whc1xxx1", 0); + _show_damage(attack, gMaximumBloodDeathAnimations[damageType], 0); + // TODO: Get rid of casts. + reg_anim_11_1((Object*)attack, NULL, (AnimationProc*)_report_dmg, 0); + reg_anim_hide(obj); + + if (reg_anim_end() == -1) { + objectDestroy(obj, NULL); + internal_free(attack); + return; + } + } else { + if (v9 != NULL) { + if ((attack->defenderFlags & DAM_DEAD) != 0) { + critterKill(v9, -1, 1); + } + } + + _combat_display(attack); + _apply_damage(attack, false); + internal_free(attack); + gameUiEnable(); + objectDestroy(obj, NULL); + } + + gameUiEnable(); +} + +// 0x41363C +int _report_dmg(Attack* attack, Object* a2) +{ + _combat_display(attack); + _apply_damage(attack, false); + internal_free(attack); + gameUiEnable(); + return 0; +} + +// Calculate damage by applying threshold and resistances. +// +// 0x413660 +int _compute_dmg_damage(int min, int max, Object* obj, int* a4, int damageType) +{ + if (!_critter_flag_check(obj->pid, 0x4000)) { + a4 = NULL; + } + + int v8 = randomBetween(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_FLAG_0x800) == 0 && damageType != DAMAGE_TYPE_ELECTRICAL) { + *a4 = v10 / 10; + } + } + + return v10; +} + +// 0x4136EC +bool actionCheckPush(Object* a1, Object* a2) +{ + // Cannot push anything but critters. + if ((a2->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return false; + } + + // Cannot push itself. + if (a1 == a2) { + return false; + } + + // Cannot push inactive critters. + if (!critterIsActive(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 actionPush(Object* a1, Object* a2) +{ + if (!actionCheckPush(a1, a2)) { + return -1; + } + + int sid; + if (_obj_sid(a2, &sid) == 0) { + scriptSetObjects(sid, a1, a2); + scriptExecProc(sid, SCRIPT_PROC_PUSH); + + bool scriptOverrides = false; + + Script* scr; + if (scriptGetScript(sid, &scr) != -1) { + scriptOverrides = scr->scriptOverrides; + } + + if (scriptOverrides) { + return -1; + } + } + + int rotation = tileGetRotationTo(a1->tile, a2->tile); + int tile; + do { + tile = tileGetTileInDirection(a2->tile, rotation, 1); + if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) { + break; + } + + tile = tileGetTileInDirection(a2->tile, (rotation + 1) % ROTATION_COUNT, 1); + if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) { + break; + } + + tile = tileGetTileInDirection(a2->tile, (rotation + 5) % ROTATION_COUNT, 1); + if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) { + break; + } + + tile = tileGetTileInDirection(a2->tile, (rotation + 2) % ROTATION_COUNT, 1); + if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) { + break; + } + + tile = tileGetTileInDirection(a2->tile, (rotation + 4) % ROTATION_COUNT, 1); + if (_obj_blocking_at(a2, tile, a2->elevation) == NULL) { + break; + } + + tile = tileGetTileInDirection(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; + } + + reg_anim_begin(2); + reg_anim_set_rotation_to_tile(a2, tile); + reg_anim_obj_move_to_tile(a2, tile, a2->elevation, actionPoints, 0); + return reg_anim_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 (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_sight_blocking_at) == 0) { + return -1; + } + + if (tileDistanceBetween(a1->tile, a2->tile) > 12) { + return -2; + } + + return 0; +} diff --git a/src/actions.h b/src/actions.h new file mode 100644 index 0000000..412e360 --- /dev/null +++ b/src/actions.h @@ -0,0 +1,52 @@ +#ifndef ACTIONS_H +#define ACTIONS_H + +#include "combat_defs.h" +#include "obj_types.h" +#include "proto_types.h" + +#include + +extern int _action_in_explode; +extern const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT]; +extern const int gMaximumBloodDeathAnimations[DAMAGE_TYPE_COUNT]; + +int actionKnockdown(Object* obj, int* anim, int maxDistance, int rotation, int delay); +int _action_blood(Object* obj, int anim, int delay); +int _pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack); +int _check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack); +int _internal_destroy(Object* a1, Object* a2); +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_death(Object* obj, int anim); +int _show_damage_extras(Attack* attack); +void _show_damage(Attack* attack, int a2, int a3); +int _action_attack(Attack* attack); +int _action_melee(Attack* attack, int a2); +int _action_ranged(Attack* attack, int a2); +int _is_next_to(Object* a1, Object* a2); +int _action_climb_ladder(Object* a1, Object* a2); +int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3); +int _action_use_an_object(Object* a1, Object* a2); +int actionPickUp(Object* critter, Object* item); +int _action_loot_container(Object* critter, Object* container); +int _action_skill_use(int a1); +int actionUseSkill(Object* a1, Object* a2, int skill); +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 actionExplode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6); +int _report_explosion(Attack* attack, Object* a2); +int _finished_explosion(Object* a1, Object* a2); +int _compute_explosion_damage(int min, int max, Object* a3, int* a4); +int actionTalk(Object* a1, Object* a2); +int _can_talk_to(Object* a1, Object* a2); +int _talk_to(Object* a1, Object* a2); +void _action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor); +int _report_dmg(Attack* attack, Object* a2); +int _compute_dmg_damage(int min, int max, Object* obj, int* a4, int damage_type); +bool actionCheckPush(Object* a1, Object* a2); +int actionPush(Object* a1, Object* a2); +int _action_can_talk_to(Object* a1, Object* a2); + +#endif /* ACTIONS_H */ diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..cca2a95 --- /dev/null +++ b/src/animation.c @@ -0,0 +1,2853 @@ +#include "animation.h" + +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "geometry.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "random.h" +#include "scripts.h" +#include "stat.h" +#include "text_object.h" +#include "tile.h" +#include "trait.h" + +#include +#include + +// 0x510718 +int _curr_sad = 0; + +// 0x51071C +int gAnimationSequenceCurrentIndex = -1; + +// 0x510720 +int _anim_in_init = 0; + +// 0x510724 +bool _anim_in_anim_stop = false; + +// 0x510728 +bool _anim_in_bk = false; + +// 0x51072C +int _lastDestination = -2; + +// 0x510730 +unsigned int _last_time_ = 0; + +// 0x510734 +unsigned int _next_time = 0; + +// 0x530014 +STRUCT_530014 _sad[24]; + +// 0x542FD4 +PathNode gClosedPathNodeList[2000]; + +// 0x54CC14 +AnimationSequence gAnimationSequences[32]; + +// 0x561814 +unsigned char gPathfinderProcessedTiles[5000]; + +// 0x562B9C +PathNode gOpenPathNodeList[2000]; + +// 0x56C7DC +int gAnimationDescriptionCurrentIndex; + +// 0x56C7E0 +Object* dword_56C7E0[100]; + +// anim_init +// 0x413A20 +void animationInit() +{ + _anim_in_init = 1; + animationReset(); + _anim_in_init = 0; +} + +// 0x413A40 +void animationReset() +{ + if (!_anim_in_init) { + // NOTE: Uninline. + _anim_stop(); + } + + _curr_sad = 0; + gAnimationSequenceCurrentIndex = -1; + + for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { + gAnimationSequences[index].field_0 = -1000; + gAnimationSequences[index].flags = 0; + } +} + +// 0x413AB8 +void animationExit() +{ + // NOTE: Uninline. + _anim_stop(); +} + +// 0x413AF4 +int reg_anim_begin(int flags) +{ + if (gAnimationSequenceCurrentIndex != -1) { + return -1; + } + + if (_anim_in_anim_stop) { + return -1; + } + + int v1 = _anim_free_slot(flags); + if (v1 == -1) { + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[v1]); + animationSequence->flags |= 0x08; + + if (flags & 0x02) { + animationSequence->flags |= 0x04; + } + + if (flags & 0x0200) { + animationSequence->flags |= 0x40; + } + + if (flags & 0x04) { + animationSequence->flags |= 0x80; + } + + gAnimationSequenceCurrentIndex = v1; + + gAnimationDescriptionCurrentIndex = 0; + + return 0; +} + +// 0x413B80 +int _anim_free_slot(int flags) +{ + int v1 = -1; + int v2 = 0; + for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { + // TODO: Check. + if (gAnimationSequences[index].field_0 != -1000 || gAnimationSequences[index].flags & 8 || gAnimationSequences[index].flags & 0x20) { + if (!(gAnimationSequences[index].flags & 4)) { + v2++; + } + } else if (v1 == -1 && (!((flags >> 8) & 1) || !(gAnimationSequences[index].flags & 0x10))) { + v1 = index; + } + } + + if (v1 == -1) { + if (flags & 0x02) { + debugPrint("Unable to begin reserved animation!\n"); + } + + return -1; + } else if (flags & 0x02 || v2 < 20) { + return v1; + } + + return -1; +} + +// 0x413C20 +int _register_priority(int a1) +{ + if (gAnimationSequenceCurrentIndex == -1) { + return -1; + } + + if (a1 == 0) { + return -1; + } + + gAnimationSequences[gAnimationSequenceCurrentIndex].flags |= 0x01; + + return 0; +} + +// 0x413C4C +int reg_anim_clear(Object* a1) +{ + for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { + if (gAnimationSequences[animationSequenceIndex].field_0 == -1000) { + continue; + } + + int animationDescriptionIndex; + for (animationDescriptionIndex = 0; animationDescriptionIndex < gAnimationSequences[animationSequenceIndex].length; animationDescriptionIndex++) { + if (a1 != gAnimationSequences[animationSequenceIndex].animations[animationDescriptionIndex].owner || gAnimationSequences[animationSequenceIndex].animations[animationDescriptionIndex].type == 11) { + continue; + } + + break; + } + + if (animationDescriptionIndex == gAnimationSequences[animationSequenceIndex].length) { + continue; + } + + if (gAnimationSequences[animationSequenceIndex].flags & 0x01) { + return -2; + } + + _anim_set_end(animationSequenceIndex); + + return 0; + } + + return -1; +} + +// 0x413CCC +int reg_anim_end() +{ + if (gAnimationSequenceCurrentIndex == -1) { + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + animationSequence->field_0 = 0; + animationSequence->length = gAnimationDescriptionCurrentIndex; + animationSequence->animationIndex = -1; + animationSequence->flags &= ~0x08; + animationSequence->animations[0].delay = 0; + if (isInCombat()) { + _combat_anim_begin(); + animationSequence->flags |= 0x02; + } + + int v1 = gAnimationSequenceCurrentIndex; + gAnimationSequenceCurrentIndex = -1; + + if (!(animationSequence->flags & 0x10)) { + _anim_set_continue(v1, 1); + } + + return 0; +} + +// 0x413D98 +void _anim_cleanup() +{ + if (gAnimationSequenceCurrentIndex == -1) { + return; + } + + for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { + gAnimationSequences[index].flags &= ~0x18; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + for (int index = 0; index < gAnimationDescriptionCurrentIndex; index++) { + AnimationDescription* animationDescription = &(animationSequence->animations[index]); + if (animationDescription->field_2C != NULL) { + artUnlock(animationDescription->field_2C); + } + + if (animationDescription->type == ANIM_KIND_EXEC && animationDescription->soundProc == _gsnd_anim_sound) { + soundEffectDelete(animationDescription->sound); + } + } + + gAnimationSequenceCurrentIndex = -1; +} + +// 0x413E2C +int _check_registry(Object* obj) +{ + if (gAnimationSequenceCurrentIndex == -1) { + return -1; + } + + if (gAnimationDescriptionCurrentIndex >= 55) { + return -1; + } + + if (obj == NULL) { + return 0; + } + + for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { + AnimationSequence* animationSequence = &(gAnimationSequences[animationSequenceIndex]); + + if (animationSequenceIndex != gAnimationSequenceCurrentIndex && animationSequence->field_0 != -1000) { + for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) { + AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]); + if (obj == animationDescription->owner && animationDescription->type != 11) { + if (!(animationSequence->flags & 0x40)) { + return -1; + } + + _anim_set_end(animationSequenceIndex); + } + } + } + } + + return 0; +} + +// Returns -1 if object is playing some animation. +// +// 0x413EC8 +int animationIsBusy(Object* a1) +{ + if (gAnimationDescriptionCurrentIndex >= ANIMATION_DESCRIPTION_LIST_CAPACITY || a1 == NULL) { + return 0; + } + + for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { + AnimationSequence* animationSequence = &(gAnimationSequences[animationSequenceIndex]); + if (animationSequenceIndex != gAnimationSequenceCurrentIndex && animationSequence->field_0 != -1000) { + for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) { + AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]); + if (a1 != animationDescription->owner) { + continue; + } + + if (animationDescription->type == ANIM_KIND_EXEC) { + continue; + } + + if (animationSequence->length == 1 && animationDescription->anim == ANIM_STAND) { + continue; + } + + return -1; + } + } + } + + return 0; +} + +// 0x413F5C +int reg_anim_obj_move_to_obj(Object* a1, Object* a2, int actionPoints, int delay) +{ + if (_check_registry(a1) == -1 || actionPoints == 0) { + _anim_cleanup(); + return -1; + } + + if (a1->tile == a2->tile && a1->elevation == a2->elevation) { + return 0; + } + + AnimationDescription* animationDescription = &(gAnimationSequences[gAnimationSequenceCurrentIndex].animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_OBJ_MOVE_TO_OBJ; + animationDescription->anim = ANIM_WALK; + animationDescription->owner = a1; + animationDescription->destinationObj = a2; + animationDescription->field_28 = actionPoints; + animationDescription->delay = delay; + + int fid = buildFid((a1->fid & 0xF000000) >> 24, a1->fid & 0xFFF, animationDescription->anim, (a1->fid & 0xF000) >> 12, a1->rotation + 1); + + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return reg_anim_set_rotation_to_tile(a1, a2->tile); +} + +// 0x41405C +int reg_anim_obj_run_to_obj(Object* owner, Object* destination, int actionPoints, int delay) +{ + MessageListItem msg; + const char* text; + const char* name; + char formatted_text[90]; // TODO: Size is probably wrong. + + if (_check_registry(owner) == -1 || actionPoints == 0) { + _anim_cleanup(); + return -1; + } + + if (owner->tile == destination->tile && owner->elevation == destination->elevation) { + return 0; + } + + if (critterIsEncumbered(owner)) { + if (objectIsPartyMember(owner)) { + if (owner == gDude) { + // You are overloaded. + text = getmsg(&gMiscMessageList, &msg, 8000); + strcpy(formatted_text, text); + } else { + // %s is overloaded. + name = critterGetName(owner); + text = getmsg(&gMiscMessageList, &msg, 8001); + sprintf(formatted_text, text, name); + } + displayMonitorAddMessage(formatted_text); + } + return reg_anim_obj_move_to_obj(owner, destination, actionPoints, delay); + } + + AnimationDescription* animationDescription = &(gAnimationSequences[gAnimationSequenceCurrentIndex].animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_OBJ_MOVE_TO_OBJ; + animationDescription->owner = owner; + animationDescription->destinationObj = destination; + + if ((owner->fid & 0xF000000) >> 24 == 1 && (owner->data.critter.combat.results & (DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT)) + || owner == gDude && dudeHasState(0) && !perkGetRank(gDude, PERK_SILENT_RUNNING) + || !artExists(buildFid((owner->fid & 0xF000000) >> 24, owner->fid & 0xFFF, ANIM_RUNNING, 0, owner->rotation + 1))) { + animationDescription->anim = ANIM_WALK; + } else { + animationDescription->anim = ANIM_RUNNING; + } + + animationDescription->field_28 = actionPoints; + animationDescription->delay = delay; + + int fid = buildFid((owner->fid & 0xF000000) >> 24, owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); + + animationDescription->field_2C = NULL; + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + return reg_anim_set_rotation_to_tile(owner, destination->tile); +} + +// 0x414294 +int reg_anim_obj_move_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay) +{ + AnimationDescription* ptr; + int fid; + + if (_check_registry(obj) == -1 || actionPoints == 0) { + _anim_cleanup(); + return -1; + } + + if (tile_num == obj->tile && elev == obj->elevation) { + return 0; + } + + ptr = &(gAnimationSequences[gAnimationSequenceCurrentIndex].animations[gAnimationDescriptionCurrentIndex]); + ptr->type = ANIM_KIND_OBJ_MOVE_TO_TILE; + ptr->anim = ANIM_WALK; + ptr->owner = obj; + ptr->tile = tile_num; + ptr->elevation = elev; + ptr->field_28 = actionPoints; + ptr->delay = delay; + + ptr->field_2C = NULL; + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ptr->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(ptr->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(ptr->field_2C); + ptr->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414394 +int reg_anim_obj_run_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay) +{ + MessageListItem msg; + const char* text; + const char* name; + char str[72]; // TODO: Size is probably wrong. + AnimationDescription* animationDescription; + int fid; + + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + if (actionPoints == 0) { + _anim_cleanup(); + return -1; + } + + if (tile_num == obj->tile && elev == obj->elevation) { + return 0; + } + + if (critterIsEncumbered(obj)) { + if (objectIsPartyMember(obj)) { + if (obj == gDude) { + // You are overloaded. + text = getmsg(&gMiscMessageList, &msg, 8000); + strcpy(str, text); + } else { + // %s is overloaded. + name = critterGetName(obj); + text = getmsg(&gMiscMessageList, &msg, 8001); + sprintf(str, text, name); + } + + displayMonitorAddMessage(str); + } + + return reg_anim_obj_move_to_tile(obj, tile_num, elev, actionPoints, delay); + } + + animationDescription = &(gAnimationSequences[gAnimationSequenceCurrentIndex].animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_OBJ_MOVE_TO_TILE; + animationDescription->owner = obj; + animationDescription->tile = tile_num; + animationDescription->elevation = elev; + + // TODO: Check. + if ((obj->fid & 0xF000000) >> 24 == 1 && (obj->data.critter.combat.results & (DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT)) + || obj == gDude && dudeHasState(0) && !perkGetRank(gDude, PERK_SILENT_RUNNING) + || !artExists(buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_RUNNING, 0, obj->rotation + 1))) { + animationDescription->anim = ANIM_WALK; + } else { + animationDescription->anim = ANIM_RUNNING; + } + + animationDescription->field_28 = actionPoints; + animationDescription->delay = delay; + + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, animationDescription->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + + animationDescription->field_2C = NULL; + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4145D0 +int reg_anim_2(Object* obj, int tile_num, int elev, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + if (tile_num == obj->tile && elev == obj->elevation) { + return 0; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_2; + animationDescription->owner = obj; + animationDescription->tile = tile_num; + animationDescription->elevation = elev; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, animationDescription->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4146C4 +int reg_anim_knockdown(Object* obj, int tile, int elev, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + if (tile == obj->tile && elev == obj->elevation) { + return 0; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_KNOCKDOWN; + animationDescription->owner = obj; + animationDescription->tile = tile; + animationDescription->elevation = elev; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, animationDescription->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4149D0 +int reg_anim_animate(Object* obj, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_ANIMATE; + animationDescription->owner = obj; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, animationDescription->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414AA8 +int reg_anim_animate_reverse(Object* obj, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_ANIMATE_REVERSE; + animationDescription->owner = obj; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, animationDescription->anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414B7C +int reg_anim_6(Object* obj, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_6; + animationDescription->owner = obj; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414C50 +int reg_anim_set_rotation_to_tile(Object* owner, int tile) +{ + if (_check_registry(owner) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_SET_ROTATION_TO_TILE; + animationDescription->delay = -1; + animationDescription->field_2C = NULL; + animationDescription->owner = owner; + animationDescription->tile = tile; + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414CC8 +int reg_anim_rotate_clockwise(Object* obj) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_ROTATE_CLOCKWISE; + animationDescription->delay = -1; + animationDescription->field_2C = NULL; + animationDescription->owner = obj; + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414D38 +int reg_anim_rotate_counter_clockwise(Object* obj) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_ROTATE_COUNTER_CLOCKWISE; + animationDescription->delay = -1; + animationDescription->field_2C = NULL; + animationDescription->owner = obj; + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414E20 +int reg_anim_hide(Object* obj) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_HIDE; + animationDescription->delay = -1; + animationDescription->field_2C = NULL; + animationDescription->field_24 = 1; + animationDescription->owner = obj; + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414E98 +int reg_anim_11_0(Object* a1, Object* a2, AnimationProc* proc, int delay) +{ + if (_check_registry(NULL) == -1 || proc == NULL) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_EXEC; + animationDescription->field_24 = 0; + animationDescription->field_2C = NULL; + animationDescription->owner = a2; + animationDescription->destinationObj = a1; + animationDescription->proc = proc; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414F20 +int reg_anim_12(Object* a1, Object* a2, void* a3, AnimationProc2* proc, int delay) +{ + if (_check_registry(NULL) == -1 || !proc) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_EXEC_2; + animationDescription->field_24 = 0; + animationDescription->field_2C = NULL; + animationDescription->owner = a2; + animationDescription->destinationObj = a1; + animationDescription->field_20 = proc; + animationDescription->field_28_void = a3; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x414FAC +int reg_anim_11_1(Object* a1, Object* a2, AnimationProc* proc, int delay) +{ + if (_check_registry(NULL) == -1 || proc == NULL) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_EXEC; + animationDescription->field_24 = 1; + animationDescription->field_2C = NULL; + animationDescription->owner = a2; + animationDescription->destinationObj = a1; + animationDescription->proc = proc; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4150A8 +int reg_anim_15(Object* obj, int a2, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_15; + animationDescription->field_2C = NULL; + animationDescription->owner = obj; + animationDescription->field_24 = a2; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x41518C +int reg_anim_17(Object* obj, int fid, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_17; + animationDescription->owner = obj; + animationDescription->fid = fid; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x415238 +int reg_anim_18(Object* obj, int weaponAnimationCode, int delay) +{ + const char* sfx = sfxBuildCharName(obj, ANIM_TAKE_OUT, weaponAnimationCode); + if (reg_anim_play_sfx(obj, sfx, delay) == -1) { + return -1; + } + + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_18; + animationDescription->anim = ANIM_TAKE_OUT; + animationDescription->delay = 0; + animationDescription->owner = obj; + animationDescription->weaponAnimationCode = weaponAnimationCode; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_TAKE_OUT, weaponAnimationCode, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x415334 +int reg_anim_update_light(Object* obj, int lightDistance, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_19; + animationDescription->field_2C = NULL; + animationDescription->owner = obj; + animationDescription->lightDistance = lightDistance; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x41541C +int reg_anim_play_sfx(Object* obj, const char* soundEffectName, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_EXEC; + animationDescription->owner = obj; + if (soundEffectName != NULL) { + int volume = _gsound_compute_relative_volume(obj); + animationDescription->sound = soundEffectLoadWithVolume(soundEffectName, obj, volume); + if (animationDescription->sound != NULL) { + animationDescription->soundProc = _gsnd_anim_sound; + } else { + animationDescription->type = ANIM_KIND_28; + } + } else { + animationDescription->type = ANIM_KIND_28; + } + + animationDescription->field_2C = NULL; + animationDescription->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4154C4 +int reg_anim_animate_forever(Object* obj, int anim, int delay) +{ + if (_check_registry(obj) == -1) { + _anim_cleanup(); + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[gAnimationSequenceCurrentIndex]); + AnimationDescription* animationDescription = &(animationSequence->animations[gAnimationDescriptionCurrentIndex]); + animationDescription->type = ANIM_KIND_ANIMATE_FOREVER; + animationDescription->owner = obj; + animationDescription->anim = anim; + animationDescription->delay = delay; + animationDescription->field_2C = NULL; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + if (artLock(fid, &(animationDescription->field_2C)) == NULL) { + _anim_cleanup(); + return -1; + } + + artUnlock(animationDescription->field_2C); + animationDescription->field_2C = NULL; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x415598 +int reg_anim_26(int a1, int delay) +{ + AnimationDescription* ptr; + int v5; + + if (_check_registry(NULL) == -1) { + _anim_cleanup(); + return -1; + } + + v5 = _anim_free_slot(a1 | 0x0100); + if (v5 == -1) { + return -1; + } + + gAnimationSequences[v5].flags = 16; + + ptr = &(gAnimationSequences[gAnimationSequenceCurrentIndex].animations[gAnimationDescriptionCurrentIndex]); + ptr->owner = NULL; + ptr->type = ANIM_KIND_26; + ptr->field_2C = NULL; + ptr->field_28 = v5; + ptr->delay = delay; + + gAnimationDescriptionCurrentIndex++; + + return 0; +} + +// 0x4156A8 +int animationRunSequence(int animationSequenceIndex) +{ + if (animationSequenceIndex == -1) { + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[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->type) { + case ANIM_KIND_OBJ_MOVE_TO_OBJ: + rc = animateMoveObjectToObject(animationDescription->owner, animationDescription->destinationObj, animationDescription->field_28, animationDescription->anim, animationSequenceIndex); + break; + case ANIM_KIND_OBJ_MOVE_TO_TILE: + rc = animateMoveObjectToTile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->field_28, animationDescription->anim, animationSequenceIndex); + break; + case ANIM_KIND_2: + rc = _anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, 0x00); + break; + case ANIM_KIND_KNOCKDOWN: + rc = _anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, 0x10); + break; + case ANIM_KIND_ANIMATE: + rc = _anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0); + break; + case ANIM_KIND_ANIMATE_REVERSE: + rc = _anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0x01); + break; + case ANIM_KIND_6: + rc = _anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0x40); + if (rc == -1) { + Rect rect; + if (objectHide(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->elevation); + } + + if (animationSequenceIndex != -1) { + _anim_set_continue(animationSequenceIndex, 0); + } + rc = 0; + } + break; + case ANIM_KIND_ANIMATE_FOREVER: + rc = _anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0x80); + break; + case ANIM_KIND_SET_ROTATION_TO_TILE: + if (!_critter_is_prone(animationDescription->owner)) { + int rotation = tileGetRotationTo(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 = actionRotate(animationDescription->owner, 1, animationSequenceIndex); + break; + case ANIM_KIND_ROTATE_COUNTER_CLOCKWISE: + rc = actionRotate(animationDescription->owner, -1, animationSequenceIndex); + break; + case ANIM_KIND_HIDE: + if (objectHide(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + if (animationSequenceIndex != -1) { + _anim_set_continue(animationSequenceIndex, 0); + } + rc = 0; + break; + case ANIM_KIND_EXEC: + rc = animationDescription->proc(animationDescription->destinationObj, animationDescription->owner); + if (rc == 0) { + rc = _anim_set_continue(animationSequenceIndex, 0); + } + break; + case ANIM_KIND_EXEC_2: + rc = animationDescription->field_20(animationDescription->destinationObj, animationDescription->owner, animationDescription->field_28_obj); + if (rc == 0) { + rc = _anim_set_continue(animationSequenceIndex, 0); + } + break; + case ANIM_KIND_14: + if (animationDescription->field_24 == 32) { + if (_obj_turn_on_light(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } else if (animationDescription->field_24 == 1) { + if (objectHide(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } else { + animationDescription->owner->flags |= animationDescription->field_24; + } + + rc = _anim_set_continue(animationSequenceIndex, 0); + break; + case ANIM_KIND_15: + if (animationDescription->field_24 == 32) { + if (_obj_turn_off_light(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } else if (animationDescription->field_24 == 1) { + if (objectShow(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } else { + animationDescription->owner->flags &= ~animationDescription->field_24; + } + + rc = _anim_set_continue(animationSequenceIndex, 0); + break; + case ANIM_KIND_16: + if (_obj_toggle_flat(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + rc = _anim_set_continue(animationSequenceIndex, 0); + break; + case ANIM_KIND_17: + rc = _anim_change_fid(animationDescription->owner, animationSequenceIndex, animationDescription->fid); + break; + case ANIM_KIND_18: + rc = _anim_animate(animationDescription->owner, ANIM_TAKE_OUT, animationSequenceIndex, animationDescription->tile); + break; + case ANIM_KIND_19: + objectSetLight(animationDescription->owner, animationDescription->lightDistance, animationDescription->owner->lightIntensity, &rect); + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + rc = _anim_set_continue(animationSequenceIndex, 0); + break; + case ANIM_KIND_20: + rc = _anim_move_on_stairs(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex); + break; + case ANIM_KIND_23: + rc = _check_for_falling(animationDescription->owner, animationDescription->anim, animationSequenceIndex); + break; + case ANIM_KIND_24: + if (animationDescription->tile) { + if (objectEnableOutline(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } else { + if (objectDisableOutline(animationDescription->owner, &rect) == 0) { + tileWindowRefreshRect(&rect, animationDescription->owner->elevation); + } + } + rc = _anim_set_continue(animationSequenceIndex, 0); + break; + case ANIM_KIND_26: + gAnimationSequences[animationDescription->field_28].flags &= ~0x10; + rc = _anim_set_continue(animationDescription->field_28, 1); + if (rc != -1) { + rc = _anim_set_continue(animationSequenceIndex, 0); + } + break; + case ANIM_KIND_28: + 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 +int _anim_set_continue(int animationSequenceIndex, int a2) +{ + if (animationSequenceIndex == -1) { + return -1; + } + + AnimationSequence* animationSequence = &(gAnimationSequences[animationSequenceIndex]); + if (animationSequence->field_0 == -1000) { + return -1; + } + + animationSequence->animationIndex++; + if (animationSequence->animationIndex == animationSequence->length) { + return _anim_set_end(animationSequenceIndex); + } else { + if (a2) { + return animationRunSequence(animationSequenceIndex); + } + } + + return 0; +} + +// 0x415B9C +int _anim_set_end(int animationSequenceIndex) +{ + AnimationSequence* animationSequence; + AnimationDescription* animationDescription; + STRUCT_530014* ptr_530014; + int i; + Rect v27; + + if (animationSequenceIndex == -1) { + return -1; + } + + animationSequence = &(gAnimationSequences[animationSequenceIndex]); + if (animationSequence->field_0 == -1000) { + return -1; + } + + for (i = 0; i < _curr_sad; i++) { + ptr_530014 = &(_sad[i]); + if (ptr_530014->animationSequenceIndex == animationSequenceIndex) { + ptr_530014->field_20 = -1000; + } + } + + for (i = 0; i < animationSequence->length; i++) { + animationDescription = &(animationSequence->animations[i]); + if (animationDescription->type == ANIM_KIND_HIDE && ((i < animationSequence->animationIndex) || (animationDescription->field_24 & 0x01))) { + objectDestroy(animationDescription->owner, &v27); + tileWindowRefreshRect(&v27, animationDescription->owner->elevation); + } + } + + for (i = 0; i < animationSequence->length; i++) { + animationDescription = &(animationSequence->animations[i]); + if (animationDescription->field_2C) { + artUnlock(animationDescription->field_2C); + } + + if (animationDescription->type != 11 && animationDescription->type != 12) { + // TODO: Check. + if (animationDescription->type != ANIM_KIND_26) { + Object* owner = animationDescription->owner; + if (((owner->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + int j = 0; + for (; j < i; j++) { + AnimationDescription* ad = &(animationSequence->animations[j]); + if (owner == ad->owner) { + if (ad->type != 11 && ad->type != 12) { + break; + } + } + } + + if (i == j) { + int k = 0; + for (; k < animationSequence->animationIndex; k++) { + AnimationDescription* ad = &(animationSequence->animations[k]); + if (ad->type == 10 && 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 & 0x80) == 0 && !_critter_is_prone(owner)) { + _dude_stand(owner, owner->rotation, -1); + } + } + } + } + } + } else if (i >= animationSequence->field_0) { + if (animationDescription->field_24 & 0x01) { + animationDescription->proc(animationDescription->destinationObj, animationDescription->owner); + } else { + if (animationDescription->type == ANIM_KIND_EXEC && animationDescription->soundProc == _gsnd_anim_sound) { + soundEffectDelete(animationDescription->sound); + } + } + } + } + + animationSequence->animationIndex = -1; + animationSequence->field_0 = -1000; + if ((animationSequence->flags & 0x02) != 0) { + _combat_anim_finished(); + } + + if (_anim_in_bk) { + animationSequence->flags = 0x20; + } else { + animationSequence->flags = 0; + } + + return 0; +} + +// 0x415E24 +bool canUseDoor(Object* critter, Object* door) +{ + if (critter == gDude) { + if (!_obj_portal_is_walk_thru(door)) { + return false; + } + } + + if ((critter->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return false; + } + + if ((door->fid & 0xF000000) >> 24 != OBJ_TYPE_SCENERY) { + return false; + } + + int bodyType = critterGetBodyType(critter); + if (bodyType != BODY_TYPE_BIPED && bodyType != BODY_TYPE_ROBOTIC) { + return false; + } + + Proto* proto; + if (protoGetProto(door->pid, &proto) == -1) { + return false; + } + + if (proto->scenery.type != SCENERY_TYPE_DOOR) { + return false; + } + + if (objectIsLocked(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 pathfinderFindPath(object, from, to, rotations, a5, _obj_blocking_at); +} + +// 0x415EFC +int pathfinderFindPath(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 ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + isCritter = true; + kt = critterGetKillType(object); + } + + bool isNotInCombat = !isInCombat(); + + memset(gPathfinderProcessedTiles, 0, sizeof(gPathfinderProcessedTiles)); + + gPathfinderProcessedTiles[from / 8] |= 1 << (from & 7); + + gOpenPathNodeList[0].tile = from; + gOpenPathNodeList[0].from = -1; + gOpenPathNodeList[0].rotation = 0; + gOpenPathNodeList[0].field_C = _tile_idistance(from, to); + gOpenPathNodeList[0].field_10 = 0; + + for (int index = 1; index < 2000; index += 1) { + gOpenPathNodeList[index].tile = -1; + } + + int toScreenX; + int toScreenY; + tileToScreenXY(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 = &(gOpenPathNodeList[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 = &(gOpenPathNodeList[v63]); + + memcpy(&temp, curr, sizeof(temp)); + + openPathNodeListLength -= 1; + + curr->tile = -1; + + if (temp.tile == to) { + if (openPathNodeListLength == 0) { + openPathNodeListLength = 1; + } + break; + } + + PathNode* curr1 = &(gClosedPathNodeList[closedPathNodeListLength]); + memcpy(curr1, &temp, sizeof(temp)); + + closedPathNodeListLength += 1; + + if (closedPathNodeListLength == 2000) { + return 0; + } + + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + int tile = tileGetTileInDirection(temp.tile, rotation, 1); + int bit = 1 << (tile & 7); + if ((gPathfinderProcessedTiles[tile / 8] & bit) != 0) { + continue; + } + + if (tile != to) { + Object* v24 = callback(object, tile, object->elevation); + if (v24 != NULL) { + if (!canUseDoor(object, v24)) { + continue; + } + } + } + + int v25 = 0; + for (; v25 < 2000; v25++) { + if (gOpenPathNodeList[v25].tile == -1) { + break; + } + } + + openPathNodeListLength += 1; + + if (openPathNodeListLength == 2000) { + return 0; + } + + gPathfinderProcessedTiles[tile / 8] |= bit; + + PathNode* v27 = &(gOpenPathNodeList[v25]); + v27->tile = tile; + v27->from = temp.tile; + v27->rotation = rotation; + + int newX; + int newY; + tileToScreenXY(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 = objectFindFirstAtLocation(object->elevation, v27->tile); + while (o != NULL) { + if (o->pid >= 0x20003D9 && o->pid <= 0x20003DC) { + break; + } + o = objectFindNextAtLocation(); + } + + 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 (gClosedPathNodeList[j].tile != temp.from) { + j++; + } + + PathNode* v36 = &(gClosedPathNodeList[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 _tile_idistance(int tile1, int tile2) +{ + int x1; + int y1; + tileToScreenXY(tile1, &x1, &y1, gElevation); + + int x2; + int y2; + tileToScreenXY(tile2, &x2, &y2, gElevation); + + return _idist(x1, y1, x2, y2); +} + +// 0x4163AC +int _make_straight_path(Object* a1, int from, int to, STRUCT_530014_28* 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, STRUCT_530014_28* a4, Object** a5, int a6, Object* (*a7)(Object*, int, int)) +{ + if (a5 != NULL) { + Object* v11 = a7(a1, from, a1->elevation); + if (v11 != NULL) { + if (v11 != *a5 && (a6 != 32 || (v11->flags & OBJECT_FLAG_0x80000000) == 0)) { + *a5 = v11; + return 0; + } + } + } + + int fromX; + int fromY; + tileToScreenXY(from, &fromX, &fromY, a1->elevation); + fromX += 16; + fromY += 8; + + int toX; + int toY; + tileToScreenXY(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 v50 = from; + int v22 = 0; + int tile; + + if (v48 <= v47) { + int middle = v48 - v47 / 2; + while (true) { + tile = tileFromScreenXY(tileX, tileY, a1->elevation); + + v22 += 1; + if (v22 == a6) { + if (pathNodeIndex >= 200) { + return 0; + } + + if (a4 != NULL) { + STRUCT_530014_28* pathNode = &(a4[pathNodeIndex]); + pathNode->tile = tile; + pathNode->elevation = a1->elevation; + + tileToScreenXY(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 != v50) { + if (a5 != NULL) { + Object* obj = a7(a1, tile, a1->elevation); + if (obj != NULL) { + if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_FLAG_0x80000000) == 0)) { + *a5 = obj; + break; + } + } + } + v50 = tile; + } + } + } else { + int middle = v47 - v48 / 2; + while (true) { + tile = tileFromScreenXY(tileX, tileY, a1->elevation); + + v22 += 1; + if (v22 == a6) { + if (pathNodeIndex >= 200) { + return 0; + } + + if (a4 != NULL) { + STRUCT_530014_28* pathNode = &(a4[pathNodeIndex]); + pathNode->tile = tile; + pathNode->elevation = a1->elevation; + + tileToScreenXY(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 != v50) { + if (a5 != NULL) { + Object* obj = a7(a1, tile, a1->elevation); + if (obj != NULL) { + if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_FLAG_0x80000000) == 0)) { + *a5 = obj; + break; + } + } + } + } + } + } + + if (v22 != 0) { + if (pathNodeIndex >= 200) { + return 0; + } + + if (a4 != NULL) { + STRUCT_530014_28* pathNode = &(a4[pathNodeIndex]); + pathNode->tile = tile; + pathNode->elevation = a1->elevation; + + tileToScreenXY(tile, &fromX, &fromY, a1->elevation); + pathNode->x = tileX - fromX - 16; + pathNode->y = tileY - fromY - 8; + } + + pathNodeIndex += 1; + } else { + if (pathNodeIndex > 0 && a4 != NULL) { + a4[pathNodeIndex - 1].elevation = a1->elevation; + } + } + + return pathNodeIndex; +} + +// 0x4167F8 +int animateMoveObjectToObject(Object* a1, Object* a2, int a3, int anim, int animationSequenceIndex) +{ + int v10; + int v13; + STRUCT_530014* ptr; + + int hidden = (a2->flags & OBJECT_HIDDEN); + a2->flags |= OBJECT_HIDDEN; + + v10 = _anim_move(a1, a2->tile, a2->elevation, -1, anim, 0, animationSequenceIndex); + + if (hidden == 0) { + a2->flags &= ~OBJECT_HIDDEN; + } + + if (v10 == -1) { + return -1; + } + + ptr = &(_sad[v10]); + v13 = (((a1->flags & OBJECT_FLAG_0x800) != 0) + 1); // TODO: What the hell is this? + ptr->field_1C -= v13; + if (ptr->field_1C <= 0) { + ptr->field_20 = -1000; + _anim_set_continue(animationSequenceIndex, 0); + } + + if (v13) { + ptr->field_24 = tileGetTileInDirection(a2->tile, ptr->field_24 + v13 + ptr->field_1C + 3, 1); + } + + if (v13 == 2) { + ptr->field_24 = tileGetTileInDirection(ptr->field_24, ptr->rotations[ptr->field_1C], 1); + } + + if (a3 != -1 && a3 < ptr->field_1C) { + ptr->field_1C = a3; + } + + return 0; +} + +// 0x416CFC +int animateMoveObjectToTile(Object* obj, int tile, int elev, int a4, int anim, int animationSequenceIndex) +{ + STRUCT_530014* ptr; + int v1; + + v1 = _anim_move(obj, tile, elev, -1, anim, 0, animationSequenceIndex); + if (v1 == -1) { + return -1; + } + + if (_obj_blocking_at(obj, tile, elev)) { + ptr = &(_sad[v1]); + ptr->field_1C--; + if (ptr->field_1C <= 0) { + ptr->field_20 = -1000; + _anim_set_continue(animationSequenceIndex, 0); + } + + ptr->field_24 = tileGetTileInDirection(tile, ptr->rotations[ptr->field_1C], 1); + if (a4 != -1 && a4 < ptr->field_1C) { + ptr->field_1C = a4; + } + } + + return 0; +} + +// 0x416DFC +int _anim_move(Object* obj, int tile, int elev, int a3, int anim, int a5, int animationSequenceIndex) +{ + STRUCT_530014* ptr; + + if (_curr_sad == 24) { + return -1; + } + + ptr = &(_sad[_curr_sad]); + ptr->obj = obj; + + if (a5) { + ptr->flags = 0x20; + } else { + ptr->flags = 0; + } + + ptr->field_20 = -2000; + ptr->fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + ptr->field_10 = 0; + ptr->field_14 = _compute_tpf(obj, ptr->fid); + ptr->field_24 = tile; + ptr->animationSequenceIndex = animationSequenceIndex; + ptr->field_C = anim; + + ptr->field_1C = _make_path(obj, obj->tile, tile, ptr->rotations, a5); + if (ptr->field_1C == 0) { + ptr->field_20 = -1000; + return -1; + } + + if (a3 != -1 && ptr->field_1C > a3) { + ptr->field_1C = a3; + } + + return _curr_sad++; +} + +// 0x416F54 +int _anim_move_straight_to_tile(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex, int flags) +{ + if (_curr_sad == 24) { + return -1; + } + + STRUCT_530014* ptr = &(_sad[_curr_sad]); + ptr->obj = obj; + ptr->flags = flags | 0x02; + if (anim == -1) { + ptr->fid = obj->fid; + ptr->flags |= 0x04; + } else { + ptr->fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + } + ptr->field_20 = -2000; + ptr->field_10 = 0; + ptr->field_14 = _compute_tpf(obj, ptr->fid); + ptr->animationSequenceIndex = animationSequenceIndex; + + int v15; + if (((obj->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + if (((obj->fid & 0xFF0000) >> 16) == ANIM_JUMP_BEGIN) + v15 = 16; + else + v15 = 4; + } else { + v15 = 32; + } + + ptr->field_1C = _make_straight_path(obj, obj->tile, tile, ptr->field_28, NULL, v15); + if (ptr->field_1C == 0) { + ptr->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) +{ + STRUCT_530014* ptr; + + if (_curr_sad == 24) { + return -1; + } + + ptr = &(_sad[_curr_sad]); + ptr->flags = 0x02; + ptr->obj = obj; + if (anim == -1) { + ptr->fid = obj->fid; + ptr->flags |= 0x04; + } else { + ptr->fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + } + ptr->field_20 = -2000; + ptr->field_10 = 0; + ptr->field_14 = _compute_tpf(obj, ptr->fid); + ptr->animationSequenceIndex = animationSequenceIndex; + // TODO: Incomplete. + // ptr->field_1C = _make_stair_path(obj, obj->tile_index, obj->elevation, tile, elevation, ptr->field_28, 0); + if (ptr->field_1C == 0) { + ptr->field_20 = -1000; + return -1; + } + + _curr_sad++; + + return 0; +} + +// 0x417248 +int _check_for_falling(Object* obj, int anim, int a3) +{ + STRUCT_530014* ptr; + + if (_curr_sad == 24) { + return -1; + } + + if (_check_gravity(obj->tile, obj->elevation) == obj->elevation) { + return -1; + } + + ptr = &(_sad[_curr_sad]); + ptr->flags = 0x02; + ptr->obj = obj; + if (anim == -1) { + ptr->fid = obj->fid; + ptr->flags |= 0x04; + } else { + ptr->fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + } + ptr->field_20 = -2000; + ptr->field_10 = 0; + ptr->field_14 = _compute_tpf(obj, ptr->fid); + ptr->animationSequenceIndex = a3; + ptr->field_1C = _make_straight_path_func(obj, obj->tile, obj->tile, ptr->field_28, 0, 16, _obj_blocking_at); + if (ptr->field_1C == 0) { + ptr->field_20 = -1000; + return -1; + } + + _curr_sad++; + + return 0; +} + +// 0x417360 +void _object_move(int index) +{ + STRUCT_530014* p530014 = &(_sad[index]); + Object* object = p530014->obj; + + Rect dirty; + Rect temp; + + if (p530014->field_20 == -2000) { + objectSetLocation(object, object->tile, object->elevation, &dirty); + + objectSetFrame(object, 0, &temp); + rectUnion(&dirty, &temp, &dirty); + + objectSetRotation(object, p530014->rotations[0], &temp); + rectUnion(&dirty, &temp, &dirty); + + int fid = buildFid((object->fid & 0xF000000) >> 24, object->fid & 0xFFF, p530014->field_C, (object->fid & 0xF000) >> 12, object->rotation + 1); + objectSetFid(object, fid, &temp); + rectUnion(&dirty, &temp, &dirty); + + p530014->field_20 = 0; + } else { + objectSetNextFrame(object, &dirty); + } + + int frameX; + int frameY; + + CacheEntry* cacheHandle; + Art* art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + artGetFrameOffsets(art, object->frame, object->rotation, &frameX, &frameY); + artUnlock(cacheHandle); + } else { + frameX = 0; + frameY = 0; + } + + _obj_offset(object, frameX, frameY, &temp); + rectUnion(&dirty, &temp, &dirty); + + int rotation = p530014->rotations[p530014->field_20]; + int y = dword_51D984[rotation]; + int x = _off_tile[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 = tileGetTileInDirection(object->tile, rotation, 1); + Object* v12 = _obj_blocking_at(object, v10, object->elevation); + if (v12 != NULL) { + if (!canUseDoor(object, v12)) { + p530014->field_1C = _make_path(object, object->tile, p530014->field_24, p530014->rotations, 1); + if (p530014->field_1C != 0) { + objectSetLocation(object, object->tile, object->elevation, &temp); + rectUnion(&dirty, &temp, &dirty); + + objectSetFrame(object, 0, &temp); + rectUnion(&dirty, &temp, &dirty); + + objectSetRotation(object, p530014->rotations[0], &temp); + rectUnion(&dirty, &temp, &dirty); + + p530014->field_20 = 0; + } else { + p530014->field_20 = -1000; + } + v10 = -1; + } else { + _obj_use_door(object, v12, 0); + } + } + + if (v10 != -1) { + objectSetLocation(object, v10, object->elevation, &temp); + rectUnion(&dirty, &temp, &dirty); + + int v17 = 0; + if (isInCombat() && ((object->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + int v18 = critterGetMovementPointCostAdjustedForCrippledLegs(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 == gDude) { + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + + v17 = (object->data.critter.combat.ap + _combat_free_move) <= 0; + } + + p530014->field_20 += 1; + + if (p530014->field_20 == p530014->field_1C || v17) { + p530014->field_20 = -1000; + } else { + objectSetRotation(object, p530014->rotations[p530014->field_20], &temp); + rectUnion(&dirty, &temp, &dirty); + + _obj_offset(object, x, y, &temp); + rectUnion(&dirty, &temp, &dirty); + } + } + } + + tileWindowRefreshRect(&dirty, object->elevation); + if (p530014->field_20 == -1000) { + _anim_set_continue(p530014->animationSequenceIndex, 1); + } +} + +// 0x4177C0 +void _object_straight_move(int index) +{ + STRUCT_530014* p530014 = &(_sad[index]); + Object* object = p530014->obj; + + Rect dirtyRect; + Rect temp; + + if (p530014->field_20 == -2000) { + objectSetFid(object, p530014->fid, &dirtyRect); + p530014->field_20 = 0; + } else { + objectGetRect(object, &dirtyRect); + } + + CacheEntry* cacheHandle; + Art* art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + int lastFrame = artGetFrameCount(art) - 1; + artUnlock(cacheHandle); + + if ((p530014->flags & 0x04) == 0 && ((p530014->flags & 0x10) == 0 || lastFrame > object->frame)) { + objectSetNextFrame(object, &temp); + rectUnion(&dirtyRect, &temp, &dirtyRect); + } + + if (p530014->field_20 < p530014->field_1C) { + STRUCT_530014_28* v12 = &(p530014->field_28[p530014->field_20]); + + objectSetLocation(object, v12->tile, v12->elevation, &temp); + rectUnion(&dirtyRect, &temp, &dirtyRect); + + _obj_offset(object, v12->x, v12->y, &temp); + rectUnion(&dirtyRect, &temp, &dirtyRect); + + p530014->field_20++; + } + + if (p530014->field_20 == p530014->field_1C && ((p530014->flags & 0x10) == 0 || object->frame == lastFrame)) { + p530014->field_20 = -1000; + } + + tileWindowRefreshRect(&dirtyRect, p530014->obj->elevation); + + if (p530014->field_20 == -1000) { + _anim_set_continue(p530014->animationSequenceIndex, 1); + } + } +} + +// 0x4179B8 +int _anim_animate(Object* obj, int anim, int animationSequenceIndex, int flags) +{ + if (_curr_sad == 24) { + return -1; + } + + STRUCT_530014* ptr = &(_sad[_curr_sad]); + + int fid; + if (anim == ANIM_TAKE_OUT) { + ptr->flags = 0; + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_TAKE_OUT, flags, obj->rotation + 1); + } else { + ptr->flags = flags; + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + } + + if (!artExists(fid)) { + return -1; + } + + ptr->obj = obj; + ptr->fid = fid; + ptr->animationSequenceIndex = animationSequenceIndex; + ptr->field_10 = 0; + ptr->field_14 = _compute_tpf(obj, ptr->fid); + ptr->field_20 = 0; + ptr->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++) { + STRUCT_530014* p530014 = &(_sad[index]); + if (p530014->field_20 == -1000) { + continue; + } + + Object* object = p530014->obj; + + int time = _get_time(); + if (getTicksBetween(time, p530014->field_10) < p530014->field_14) { + continue; + } + + p530014->field_10 = time; + + if (animationRunSequence(p530014->animationSequenceIndex) == -1) { + continue; + } + + if (p530014->field_1C > 0) { + if ((p530014->flags & 0x02) != 0) { + _object_straight_move(index); + } else { + int savedTile = object->tile; + _object_move(index); + if (savedTile != object->tile) { + scriptsExecSpatialProc(object, object->tile, object->elevation); + } + } + continue; + } + + if (p530014->field_20 == 0) { + for (int index = 0; index < _curr_sad; index++) { + STRUCT_530014* other530014 = &(_sad[index]); + if (object == other530014->obj && other530014->field_20 == -2000) { + other530014->field_20 = -1000; + _anim_set_continue(other530014->animationSequenceIndex, 1); + } + } + p530014->field_20 = -2000; + } + + Rect dirtyRect; + Rect tempRect; + + objectGetRect(object, &dirtyRect); + + if (object->fid == p530014->fid) { + if ((p530014->flags & 0x01) == 0) { + CacheEntry* cacheHandle; + Art* art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + if ((p530014->flags & 0x80) == 0 && object->frame == artGetFrameCount(art) - 1) { + p530014->field_20 = -1000; + artUnlock(cacheHandle); + + if ((p530014->flags & 0x40) != 0 && objectHide(object, &tempRect) == 0) { + tileWindowRefreshRect(&tempRect, object->elevation); + } + + _anim_set_continue(p530014->animationSequenceIndex, 1); + continue; + } else { + objectSetNextFrame(object, &tempRect); + rectUnion(&dirtyRect, &tempRect, &dirtyRect); + + int frameX; + int frameY; + artGetFrameOffsets(art, object->frame, object->rotation, &frameX, &frameY); + + _obj_offset(object, frameX, frameY, &tempRect); + rectUnion(&dirtyRect, &tempRect, &dirtyRect); + + artUnlock(cacheHandle); + } + } + + tileWindowRefreshRect(&dirtyRect, gElevation); + + continue; + } + + if ((p530014->flags & 0x80) != 0 || object->frame != 0) { + int x; + int y; + + CacheEntry* cacheHandle; + Art* art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + artGetFrameOffsets(art, object->frame, object->rotation, &x, &y); + artUnlock(cacheHandle); + } + + objectSetPrevFrame(object, &tempRect); + rectUnion(&dirtyRect, &tempRect, &dirtyRect); + + _obj_offset(object, -x, -y, &tempRect); + rectUnion(&dirtyRect, &tempRect, &dirtyRect); + + tileWindowRefreshRect(&dirtyRect, gElevation); + continue; + } + + p530014->field_20 = -1000; + _anim_set_continue(p530014->animationSequenceIndex, 1); + } else { + int x; + int y; + + CacheEntry* cacheHandle; + Art* art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + artGetRotationOffsets(art, object->rotation, &x, &y); + artUnlock(cacheHandle); + } else { + x = 0; + y = 0; + } + + Rect v29; + objectSetFid(object, p530014->fid, &v29); + rectUnion(&dirtyRect, &v29, &dirtyRect); + + art = artLock(object->fid, &cacheHandle); + if (art != NULL) { + int frame; + if ((p530014->flags & 0x01) != 0) { + frame = artGetFrameCount(art) - 1; + } else { + frame = 0; + } + + objectSetFrame(object, frame, &v29); + rectUnion(&dirtyRect, &v29, &dirtyRect); + + int frameX; + int frameY; + artGetFrameOffsets(art, object->frame, object->rotation, &frameX, &frameY); + + Rect v19; + _obj_offset(object, x + frameX, y + frameY, &v19); + rectUnion(&dirtyRect, &v19, &dirtyRect); + + artUnlock(cacheHandle); + } else { + objectSetFrame(object, 0, &v29); + rectUnion(&dirtyRect, &v29, &dirtyRect); + } + + tileWindowRefreshRect(&dirtyRect, gElevation); + } + } + + _anim_in_bk = 0; + + _object_anim_compact(); +} + +// 0x417F18 +void _object_anim_compact() +{ + for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { + AnimationSequence* animationSequence = &(gAnimationSequences[index]); + if ((animationSequence->flags & 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(STRUCT_530014)); + _sad[v2].field_20 = -1000; + _sad[v2].flags = 0; + } + } + } + _curr_sad = index; +} + +// 0x417FFC +int _check_move(int* a1) +{ + int x; + int y; + mouseGetPosition(&x, &y); + + int tile = tileFromScreenXY(x, y, gElevation); + if (tile == -1) { + return -1; + } + + if (isInCombat()) { + if (*a1 != -1) { + if (gPressedPhysicalKeys[DIK_LCONTROL] || gPressedPhysicalKeys[DIK_RCONTROL]) { + int hitMode; + bool aiming; + interfaceGetCurrentHitMode(&hitMode, &aiming); + + int v6 = _item_mp_cost(gDude, hitMode, aiming); + *a1 = *a1 - v6; + if (*a1 <= 0) { + return -1; + } + } + } + } else { + bool interruptWalk; + configGetBool(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, &interruptWalk); + if (interruptWalk) { + reg_anim_clear(gDude); + } + } + + return tile; +} + +// 0x4180B4 +int _dude_move(int a1) +{ + int v1; + int tile = _check_move(&v1); + if (tile == -1) { + return -1; + } + + if (_lastDestination == tile) { + return _dude_run(a1); + } + + _lastDestination = tile; + + reg_anim_begin(2); + + reg_anim_obj_move_to_tile(gDude, tile, gDude->elevation, a1, 0); + + return reg_anim_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 (!perkGetRank(gDude, PERK_SILENT_RUNNING)) { + dudeDisableState(0); + } + + reg_anim_begin(2); + + reg_anim_obj_run_to_tile(gDude, tile_num, gDude->elevation, a4, 0); + + return reg_anim_end(); +} + +// 0x418168 +void _dude_fidget() +{ + if (_game_user_wants_to_quit != 0) { + return; + } + + if (isInCombat()) { + return; + } + + if (_vcr_status() != 2) { + return; + } + + if ((gDude->flags & OBJECT_HIDDEN) != 0) { + return; + } + + unsigned int v0 = _get_bk_time(); + if (getTicksBetween(v0, _last_time_) <= _next_time) { + return; + } + + _last_time_ = v0; + + int v5 = 0; + Object* object = objectFindFirstAtElevation(gDude->elevation); + while (object != NULL) { + if (v5 >= 100) { + break; + } + + if ((object->flags & OBJECT_HIDDEN) == 0 && ((object->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER && ((object->fid & 0xFF0000) >> 16) == 0 && !critterIsDead(object)) { + Rect rect; + objectGetRect(object, &rect); + + Rect intersection; + if (rectIntersection(&rect, &_scr_size, &intersection) == 0 && (gMapHeader.field_34 != 97 || object->pid != 0x10000FA)) { + dword_56C7E0[v5++] = object; + } + } + + object = objectFindNextAtElevation(); + } + + int v13; + if (v5 != 0) { + int r = randomBetween(0, v5 - 1); + Object* object = dword_56C7E0[r]; + + reg_anim_begin(0x201); + + bool v8 = false; + if (object == gDude) { + v8 = true; + } else { + char v15[16]; + v15[0] = '\0'; + artCopyFileName(1, object->fid & 0xFFF, v15); + if (v15[0] == 'm' || v15[0] == 'M') { + if (objectGetDistanceBetween(object, gDude) < critterGetStat(gDude, STAT_PERCEPTION) * 2) { + v8 = true; + } + } + } + + if (v8) { + const char* sfx = sfxBuildCharName(object, ANIM_STAND, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(object, sfx, 0); + } + + reg_anim_animate(object, 0, 0); + reg_anim_end(); + + v13 = 20 / v5; + } else { + v13 = 7; + } + + if (v13 < 1) { + v13 = 1; + } else if (v13 > 7) { + v13 = 7; + } + + _next_time = randomBetween(0, 3000) + 1000 * v13; +} + +// 0x418378 +void _dude_stand(Object* obj, int rotation, int fid) +{ + Rect rect; + + objectSetRotation(obj, rotation, &rect); + + int x = 0; + int y = 0; + + int v4 = (obj->fid & 0xF000) >> 12; + if (v4 != 0) { + if (fid == -1) { + int takeOutFid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_TAKE_OUT, v4, obj->rotation + 1); + CacheEntry* takeOutFrmHandle; + Art* takeOutFrm = artLock(takeOutFid, &takeOutFrmHandle); + if (takeOutFrm != NULL) { + int frameCount = artGetFrameCount(takeOutFrm); + for (int frame = 0; frame < frameCount; frame++) { + int offsetX; + int offsetY; + artGetFrameOffsets(takeOutFrm, frame, obj->rotation, &offsetX, &offsetY); + x += offsetX; + y += offsetY; + } + artUnlock(takeOutFrmHandle); + + CacheEntry* standFrmHandle; + int standFid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_STAND, 0, obj->rotation + 1); + Art* standFrm = artLock(standFid, &standFrmHandle); + if (standFrm != NULL) { + int offsetX; + int offsetY; + if (artGetRotationOffsets(standFrm, obj->rotation, &offsetX, &offsetY) == 0) { + x += offsetX; + y += offsetY; + } + artUnlock(standFrmHandle); + } + } + } + } + + if (fid == -1) { + int anim; + if (((obj->fid & 0xFF0000) >> 16) == ANIM_FIRE_DANCE) { + anim = ANIM_FIRE_DANCE; + } else { + anim = ANIM_STAND; + } + fid = buildFid((obj->fid & 0xF000000) >> 24, (obj->fid & 0xFFF), anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + } + + Rect temp; + objectSetFid(obj, fid, &temp); + rectUnion(&rect, &temp, &rect); + + objectSetLocation(obj, obj->tile, obj->elevation, &temp); + rectUnion(&rect, &temp, &rect); + + objectSetFrame(obj, 0, &temp); + rectUnion(&rect, &temp, &rect); + + _obj_offset(obj, x, y, &temp); + rectUnion(&rect, &temp, &rect); + + tileWindowRefreshRect(&rect, obj->elevation); +} + +// 0x418574 +void _dude_standup(Object* a1) +{ + reg_anim_begin(2); + + int anim; + if ((a1->fid & 0xFF0000) >> 16 == ANIM_FALL_BACK) { + anim = ANIM_BACK_TO_STANDING; + } else { + anim = ANIM_PRONE_TO_STANDING; + } + + reg_anim_animate(a1, anim, 0); + reg_anim_end(); + a1->data.critter.combat.results &= ~DAM_KNOCKED_DOWN; +} + +// 0x4185EC +int actionRotate(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; +} + +// 0x418660 +int _anim_change_fid(Object* obj, int animationSequenceIndex, int fid) +{ + Rect rect; + Rect v7; + + if ((fid & 0xFF0000) >> 16) { + objectSetFid(obj, fid, &rect); + objectSetFrame(obj, 0, &v7); + rectUnion(&rect, &v7, &rect); + tileWindowRefreshRect(&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 = 1; + gAnimationSequenceCurrentIndex = -1; + + for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { + _anim_set_end(index); + } + + _anim_in_anim_stop = 0; + _curr_sad = 0; +} + +// 0x418708 +int _check_gravity(int tile, int elevation) +{ + for (; elevation > 0; elevation--) { + int x; + int y; + tileToScreenXY(tile, &x, &y, elevation); + + int v4 = _square_num(x + 2, y + 8, elevation); + int fid = buildFid(4, _square[elevation]->field_0[v4] & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + break; + } + } + return elevation; +} + +// 0x418794 +unsigned int _compute_tpf(Object* object, int fid) +{ + int fps; + + CacheEntry* handle; + Art* frm = artLock(fid, &handle); + if (frm != NULL) { + fps = artGetFramesPerSecond(frm); + artUnlock(handle); + } else { + fps = 10; + } + + if (isInCombat()) { + if (((fid & 0xFF0000) >> 16) == 1) { + int playerSpeedup = 0; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &playerSpeedup); + + if (object != gDude || playerSpeedup == 1) { + int combatSpeed = 0; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &combatSpeed); + fps += combatSpeed; + } + } + } + + return 1000 / fps; +} diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 0000000..1a16828 --- /dev/null +++ b/src/animation.h @@ -0,0 +1,287 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include "art.h" +#include "combat_defs.h" +#include "obj_types.h" +#include "sound.h" + +#include + +#define ANIMATION_SEQUENCE_LIST_CAPACITY (32) +#define ANIMATION_DESCRIPTION_LIST_CAPACITY (55) + +typedef enum AnimKind { + ANIM_KIND_OBJ_MOVE_TO_OBJ = 0, + ANIM_KIND_OBJ_MOVE_TO_TILE = 1, + ANIM_KIND_2 = 2, + ANIM_KIND_KNOCKDOWN = 3, + ANIM_KIND_ANIMATE = 4, + ANIM_KIND_ANIMATE_REVERSE = 5, + ANIM_KIND_6 = 6, + ANIM_KIND_SET_ROTATION_TO_TILE = 7, + ANIM_KIND_ROTATE_CLOCKWISE = 8, + ANIM_KIND_ROTATE_COUNTER_CLOCKWISE = 9, + ANIM_KIND_HIDE = 10, + ANIM_KIND_EXEC = 11, + ANIM_KIND_EXEC_2 = 12, + ANIM_KIND_14 = 14, + ANIM_KIND_15 = 15, + ANIM_KIND_16 = 16, + ANIM_KIND_17 = 17, + ANIM_KIND_18 = 18, + ANIM_KIND_19 = 19, + ANIM_KIND_20 = 20, + ANIM_KIND_23 = 23, + ANIM_KIND_24 = 24, + ANIM_KIND_ANIMATE_FOREVER = 25, + ANIM_KIND_26 = 26, + ANIM_KIND_27 = 27, + ANIM_KIND_28 = 28, +} AnimKind; + +// 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; + +typedef int AnimationProc(Object*, Object*); +typedef int AnimationSoundProc(Sound*); +typedef int AnimationProc2(Object*, Object*, void*); + +typedef struct AnimationDescription { + int type; + Object* owner; + union { + Object* destinationObj; + Sound* sound; + }; + union { + int tile; + int fid; // for type == 17 + int weaponAnimationCode; // for type == 18 + int lightDistance; // for type == 19 + }; + int elevation; + int anim; // anim + int delay; // delay + union { + AnimationProc* proc; + AnimationSoundProc* soundProc; + }; + AnimationProc2* field_20; // func + int field_24; + union { + int field_28; // actionPoints + Object* field_28_obj; // obj in type == 12 + void* field_28_void; + }; + CacheEntry* field_2C; +} 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; + 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; + +typedef struct STRUCT_530014_28 { + int tile; + int elevation; + int x; + int y; +} STRUCT_530014_28; + +typedef struct STRUCT_530014 { + int flags; // flags + Object* obj; + int fid; // fid + int field_C; + int field_10; + int field_14; // animation speed? + 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]; + STRUCT_530014_28 field_28[200]; + }; +} STRUCT_530014; + +static_assert(sizeof(STRUCT_530014) == 3240, "wrong size"); + +typedef Object* PathBuilderCallback(Object* object, int tile, int elevation); + +extern int _curr_sad; +extern int gAnimationSequenceCurrentIndex; +extern int _anim_in_init; +extern bool _anim_in_anim_stop; +extern bool _anim_in_bk; +extern int _lastDestination; +extern unsigned int _last_time_; +extern unsigned int _next_time; + +extern STRUCT_530014 _sad[24]; +extern PathNode gClosedPathNodeList[2000]; +extern AnimationSequence gAnimationSequences[32]; +extern unsigned char gPathfinderProcessedTiles[5000]; +extern PathNode gOpenPathNodeList[2000]; +extern int gAnimationDescriptionCurrentIndex; +extern Object* dword_56C7E0[100]; + +void animationInit(); +void animationReset(); +void animationExit(); +int reg_anim_begin(int a1); +int _anim_free_slot(int a1); +int _register_priority(int a1); +int reg_anim_clear(Object* a1); +int reg_anim_end(); +void _anim_cleanup(); +int _check_registry(Object* obj); +int animationIsBusy(Object* a1); +int reg_anim_obj_move_to_obj(Object* a1, Object* a2, int actionPoints, int delay); +int reg_anim_obj_run_to_obj(Object* owner, Object* destination, int actionPoints, int delay); +int reg_anim_obj_move_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay); +int reg_anim_obj_run_to_tile(Object* obj, int tile_num, int elev, int actionPoints, int delay); +int reg_anim_2(Object* obj, int tile_num, int elev, int a4, int a5); +int reg_anim_knockdown(Object* obj, int tile, int elev, int anim, int delay); +int reg_anim_animate(Object* obj, int anim, int delay); +int reg_anim_animate_reverse(Object* obj, int anim, int delay); +int reg_anim_6(Object* obj, int anim, int delay); +int reg_anim_set_rotation_to_tile(Object* owner, int tile); +int reg_anim_rotate_clockwise(Object* obj); +int reg_anim_rotate_counter_clockwise(Object* obj); +int reg_anim_hide(Object* obj); +int reg_anim_11_0(Object* a1, Object* a2, AnimationProc* proc, int delay); +int reg_anim_12(Object* a1, Object* a2, void* a3, AnimationProc2* proc, int delay); +int reg_anim_11_1(Object* a1, Object* a2, AnimationProc* proc, int delay); +int reg_anim_15(Object* obj, int a2, int a3); +int reg_anim_17(Object* obj, int fid, int a3); +int reg_anim_18(Object* obj, int a2, int a3); +int reg_anim_update_light(Object* obj, int fid, int a3); +int reg_anim_play_sfx(Object* obj, const char* a2, int a3); +int reg_anim_animate_forever(Object* obj, int a2, int a3); +int reg_anim_26(int a1, int a2); +int animationRunSequence(int a1); +int _anim_set_continue(int a1, int a2); +int _anim_set_end(int a1); +bool canUseDoor(Object* critter, Object* door); +int _make_path(Object* object, int from, int to, unsigned char* a4, int a5); +int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback); +int _idist(int a1, int a2, int a3, int a4); +int _tile_idistance(int tile1, int tile2); +int _make_straight_path(Object* a1, int from, int to, STRUCT_530014_28* pathNodes, Object** a5, int a6); +int _make_straight_path_func(Object* a1, int from, int to, STRUCT_530014_28* a4, Object** a5, int a6, Object* (*a7)(Object*, int, int)); +int animateMoveObjectToObject(Object* a1, Object* a2, int a3, int a4, int a5); +int animateMoveObjectToTile(Object* obj, int tile_num, int elev, int a4, int a5, int a6); +int _anim_move(Object* obj, int tile, int elev, int a3, int a4, int a5, int animationSequenceIndex); +int _anim_move_straight_to_tile(Object* obj, int a2, int a3, int fid, int a5, int a6); +int _anim_move_on_stairs(Object* obj, int a2, int a3, int fid, int a5); +int _check_for_falling(Object* obj, int a2, int a3); +void _object_move(int index); +void _object_straight_move(int a1); +int _anim_animate(Object* obj, int a2, int a3, int a4); +void _object_animate(); +void _object_anim_compact(); +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 actionRotate(Object* obj, int a2, int a3); +int _anim_change_fid(Object* obj, int a2, int fid); +void _anim_stop(); +int _check_gravity(int tile, int elevation); +unsigned int _compute_tpf(Object* object, int fid); + +#endif /* ANIMATION_H */ diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..3ef301d --- /dev/null +++ b/src/args.c @@ -0,0 +1,114 @@ +#include "args.h" + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 0x4E3B90 +void argsInit(CommandLineArguments* commandLineArguments) +{ + if (commandLineArguments != NULL) { + commandLineArguments->argc = 0; + commandLineArguments->argv = NULL; + } +} + +// 0x4E3BA4 +bool argsParse(CommandLineArguments* commandLineArguments, 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) { + argsFree(commandLineArguments); + 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++; + + commandLineArguments->argc = argc; + commandLineArguments->argv = malloc(sizeof(*commandLineArguments->argv) * argc); + if (commandLineArguments->argv == NULL) { + argsFree(commandLineArguments); + return false; + } + + for (int arg = 0; arg < argc; arg++) { + commandLineArguments->argv[arg] = NULL; + } + + // Copy program name into argv[0]. + char moduleFileName[MAX_PATH]; + int moduleFileNameLength = GetModuleFileNameA(NULL, moduleFileName, MAX_PATH); + if (moduleFileNameLength == 0) { + argsFree(commandLineArguments); + return false; + } + + if (moduleFileNameLength >= MAX_PATH) { + moduleFileNameLength = MAX_PATH - 1; + } + + moduleFileName[moduleFileNameLength] = '\0'; + + commandLineArguments->argv[0] = strdup(moduleFileName); + if (commandLineArguments->argv[0] == NULL) { + argsFree(commandLineArguments); + return false; + } + + // Copy arguments from command line into argv. + if (*commandLine != '\0') { + char* copy = strdup(commandLine); + if (copy == NULL) { + argsFree(commandLineArguments); + return false; + } + + int arg = 1; + + char* tok = strtok(copy, delim); + while (tok != NULL) { + commandLineArguments->argv[arg] = strdup(tok); + tok = strtok(NULL, delim); + arg++; + } + + free(copy); + } + + return true; +} + +// 0x4E3D3C +void argsFree(CommandLineArguments* commandLineArguments) +{ + if (commandLineArguments->argv != NULL) { + // NOTE: Compiled code is slightly different - it decrements argc. + for (int index = 0; index < commandLineArguments->argc; index++) { + if (commandLineArguments->argv[index] != NULL) { + free(commandLineArguments->argv[index]); + } + } + free(commandLineArguments->argv); + } + + commandLineArguments->argc = 0; + commandLineArguments->argv = NULL; +} diff --git a/src/args.h b/src/args.h new file mode 100644 index 0000000..00c3930 --- /dev/null +++ b/src/args.h @@ -0,0 +1,15 @@ +#ifndef ARGS_H +#define ARGS_H + +#include + +typedef struct CommandLineArguments { + int argc; + char** argv; +} CommandLineArguments; + +void argsInit(CommandLineArguments* commandLineArguments); +bool argsParse(CommandLineArguments* commandLineArguments, char* commandLine); +void argsFree(CommandLineArguments* commandLineArguments); + +#endif /* ARGS_H */ diff --git a/src/art.c b/src/art.c new file mode 100644 index 0000000..17e3624 --- /dev/null +++ b/src/art.c @@ -0,0 +1,1115 @@ +#include "art.h" + +#include "animation.h" +#include "debug.h" +#include "draw.h" +#include "game_config.h" +#include "memory.h" +#include "object.h" +#include "proto.h" + +#include +#include +#include + +// 0x510738 +ArtListDescription gArtListDescriptions[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 [gArtLanguage]. +// +// 0x510898 +bool gArtLanguageInitialized = false; + +// 0x51089C +const char* _head1 = "gggnnnbbbgnb"; + +// 0x5108A0 +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 +char gArtLanguage[32]; + +// 0x56C990 +Cache gArtCache; + +// 0x56C9E4 +char _art_name[MAX_PATH]; + +// head_info +// 0x56CAE8 +HeadDescription* gHeadDescriptions; + +// anon_alias +// 0x56CAEC +int* _anon_alias; + +// artCritterFidShouldRunData +// 0x56CAF0 +int* gArtCritterFidShoudRunData; + +// 0x418840 +int artInit() +{ + char path[MAX_PATH]; + int i; + File* stream; + char str[200]; + char *ptr, *curr; + + int cacheSize; + if (!configGetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, &cacheSize)) { + cacheSize = 8; + } + + if (!cacheInit(&gArtCache, artCacheGetFileSizeImpl, artCacheReadDataImpl, artCacheFreeImpl, cacheSize << 20)) { + debugPrint("cache_init failed in art_init\n"); + return -1; + } + + char* language; + if (configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language) && stricmp(language, ENGLISH) != 0) { + strcpy(gArtLanguage, language); + gArtLanguageInitialized = true; + } + + for (i = 0; i < 11; i++) { + gArtListDescriptions[i].flags = 0; + sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[i].name, gArtListDescriptions[i].name); + + if (artReadList(path, &(gArtListDescriptions[i].fileNames), &(gArtListDescriptions[i].fileNamesLength)) != 0) { + debugPrint("art_read_lst failed in art_init\n"); + cacheFree(&gArtCache); + return -1; + } + } + + _anon_alias = internal_malloc(sizeof(*_anon_alias) * gArtListDescriptions[1].fileNamesLength); + if (_anon_alias == NULL) { + gArtListDescriptions[1].fileNamesLength = 0; + debugPrint("Out of memory for anon_alias in art_init\n"); + cacheFree(&gArtCache); + return -1; + } + + gArtCritterFidShoudRunData = internal_malloc(sizeof(*gArtCritterFidShoudRunData) * gArtListDescriptions[1].fileNamesLength); + if (gArtCritterFidShoudRunData == NULL) { + gArtListDescriptions[1].fileNamesLength = 0; + debugPrint("Out of memory for artCritterFidShouldRunData in art_init\n"); + cacheFree(&gArtCache); + return -1; + } + + for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) { + gArtCritterFidShoudRunData[i] = 0; + } + + sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].name); + + stream = fileOpen(path, "rt"); + if (stream == NULL) { + debugPrint("Unable to open %s in art_init\n", path); + cacheFree(&gArtCache); + return -1; + } + + ptr = gArtListDescriptions[1].fileNames; + for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) { + if (stricmp(ptr, "hmjmps") == 0) { + _art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = i; + } else if (stricmp(ptr, "hfjmps") == 0) { + _art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = i; + } + + if (stricmp(ptr, "hmwarr") == 0) { + _art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = i; + _art_vault_guy_num = i; + } else if (stricmp(ptr, "hfprim") == 0) { + _art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = i; + } + + ptr += 13; + } + + for (i = 0; i < gArtListDescriptions[1].fileNamesLength; i++) { + if (!fileReadString(str, sizeof(str), stream)) { + break; + } + + ptr = str; + curr = ptr; + while (*curr != '\0' && *curr != ',') { + curr++; + } + + if (*curr != '\0') { + _anon_alias[i] = atoi(curr + 1); + + ptr = curr + 1; + curr = ptr; + while (*curr != '\0' && *curr != ',') { + curr++; + } + + gArtCritterFidShoudRunData[i] = *curr != '\0' ? atoi(ptr) : 0; + } else { + _anon_alias[i] = _art_vault_guy_num; + gArtCritterFidShoudRunData[i] = 1; + } + } + + fileClose(stream); + + ptr = gArtListDescriptions[4].fileNames; + for (i = 0; i < gArtListDescriptions[4].fileNamesLength; i++) { + if (stricmp(ptr, "grid001.frm") == 0) { + _art_mapper_blank_tile = i; + } + } + + gHeadDescriptions = internal_malloc(sizeof(HeadDescription) * gArtListDescriptions[8].fileNamesLength); + if (gHeadDescriptions == NULL) { + gArtListDescriptions[8].fileNamesLength = 0; + debugPrint("Out of memory for head_info in art_init\n"); + cacheFree(&gArtCache); + return -1; + } + + sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].name); + + stream = fileOpen(path, "rt"); + if (stream == NULL) { + debugPrint("Unable to open %s in art_init\n", path); + cacheFree(&gArtCache); + return -1; + } + + for (i = 0; i < gArtListDescriptions[8].fileNamesLength; i++) { + if (!fileReadString(str, sizeof(str), stream)) { + break; + } + + ptr = str; + curr = ptr; + while (*curr != '\0' && *curr != ',') { + curr++; + } + + if (*curr != '\0') { + ptr = curr + 1; + curr = ptr; + while (*curr != '\0' && *curr != ',') { + curr++; + } + + if (*curr != '\0') { + gHeadDescriptions[i].goodFidgetCount = atoi(ptr); + + ptr = curr + 1; + curr = ptr; + while (*curr != '\0' && *curr != ',') { + curr++; + } + + if (*curr != '\0') { + gHeadDescriptions[i].neutralFidgetCount = atoi(ptr); + + ptr = curr + 1; + curr = strpbrk(ptr, " ,;\t\n"); + if (curr != NULL) { + *curr = '\0'; + } + + gHeadDescriptions[i].badFidgetCount = atoi(ptr); + } + } + } + } + + fileClose(stream); + + return 0; +} + +// 0x418EB8 +void artReset() +{ +} + +// 0x418EBC +void artExit() +{ + cacheFree(&gArtCache); + + internal_free(_anon_alias); + internal_free(gArtCritterFidShoudRunData); + + for (int index = 0; index < OBJ_TYPE_COUNT; index++) { + internal_free(gArtListDescriptions[index].fileNames); + gArtListDescriptions[index].fileNames = NULL; + + internal_free(gArtListDescriptions[index].field_18); + gArtListDescriptions[index].field_18 = NULL; + } + + internal_free(gHeadDescriptions); +} + +// 0x418F1C +char* artGetObjectTypeName(int objectType) +{ + return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].name : NULL; +} + +// 0x418F34 +int artIsObjectTypeHidden(int objectType) +{ + return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0; +} + +// 0x418F7C +int artGetFidgetCount(int headFid) +{ + if ((headFid & 0xF000000) >> 24 != OBJ_TYPE_HEAD) { + return 0; + } + + int head = headFid & 0xFFF; + + if (head > gArtListDescriptions[OBJ_TYPE_HEAD].fileNamesLength) { + return 0; + } + + HeadDescription* headDescription = &(gHeadDescriptions[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 artRender(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 [artLock] it calls lower level + // [cacheLock], instead of [artGetWidth] is calls [artGetFrame], 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 = artLock(fid, &handle); + if (frm == NULL) { + return; + } + + unsigned char* frameData = artGetFrameData(frm, 0, 0); + int frameWidth = artGetWidth(frm, 0, 0); + int frameHeight = artGetHeight(frm, 0, 0); + + int remainingWidth = width - frameWidth; + int remainingHeight = height - frameHeight; + if (remainingWidth < 0 || remainingHeight < 0) { + if (height * frameWidth >= width * frameHeight) { + blitBufferToBufferStretchTrans(frameData, + frameWidth, + frameHeight, + frameWidth, + dest + pitch * ((height - width * frameHeight / frameWidth) / 2), + width, + width * frameHeight / frameWidth, + pitch); + } else { + blitBufferToBufferStretchTrans(frameData, + frameWidth, + frameHeight, + frameWidth, + dest + (width - height * frameWidth / frameHeight) / 2, + height * frameWidth / frameHeight, + height, + pitch); + } + } else { + blitBufferToBufferTrans(frameData, + frameWidth, + frameHeight, + frameWidth, + dest + pitch * (remainingHeight / 2) + remainingWidth / 2, + pitch); + } + + artUnlock(handle); +} + +// 0x419160 +Art* artLock(int fid, CacheEntry** handlePtr) +{ + if (handlePtr == NULL) { + return NULL; + } + + Art* art = NULL; + cacheLock(&gArtCache, fid, (void**)&art, handlePtr); + return art; +} + +// 0x419188 +unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry** handlePtr) +{ + Art* art; + ArtFrame* frm; + + art = NULL; + if (handlePtr) { + cacheLock(&gArtCache, fid, (void**)&art, handlePtr); + } + + if (art != NULL) { + frm = artGetFrame(art, frame, direction); + if (frm != NULL) { + + return frm->data; + } + } + + return NULL; +} + +// 0x4191CC +unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** handlePtr, int* widthPtr, int* heightPtr) +{ + *handlePtr = NULL; + + Art* art; + cacheLock(&gArtCache, fid, (void**)&art, handlePtr); + + if (art == NULL) { + return NULL; + } + + // NOTE: Uninline. + *widthPtr = artGetWidth(art, 0, 0); + if (*widthPtr == -1) { + return NULL; + } + + // NOTE: Uninline. + *heightPtr = artGetHeight(art, 0, 0); + if (*heightPtr == -1) { + return NULL; + } + + // NOTE: Uninline. + return artGetFrameData(art, 0, 0); +} + +// 0x419260 +int artUnlock(CacheEntry* handle) +{ + return cacheUnlock(&gArtCache, handle); +} + +// 0x41927C +int artCacheFlush() +{ + return cacheFlush(&gArtCache); +} + +// 0x4192B0 +int artCopyFileName(int type, int id, char* dest) +{ + ArtListDescription* ptr; + + if (type < 0 && type >= 11) { + return -1; + } + + ptr = &(gArtListDescriptions[type]); + + 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* artBuildFilePath(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 = (v2 & 0xFF0000) >> 16; + v5 = (v2 & 0xF000) >> 12; + type = (v2 & 0xF000000) >> 24; + + if (v3 >= gArtListDescriptions[type].fileNamesLength) { + return NULL; + } + + if (type < 0 || type >= 11) { + 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\\", gArtListDescriptions[1].name, gArtListDescriptions[1].fileNames + v8, v11, v12, v10 + 47); + } else { + sprintf(_art_name, "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[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\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], 102, v5); + } else { + sprintf(_art_name, "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], v9); + } + } else { + sprintf(_art_name, "%s%s%s\\%s", _cd_path_base, "art\\", gArtListDescriptions[type].name, gArtListDescriptions[type].fileNames + v8); + } + + return _art_name; +} + +// art_read_lst +// 0x419664 +int artReadList(const char* path, char** out_arr, int* out_count) +{ + File* stream; + char str[200]; + char* arr; + int count; + char* brk; + + stream = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + count = 0; + while (fileReadString(str, sizeof(str), stream)) { + count++; + } + + fileSeek(stream, 0, SEEK_SET); + + *out_count = count; + + arr = internal_malloc(13 * count); + *out_arr = arr; + if (arr == NULL) { + goto err; + } + + while (fileReadString(str, sizeof(str), stream)) { + brk = strpbrk(str, " ,;\r\t\n"); + if (brk != NULL) { + *brk = '\0'; + } + + strncpy(arr, str, 12); + arr[12] = '\0'; + + arr += 13; + } + + fileClose(stream); + + return 0; + +err: + + fileClose(stream); + + return -1; +} + +// 0x419760 +int artGetFramesPerSecond(Art* art) +{ + if (art == NULL) { + return 10; + } + + return art->framesPerSecond == 0 ? 10 : art->framesPerSecond; +} + +// 0x419778 +int artGetActionFrame(Art* art) +{ + return art == NULL ? -1 : art->actionFrame; +} + +// 0x41978C +int artGetFrameCount(Art* art) +{ + return art == NULL ? -1 : art->frameCount; +} + +// 0x4197A0 +int artGetWidth(Art* art, int frame, int direction) +{ + ArtFrame* frm; + + frm = artGetFrame(art, frame, direction); + if (frm == NULL) { + return -1; + } + + return frm->width; +} + +// 0x4197B8 +int artGetHeight(Art* art, int frame, int direction) +{ + ArtFrame* frm; + + frm = artGetFrame(art, frame, direction); + if (frm == NULL) { + return -1; + } + + return frm->height; +} + +// 0x4197D4 +int artGetSize(Art* art, int frame, int direction, int* widthPtr, int* heightPtr) +{ + ArtFrame* frm; + + frm = artGetFrame(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 artGetFrameOffsets(Art* art, int frame, int direction, int* xPtr, int* yPtr) +{ + ArtFrame* frm; + + frm = artGetFrame(art, frame, direction); + if (frm == NULL) { + return -1; + } + + *xPtr = frm->x; + *yPtr = frm->y; + + return 0; +} + +// 0x41984C +int artGetRotationOffsets(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* artGetFrameData(Art* art, int frame, int direction) +{ + ArtFrame* frm; + + frm = artGetFrame(art, frame, direction); + if (frm == NULL) { + return NULL; + } + + return frm->data; +} + +// 0x419880 +ArtFrame* artGetFrame(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*)(art->data + art->dataOffsets[rotation]); + for (int index = 0; index < frame; index++) { + frm = (ArtFrame*)(frm->data + frm->size); + } + return frm; +} + +// 0x4198C8 +bool artExists(int fid) +{ + int v3; + bool result; + + v3 = -1; + result = false; + + if ((fid & 0xF000000) >> 24 == 1) { + v3 = _db_current(1); + // _db_current(_critter_db_handle); + _db_current(0); + } + + char* filePath = artBuildFilePath(fid); + if (filePath == NULL) { + goto out; + } + + int fileSize; + if (dbGetFileSize(filePath, &fileSize) == -1) { + goto out; + } + + result = true; + +out: + + if (v3 != -1) { + _db_current(v3); + } + + return result; +} + +// 0x419930 +bool _art_fid_valid(int fid) +{ + // NOTE: Original Code involves calling some unknown function. Check in debugger in mapper. + char* filePath = artBuildFilePath(fid); + if (filePath == NULL) { + return false; + } + + int fileSize; + if (dbGetFileSize(filePath, &fileSize) == -1) { + return false; + } + + return true; +} + +// 0x419998 +int _art_alias_num(int index) +{ + return _anon_alias[index]; +} + +// 0x4199AC +int artCritterFidShouldRun(int fid) +{ + if ((fid & 0xF000000) >> 24 == 1) { + return gArtCritterFidShoudRunData[fid & 0xFFF]; + } + + return 0; +} + +// 0x4199D4 +int _art_alias_fid(int fid) +{ + int v2; + int v3; + int result; + + v2 = (fid & 0xF000000) >> 24; + v3 = (fid & 0xFF0000) >> 16; + + if (v2 != 1 || v3 != 27 && v3 != 29 && v3 != 30 && v3 != 55 && v3 != 57 && v3 != 58 && v3 != 33 && v3 != 64) + result = -1; + else + result = ((fid & 0x70000000) >> 28 << 28) & 0x70000000 | (v3 << 16) & 0xFF0000 | 0x1000000 | (((fid & 0xF000) >> 12) << 12) & 0xF000 | _anon_alias[fid & 0xFFF] & 0xFFF; + + return result; +} + +// 0x419A78 +int artCacheGetFileSizeImpl(int fid, int* sizePtr) +{ + int v4; + char* str; + char* ptr; + int result; + char path[MAX_PATH]; + bool loaded; + int fileSize; + + v4 = -1; + result = -1; + + if ((fid & 0xF000000) >> 24 == 1) { + v4 = _db_current(1); + // _db_current(_critter_db_handle); + _db_current(0); + } + + str = artBuildFilePath(fid); + if (str != NULL) { + loaded = false; + if (gArtLanguageInitialized) { + ptr = str; + while (*ptr != '\0' && *ptr != '\\') { + ptr++; + } + + if (*ptr == '\0') { + ptr = str; + } + + sprintf(path, "art\\%s\\%s", gArtLanguage, ptr); + if (dbGetFileSize(path, &fileSize) == 0) { + loaded = true; + } + } + + if (!loaded) { + if (dbGetFileSize(str, &fileSize) == 0) { + loaded = true; + } + } + + if (loaded) { + *sizePtr = fileSize; + result = 0; + } + } + + if (v4 != -1) { + _db_current(v4); + } + + return result; +} + +// 0x419B78 +int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data) +{ + int v4; + char* str; + char* ptr; + int result; + char path[MAX_PATH]; + bool loaded; + + v4 = -1; + result = -1; + + if ((fid & 0xF000000) >> 24 == 1) { + v4 = _db_current(1); + // _db_current(_critter_db_handle); + _db_current(0); + } + + str = artBuildFilePath(fid); + if (str != NULL) { + loaded = false; + if (gArtLanguageInitialized) { + ptr = str; + while (*ptr != '\0' && *ptr != '\\') { + ptr++; + } + + if (*ptr == '\0') { + ptr = str; + } + + sprintf(path, "art\\%s\\%s", gArtLanguage, ptr); + if (artRead(str, data) == 0) { + loaded = true; + } + } + + if (!loaded) { + if (artRead(str, data) == 0) { + loaded = true; + } + } + + if (loaded) { + // TODO: Why it adds 74? + *sizePtr = ((Art*)data)->field_3A + 74; + result = 0; + } + } + + if (v4 != -1) { + _db_current(v4); + } + + return result; +} + +// 0x419C80 +void artCacheFreeImpl(void* ptr) +{ + internal_free(ptr); +} + +// 0x419C88 +int buildFid(int objectType, int a2, int anim, int a3, int rotation) +{ + int v7, v8, v9, v10; + + v10 = rotation; + + if (objectType != OBJ_TYPE_CRITTER) { + goto zero; + } + + if (anim == 33 || anim < 20 || anim > 35) { + goto zero; + } + + v7 = ((a3 << 12) & 0xF000) | (anim << 16) & 0xFF0000 | 0x1000000; + v8 = (rotation << 28) & 0x70000000 | v7; + v9 = a2 & 0xFFF; + + if (artExists(v9 | v8) != 0) { + goto out; + } + + if (objectType == rotation) { + goto zero; + } + + v10 = objectType; + if (artExists(v9 | v7 | 0x10000000) != 0) { + goto out; + } + +zero: + + v10 = 0; + +out: + + return (v10 << 28) & 0x70000000 | (objectType << 24) | (anim << 16) & 0xFF0000 | (a3 << 12) & 0xF000 | a2 & 0xFFF; +} + +// 0x419D60 +int artReadFrameData(unsigned char* data, File* stream, int count) +{ + unsigned char* ptr = data; + for (int index = 0; index < count; index++) { + ArtFrame* frame = (ArtFrame*)ptr; + + if (fileReadInt16(stream, &(frame->width)) == -1) return -1; + if (fileReadInt16(stream, &(frame->height)) == -1) return -1; + if (fileReadInt32(stream, &(frame->size)) == -1) return -1; + if (fileReadInt16(stream, &(frame->x)) == -1) return -1; + if (fileReadInt16(stream, &(frame->y)) == -1) return -1; + if (fileRead(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1; + + ptr += sizeof(ArtFrame) + frame->size; + } + + return 0; +} + +// 0x419E1C +int artReadHeader(Art* art, File* stream) +{ + if (fileReadInt32(stream, &(art->field_0)) == -1) return -1; + if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1; + if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1; + if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1; + if (fileReadInt16List(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1; + if (fileReadInt16List(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1; + if (fileReadInt32List(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1; + if (fileReadInt32(stream, &(art->field_3A)) == -1) return -1; + + return 0; +} + +// 0x419FC0 +int artRead(const char* path, unsigned char* data) +{ + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + return -2; + } + + Art* art = (Art*)data; + if (artReadHeader(art, stream) != 0) { + fileClose(stream); + return -3; + } + + for (int index = 0; index < ROTATION_COUNT; index++) { + if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) { + if (artReadFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) { + fileClose(stream); + return -5; + } + } + } + + fileClose(stream); + return 0; +} diff --git a/src/art.h b/src/art.h new file mode 100644 index 0000000..de279e6 --- /dev/null +++ b/src/art.h @@ -0,0 +1,187 @@ +#ifndef ART_H +#define ART_H + +#include "cache.h" +#include "db.h" +#include "heap.h" +#include "obj_types.h" +#include "proto_types.h" + +#define WIN32_LEAN_AND_MEAN +#include + +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; + unsigned char data[]; +} Art; +#pragma pack() + +static_assert(sizeof(Art) == 62, "wrong size"); + +typedef struct ArtFrame { + short width; + short height; + int size; + short x; + short y; + unsigned char data[]; +} 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 ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT]; +extern bool gArtLanguageInitialized; +extern const char* _head1; +extern const char* _head2; +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 char gArtLanguage[32]; +extern Cache gArtCache; +extern char _art_name[MAX_PATH]; +extern HeadDescription* gHeadDescriptions; +extern int* _anon_alias; +extern int* gArtCritterFidShoudRunData; + +int artInit(); +void artReset(); +void artExit(); +char* artGetObjectTypeName(int objectType); +int artIsObjectTypeHidden(int objectType); +int artGetFidgetCount(int headFid); +void artRender(int fid, unsigned char* dest, int width, int height, int pitch); +Art* artLock(int fid, CacheEntry** cache_entry); +unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry** out_cache_entry); +unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr); +int artUnlock(CacheEntry* cache_entry); +int artCacheFlush(); +int artCopyFileName(int a1, int a2, char* a3); +int _art_get_code(int a1, int a2, char* a3, char* a4); +char* artBuildFilePath(int a1); +int artReadList(const char* path, char** out_arr, int* out_count); +int artGetFramesPerSecond(Art* art); +int artGetActionFrame(Art* art); +int artGetFrameCount(Art* art); +int artGetWidth(Art* art, int frame, int direction); +int artGetHeight(Art* art, int frame, int direction); +int artGetSize(Art* art, int frame, int direction, int* out_width, int* out_height); +int artGetFrameOffsets(Art* art, int frame, int direction, int* a4, int* a5); +int artGetRotationOffsets(Art* art, int rotation, int* out_offset_x, int* out_offset_y); +unsigned char* artGetFrameData(Art* art, int frame, int direction); +ArtFrame* artGetFrame(Art* art, int frame, int direction); +bool artExists(int fid); +bool _art_fid_valid(int fid); +int _art_alias_num(int a1); +int artCritterFidShouldRun(int a1); +int _art_alias_fid(int a1); +int artCacheGetFileSizeImpl(int a1, int* out_size); +int artCacheReadDataImpl(int a1, int* a2, unsigned char* data); +void artCacheFreeImpl(void* ptr); +int buildFid(int a1, int a2, int a3, int a4, int a5); +int artReadFrameData(unsigned char* data, File* stream, int count); +int artReadHeader(Art* art, File* stream); +int artRead(const char* path, unsigned char* data); + +#endif diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..1ee0bfe --- /dev/null +++ b/src/audio.c @@ -0,0 +1,250 @@ +#include "audio.h" + +#include "db.h" +#include "debug.h" +#include "memory_manager.h" +#include "sound.h" + +#include +#include +#include + +// 0x5108BC +AudioFileIsCompressedProc* _queryCompressedFunc = _defaultCompressionFunc; + +// 0x56CB00 +int gAudioListLength; + +// 0x56CB04 +AudioFile* gAudioList; + +// 0x41A2B0 +bool _defaultCompressionFunc(char* filePath) +{ + char* pch = strrchr(filePath, '.'); + if (pch != NULL) { + strcpy(pch + 1, "war"); + } + + return false; +} + +// 0x41A2D0 +int audioSoundDecoderReadHandler(int fileHandle, void* buffer, unsigned int size) +{ + return fileRead(buffer, 1, size, (File*)fileHandle); +} + +// AudioOpen +// 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 = fileOpen(path, mode); + if (stream == NULL) { + debugPrint("AudioOpen: Couldn't open %s for read\n", path); + return -1; + } + + int index; + for (index = 0; index < gAudioListLength; index++) { + if ((gAudioList[index].flags & AUDIO_FILE_IN_USE) == 0) { + break; + } + } + + if (index == gAudioListLength) { + if (gAudioList != NULL) { + gAudioList = internal_realloc_safe(gAudioList, sizeof(*gAudioList) * (gAudioListLength + 1), __FILE__, __LINE__); // "..\int\audio.c", 216 + } else { + gAudioList = internal_malloc_safe(sizeof(*gAudioList), __FILE__, __LINE__); // "..\int\audio.c", 218 + } + gAudioListLength++; + } + + AudioFile* audioFile = &(gAudioList[index]); + audioFile->flags = AUDIO_FILE_IN_USE; + audioFile->fileHandle = (int)stream; + + if (compression == 2) { + audioFile->flags |= AUDIO_FILE_COMPRESSED; + audioFile->soundDecoder = soundDecoderInit(audioSoundDecoderReadHandler, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); + audioFile->fileSize *= 2; + } else { + audioFile->fileSize = fileGetSize(stream); + } + + audioFile->position = 0; + + return index + 1; +} + +// 0x41A50C +int audioClose(int fileHandle) +{ + AudioFile* audioFile = &(gAudioList[fileHandle - 1]); + fileClose((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 = &(gAudioList[fileHandle - 1]); + + int bytesRead; + if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { + bytesRead = soundDecoderDecode(audioFile->soundDecoder, buffer, size); + } else { + bytesRead = fileRead(buffer, 1, size, (File*)audioFile->fileHandle); + } + + audioFile->position += bytesRead; + + return bytesRead; +} + +// 0x41A5E0 +int audioSeek(int fileHandle, long offset, int origin) +{ + int pos; + unsigned char* buf; + int v10; + + AudioFile* audioFile = &(gAudioList[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); + fileSeek((File*)audioFile->fileHandle, 0, SEEK_SET); + audioFile->soundDecoder = soundDecoderInit(audioSoundDecoderReadHandler, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); + audioFile->position = 0; + audioFile->fileSize *= 2; + + if (pos != 0) { + buf = internal_malloc_safe(4096, __FILE__, __LINE__); // "..\int\audio.c", 361 + while (pos > 4096) { + pos -= 4096; + audioRead(fileHandle, buf, 4096); + } + + if (pos != 0) { + audioRead(fileHandle, buf, pos); + } + + internal_free_safe(buf, __FILE__, __LINE__); // // "..\int\audio.c", 367 + } + } else { + buf = internal_malloc_safe(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 fileSeek((File*)audioFile->fileHandle, offset, origin); + } +} + +// 0x41A78C +long audioGetSize(int fileHandle) +{ + AudioFile* audioFile = &(gAudioList[fileHandle - 1]); + return audioFile->fileSize; +} + +// 0x41A7A8 +long audioTell(int fileHandle) +{ + AudioFile* audioFile = &(gAudioList[fileHandle - 1]); + return audioFile->position; +} + +// AudioWrite +// 0x41A7C4 +int audioWrite(int handle, const void* buf, unsigned int size) +{ + debugPrint("AudioWrite shouldn't be ever called\n"); + return 0; +} + +// 0x41A7D4 +int audioInit(AudioFileIsCompressedProc* isCompressedProc) +{ + _queryCompressedFunc = isCompressedProc; + gAudioList = NULL; + gAudioListLength = 0; + + return soundSetDefaultFileIO(audioOpen, audioClose, audioRead, audioWrite, audioSeek, audioTell, audioGetSize); +} + +// 0x41A818 +void audioExit() +{ + if (gAudioList != NULL) { + internal_free_safe(gAudioList, __FILE__, __LINE__); // "..\int\audio.c", 406 + } + + gAudioListLength = 0; + gAudioList = NULL; +} diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..388c88a --- /dev/null +++ b/src/audio.h @@ -0,0 +1,23 @@ +#ifndef AUDIO_H +#define AUDIO_H + +#include "audio_file.h" + +extern AudioFileIsCompressedProc* _queryCompressedFunc; + +extern int gAudioListLength; +extern AudioFile* gAudioList; + +bool _defaultCompressionFunc(char* filePath); +int audioSoundDecoderReadHandler(int fileHandle, void* buf, unsigned int size); +int audioOpen(const char* fname, int mode, ...); +int audioClose(int fileHandle); +int audioRead(int fileHandle, void* buffer, unsigned int size); +int audioSeek(int fileHandle, long offset, int origin); +long audioGetSize(int fileHandle); +long audioTell(int fileHandle); +int audioWrite(int handle, const void* buf, unsigned int size); +int audioInit(AudioFileIsCompressedProc* isCompressedProc); +void audioExit(); + +#endif /* AUDIO_H */ diff --git a/src/audio_file.c b/src/audio_file.c new file mode 100644 index 0000000..59342ee --- /dev/null +++ b/src/audio_file.c @@ -0,0 +1,252 @@ +#include "audio_file.h" + +#include "debug.h" +#include "memory_manager.h" +#include "sound.h" + +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +static_assert(sizeof(AudioFile) == 28, "wrong size"); + +// 0x5108C0 +AudioFileIsCompressedProc* _queryCompressedFunc_2 = _defaultCompressionFunc__; + +// 0x56CB10 +AudioFile* gAudioFileList; + +// 0x56CB14 +int gAudioFileListLength; + +// 0x41A850 +bool _defaultCompressionFunc__(char* filePath) +{ + char* pch = strrchr(filePath, '.'); + if (pch != NULL) { + strcpy(pch + 1, "war"); + } + + return false; +} + +// 0x41A870 +int audioFileSoundDecoderReadHandler(int fileHandle, void* buffer, unsigned int size) +{ + return fread(buffer, 1, size, (FILE*)fileHandle); +} + +// 0x41A88C +int audioFileOpen(const char* fname, int flags, ...) +{ + char path[MAX_PATH]; + strcpy(path, fname); + + int compression; + if (_queryCompressedFunc_2(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 < gAudioFileListLength; index++) { + if ((gAudioFileList[index].flags & AUDIO_FILE_IN_USE) == 0) { + break; + } + } + + if (index == gAudioFileListLength) { + if (gAudioFileList != NULL) { + gAudioFileList = internal_realloc_safe(gAudioFileList, sizeof(*gAudioFileList) * (gAudioFileListLength + 1), __FILE__, __LINE__); // "..\int\audiof.c", 207 + } else { + gAudioFileList = internal_malloc_safe(sizeof(*gAudioFileList), __FILE__, __LINE__); // "..\int\audiof.c", 209 + } + gAudioFileListLength++; + } + + AudioFile* audioFile = &(gAudioFileList[index]); + audioFile->flags = AUDIO_FILE_IN_USE; + audioFile->fileHandle = (int)stream; + + if (compression == 2) { + audioFile->flags |= AUDIO_FILE_COMPRESSED; + audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, 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 audioFileClose(int fileHandle) +{ + AudioFile* audioFile = &(gAudioFileList[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 audioFileRead(int fileHandle, void* buffer, unsigned int size) +{ + + AudioFile* ptr = &(gAudioFileList[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 +int audioFileSeek(int fileHandle, long offset, int origin) +{ + void* buf; + int remaining; + int a4; + + AudioFile* audioFile = &(gAudioFileList[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(audioFileSoundDecoderReadHandler, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); + audioFile->fileSize *= 2; + audioFile->position = 0; + + if (a4) { + buf = internal_malloc_safe(4096, __FILE__, __LINE__); // "..\int\audiof.c", 364 + while (a4 > 4096) { + audioFileRead(fileHandle, buf, 4096); + a4 -= 4096; + } + if (a4 != 0) { + audioFileRead(fileHandle, buf, a4); + } + internal_free_safe(buf, __FILE__, __LINE__); // "..\int\audiof.c", 370 + } + } else { + buf = internal_malloc_safe(0x400, __FILE__, __LINE__); // "..\int\audiof.c", 316 + remaining = audioFile->position - a4; + while (remaining > 1024) { + audioFileRead(fileHandle, buf, 1024); + remaining -= 1024; + } + if (remaining != 0) { + audioFileRead(fileHandle, buf, remaining); + } + // TODO: Obiously leaks memory. + } + return audioFile->position; + } + + return fseek((FILE*)audioFile->fileHandle, offset, origin); +} + +// 0x41AD20 +long audioFileGetSize(int fileHandle) +{ + AudioFile* audioFile = &(gAudioFileList[fileHandle - 1]); + return audioFile->fileSize; +} + +// 0x41AD3C +long audioFileTell(int fileHandle) +{ + AudioFile* audioFile = &(gAudioFileList[fileHandle - 1]); + return audioFile->position; +} + +// AudiofWrite +// 0x41AD58 +int audioFileWrite(int fileHandle, const void* buffer, unsigned int size) +{ + debugPrint("AudiofWrite shouldn't be ever called\n"); + return 0; +} + +// 0x41AD68 +int audioFileInit(AudioFileIsCompressedProc* isCompressedProc) +{ + _queryCompressedFunc_2 = isCompressedProc; + gAudioFileList = NULL; + gAudioFileListLength = 0; + + return soundSetDefaultFileIO(audioFileOpen, audioFileClose, audioFileRead, audioFileWrite, audioFileSeek, audioFileTell, audioFileGetSize); +} + +// 0x41ADAC +void audioFileExit() +{ + if (gAudioFileList != NULL) { + internal_free_safe(gAudioFileList, __FILE__, __LINE__); // "..\int\audiof.c", 405 + } + + gAudioFileListLength = 0; + gAudioFileList = NULL; +} diff --git a/src/audio_file.h b/src/audio_file.h new file mode 100644 index 0000000..96cf75e --- /dev/null +++ b/src/audio_file.h @@ -0,0 +1,42 @@ +#ifndef AUDIO_FILE_H +#define AUDIO_FILE_H + +#include "sound_decoder.h" + +#include + +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); + +extern AudioFileIsCompressedProc* _queryCompressedFunc_2; + +extern AudioFile* gAudioFileList; +extern int gAudioFileListLength; + +bool _defaultCompressionFunc__(char* filePath); +int audioFileSoundDecoderReadHandler(int fileHandle, void* buffer, unsigned int size); +int audioFileOpen(const char* fname, int flags, ...); +int audioFileClose(int a1); +int audioFileRead(int a1, void* buf, unsigned int size); +int audioFileSeek(int handle, long offset, int origin); +long audioFileGetSize(int a1); +long audioFileTell(int a1); +int audioFileWrite(int handle, const void* buf, unsigned int size); +int audioFileInit(AudioFileIsCompressedProc* isCompressedProc); +void audioFileExit(); + +#endif /* AUDIO_FILE_H */ diff --git a/src/automap.c b/src/automap.c new file mode 100644 index 0000000..24a9314 --- /dev/null +++ b/src/automap.c @@ -0,0 +1,1118 @@ +#include "automap.h" + +#include "color.h" +#include "config.h" +#include "core.h" +#include "dbox.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "graph_lib.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "text_font.h" +#include "window_manager.h" + +#include +#include + +// 0x41ADE0 +const int _defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT] = { + { -1, -1, -1 }, + { -1, -1, -1 }, + { -1, -1, -1 }, +}; + +// 0x41B560 +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, +}; + +// 0x41B7E0 +const int gAutomapFrmIds[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 +}; + +// 0x5108C4 +int gAutomapFlags = 0; + +// 0x56CB18 +AutomapHeader gAutomapHeader; + +// 0x56D2A0 +AutomapEntry gAutomapEntry; + +// automap_init +// 0x41B7F4 +int automapInit() +{ + gAutomapFlags = 0; + automapCreate(); + return 0; +} + +// 0x41B808 +int automapReset() +{ + gAutomapFlags = 0; + automapCreate(); + return 0; +} + +// 0x41B81C +void automapExit() +{ + char* masterPatchesPath; + if (configGetString(&gGameConfig, 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 automapLoad(File* stream) +{ + return fileReadInt32(stream, &gAutomapFlags); +} + +// 0x41B898 +int automapSave(File* stream) +{ + return fileWriteInt32(stream, gAutomapFlags); +} + +// 0x41B8B4 +int _automapDisplayMap(int map) +{ + return _displayMapList[map]; +} + +// 0x41B8BC +void automapShow(bool isInGame, bool isUsingScanner) +{ + int frmIds[AUTOMAP_FRM_COUNT]; + + static_assert(sizeof(frmIds) == sizeof(gAutomapFrmIds), "wrong size"); + memcpy(frmIds, gAutomapFrmIds, sizeof(gAutomapFrmIds)); + + unsigned char* frmData[AUTOMAP_FRM_COUNT]; + CacheEntry* frmHandle[AUTOMAP_FRM_COUNT]; + for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) { + int fid = buildFid(6, frmIds[index], 0, 0, 0); + frmData[index] = artLockFrameData(fid, 0, 0, &(frmHandle[index])); + if (frmData[index] == NULL) { + while (--index >= 0) { + artUnlock(frmHandle[index]); + } + return; + } + } + + int color; + if (isInGame) { + color = _colorTable[8456]; + _obj_process_seen(); + } else { + color = _colorTable[22025]; + } + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + int window = windowCreate(AUTOMAP_WINDOW_X, AUTOMAP_WINDOW_Y, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + + int scannerBtn = buttonCreate(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) { + buttonSetCallbacks(scannerBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int cancelBtn = buttonCreate(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) { + buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int switchBtn = buttonCreate(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) { + buttonSetCallbacks(switchBtn, _gsound_toggle_butt_press_, _gsound_toggle_butt_press_); + } + + if ((gAutomapFlags & AUTOMAP_WTH_HIGH_DETAILS) == 0) { + _win_set_button_rest_state(switchBtn, 1, 0); + } + + int elevation = gElevation; + + gAutomapFlags &= AUTOMAP_WTH_HIGH_DETAILS; + + if (isInGame) { + gAutomapFlags |= AUTOMAP_IN_GAME; + } + + if (isUsingScanner) { + gAutomapFlags |= AUTOMAP_WITH_SCANNER; + } + + automapRenderInMapWindow(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], gAutomapFlags); + + bool isoWasEnabled = isoDisable(); + gameMouseSetCursor(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 ((gAutomapFlags & AUTOMAP_WTH_HIGH_DETAILS) == 0) { + gAutomapFlags |= AUTOMAP_WTH_HIGH_DETAILS; + needsRefresh = true; + } + break; + case KEY_UPPERCASE_L: + case KEY_LOWERCASE_L: + if ((gAutomapFlags & AUTOMAP_WTH_HIGH_DETAILS) != 0) { + gAutomapFlags &= ~AUTOMAP_WTH_HIGH_DETAILS; + needsRefresh = true; + } + break; + case KEY_UPPERCASE_S: + case KEY_LOWERCASE_S: + if (elevation != gElevation) { + elevation = gElevation; + needsRefresh = true; + } + + if ((gAutomapFlags & AUTOMAP_WITH_SCANNER) == 0) { + Object* scanner = NULL; + + Object* item1 = critterGetItem1(gDude); + if (item1 != NULL && item1->pid == PROTO_ID_MOTION_SENSOR) { + scanner = item1; + } else { + Object* item2 = critterGetItem2(gDude); + if (item2 != NULL && item2->pid == PROTO_ID_MOTION_SENSOR) { + scanner = item2; + } + } + + if (scanner != NULL && miscItemGetCharges(scanner) > 0) { + needsRefresh = true; + gAutomapFlags |= AUTOMAP_WITH_SCANNER; + miscItemConsumeCharge(scanner); + } else { + soundPlayFile("iisxxxx1"); + + MessageListItem messageListItem; + // 17 - The motion sensor is not installed. + // 18 - The motion sensor has no charges remaining. + const char* title = getmsg(&gMiscMessageList, &messageListItem, scanner != NULL ? 18 : 17); + showDialogBox(title, NULL, 0, 165, 140, _colorTable[32328], NULL, _colorTable[32328], 0); + } + } + + break; + case KEY_CTRL_Q: + case KEY_ALT_X: + case KEY_F10: + showQuitConfirmationDialog(); + break; + case KEY_F12: + takeScreenshot(); + break; + } + + if (_game_user_wants_to_quit != 0) { + break; + } + + if (needsRefresh) { + automapRenderInMapWindow(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], gAutomapFlags); + needsRefresh = false; + } + } + + if (isoWasEnabled) { + isoEnable(); + } + + windowDestroy(window); + fontSetCurrent(oldFont); + + for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) { + artUnlock(frmHandle[index]); + } +} + +// Renders automap in Map window. +// +// 0x41BD1C +void automapRenderInMapWindow(int window, int elevation, unsigned char* backgroundData, int flags) +{ + int color; + if ((flags & AUTOMAP_IN_GAME) != 0) { + color = _colorTable[8456]; + } else { + color = _colorTable[22025]; + } + + windowFill(window, 0, 0, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color); + windowDrawBorder(window); + + unsigned char* windowBuffer = windowGetBuffer(window); + blitBufferToBuffer(backgroundData, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, AUTOMAP_WINDOW_WIDTH, windowBuffer, AUTOMAP_WINDOW_WIDTH); + + for (Object* object = objectFindFirstAtElevation(elevation); object != NULL; object = objectFindNextAtElevation()) { + if (object->tile == -1) { + continue; + } + + int objectType = (object->fid & 0xF000000) >> 24; + 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_FLAG_0x40000000) == 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 == gDude) { + 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 == gDude) { + 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 (mapGetCurrentMap() != -1) { + char* areaName = mapGetCityName(mapGetCurrentMap()); + windowDrawText(window, areaName, 240, 150, 380, textColor | 0x2000000); + + char* mapName = mapGetName(mapGetCurrentMap(), elevation); + windowDrawText(window, mapName, 240, 150, 396, textColor | 0x2000000); + } + + windowRefresh(window); +} + +// Renders automap in Pipboy window. +// +// 0x41C004 +int automapRenderInPipboyWindow(int window, int map, int elevation) +{ + unsigned char* windowBuffer = windowGetBuffer(window) + 640 * AUTOMAP_PIPBOY_VIEW_Y + AUTOMAP_PIPBOY_VIEW_X; + + unsigned char wallColor = _colorTable[992]; + unsigned char sceneryColor = _colorTable[480]; + + gAutomapEntry.data = internal_malloc(11024); + if (gAutomapEntry.data == NULL) { + debugPrint("\nAUTOMAP: Error allocating data buffer!\n"); + return -1; + } + + if (automapLoadEntry(map, elevation) == -1) { + internal_free(gAutomapEntry.data); + return -1; + } + + int v1 = 0; + unsigned char v2 = 0; + unsigned char* ptr = gAutomapEntry.data; + + // 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; + } + + internal_free(gAutomapEntry.data); + + return 0; +} + +// automap_pip_save +// 0x41C0F0 +int automapSaveCurrent() +{ + int map = mapGetCurrentMap(); + int elevation = gElevation; + + int entryOffset = gAutomapHeader.offsets[map][elevation]; + if (entryOffset < 0) { + return 0; + } + + debugPrint("\nAUTOMAP: Saving AutoMap DB index %d, level %d\n", map, elevation); + + bool dataBuffersAllocated = false; + gAutomapEntry.data = internal_malloc(11024); + if (gAutomapEntry.data != NULL) { + gAutomapEntry.compressedData = internal_malloc(11024); + if (gAutomapEntry.compressedData != NULL) { + dataBuffersAllocated = true; + } + } + + if (!dataBuffersAllocated) { + // FIXME: Leaking gAutomapEntry.data. + debugPrint("\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 = fileOpen(path, "r+b"); + if (stream1 == NULL) { + debugPrint("\nAUTOMAP: Error opening automap database file!\n"); + debugPrint("Error continued: automap_pip_save: path: %s", path); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + if (automapLoadHeader(stream1) == -1) { + debugPrint("\nAUTOMAP: Error reading automap database file header!\n"); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + fileClose(stream1); + return -1; + } + + _decode_map_data(elevation); + + int compressedDataSize = graphCompress(gAutomapEntry.data, gAutomapEntry.compressedData, 10000); + if (compressedDataSize == -1) { + gAutomapEntry.dataSize = 10000; + gAutomapEntry.isCompressed = 0; + } else { + gAutomapEntry.dataSize = compressedDataSize; + gAutomapEntry.isCompressed = 1; + } + + if (entryOffset != 0) { + sprintf(path, "%s\\%s", "MAPS", AUTOMAP_TMP); + + File* stream2 = fileOpen(path, "wb"); + if (stream2 == NULL) { + debugPrint("\nAUTOMAP: Error creating temp file!\n"); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + fileClose(stream1); + return -1; + } + + fileRewind(stream1); + + if (_copy_file_data(stream1, stream2, entryOffset) == -1) { + debugPrint("\nAUTOMAP: Error copying file data!\n"); + fileClose(stream1); + fileClose(stream2); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + if (automapSaveEntry(stream2) == -1) { + fileClose(stream1); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + int nextEntryDataSize; + if (fileReadInt32(stream1, &nextEntryDataSize) == -1) { + debugPrint("\nAUTOMAP: Error reading database #1!\n"); + fileClose(stream1); + fileClose(stream2); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + int automapDataSize = fileGetSize(stream1); + if (automapDataSize == -1) { + debugPrint("\nAUTOMAP: Error reading database #2!\n"); + fileClose(stream1); + fileClose(stream2); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + int nextEntryOffset = entryOffset + nextEntryDataSize + 5; + if (automapDataSize != nextEntryOffset) { + if (fileSeek(stream1, nextEntryOffset, SEEK_SET) == -1) { + debugPrint("\nAUTOMAP: Error writing temp data!\n"); + fileClose(stream1); + fileClose(stream2); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + if (_copy_file_data(stream1, stream2, automapDataSize - nextEntryOffset) == -1) { + debugPrint("\nAUTOMAP: Error copying file data!\n"); + fileClose(stream1); + fileClose(stream2); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + } + + int diff = gAutomapEntry.dataSize - nextEntryDataSize; + for (int map = 0; map < AUTOMAP_MAP_COUNT; map++) { + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + if (gAutomapHeader.offsets[map][elevation] > entryOffset) { + gAutomapHeader.offsets[map][elevation] += diff; + } + } + } + + gAutomapHeader.dataSize += diff; + + if (automapSaveHeader(stream2) == -1) { + fileClose(stream1); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + fileSeek(stream2, 0, SEEK_END); + fileClose(stream2); + fileClose(stream1); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + + char* masterPatchesPath; + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { + debugPrint("\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) { + debugPrint("\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) { + debugPrint("\nAUTOMAP: Error renaming database!\n"); + return -1; + } + } else { + bool proceed = true; + if (fileSeek(stream1, 0, SEEK_END) != -1) { + if (fileTell(stream1) != gAutomapHeader.dataSize) { + proceed = false; + } + } else { + proceed = false; + } + + if (!proceed) { + debugPrint("\nAUTOMAP: Error reading automap database file header!\n"); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + fileClose(stream1); + return -1; + } + + if (automapSaveEntry(stream1) == -1) { + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + gAutomapHeader.offsets[map][elevation] = gAutomapHeader.dataSize; + gAutomapHeader.dataSize += gAutomapEntry.dataSize + 5; + + if (automapSaveHeader(stream1) == -1) { + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + return -1; + } + + fileSeek(stream1, 0, SEEK_END); + fileClose(stream1); + internal_free(gAutomapEntry.data); + internal_free(gAutomapEntry.compressedData); + } + + return 1; +} + +// Saves automap entry into stream. +// +// 0x41C844 +int automapSaveEntry(File* stream) +{ + unsigned char* buffer; + if (gAutomapEntry.isCompressed == 1) { + buffer = gAutomapEntry.compressedData; + } else { + buffer = gAutomapEntry.data; + } + + if (_db_fwriteLong(stream, gAutomapEntry.dataSize) == -1) { + goto err; + } + + if (fileWriteUInt8(stream, gAutomapEntry.isCompressed) == -1) { + goto err; + } + + if (fileWriteUInt8List(stream, buffer, gAutomapEntry.dataSize) == -1) { + goto err; + } + + return 0; + +err: + + debugPrint("\nAUTOMAP: Error writing automap database entry data!\n"); + fileClose(stream); + + return -1; +} + +// 0x41C8CC +int automapLoadEntry(int map, int elevation) +{ + gAutomapEntry.compressedData = NULL; + + char path[MAX_PATH]; + sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); + + bool success = true; + + File* stream = fileOpen(path, "r+b"); + if (stream == NULL) { + debugPrint("\nAUTOMAP: Error opening automap database file!\n"); + debugPrint("Error continued: AM_ReadEntry: path: %s", path); + return -1; + } + + if (automapLoadHeader(stream) == -1) { + debugPrint("\nAUTOMAP: Error reading automap database header!\n"); + fileClose(stream); + return -1; + } + + if (gAutomapHeader.offsets[map][elevation] <= 0) { + success = false; + goto out; + } + + if (fileSeek(stream, gAutomapHeader.offsets[map][elevation], SEEK_SET) == -1) { + success = false; + goto out; + } + + if (_db_freadInt(stream, &(gAutomapEntry.dataSize)) == -1) { + success = false; + goto out; + } + + if (fileReadUInt8(stream, &(gAutomapEntry.isCompressed)) == -1) { + success = false; + goto out; + } + + if (gAutomapEntry.isCompressed == 1) { + gAutomapEntry.compressedData = internal_malloc(11024); + if (gAutomapEntry.compressedData == NULL) { + debugPrint("\nAUTOMAP: Error allocating decompression buffer!\n"); + fileClose(stream); + return -1; + } + + if (fileReadUInt8List(stream, gAutomapEntry.compressedData, gAutomapEntry.dataSize) == -1) { + success = 0; + goto out; + } + + if (graphDecompress(gAutomapEntry.compressedData, gAutomapEntry.data, 10000) == -1) { + debugPrint("\nAUTOMAP: Error decompressing DB entry!\n"); + fileClose(stream); + return -1; + } + } else { + if (fileReadUInt8List(stream, gAutomapEntry.data, gAutomapEntry.dataSize) == -1) { + success = false; + goto out; + } + } + +out: + + fileClose(stream); + + if (!success) { + debugPrint("\nAUTOMAP: Error reading automap database entry data!\n"); + + return -1; + } + + if (gAutomapEntry.compressedData != NULL) { + internal_free(gAutomapEntry.compressedData); + } + + return 0; +} + +// Saves automap.db header. +// +// 0x41CAD8 +int automapSaveHeader(File* stream) +{ + fileRewind(stream); + + if (fileWriteUInt8(stream, gAutomapHeader.version) == -1) { + goto err; + } + + if (_db_fwriteLong(stream, gAutomapHeader.dataSize) == -1) { + goto err; + } + + if (_db_fwriteLongCount(stream, (int*)gAutomapHeader.offsets, AUTOMAP_OFFSET_COUNT) == -1) { + goto err; + } + + return 0; + +err: + + debugPrint("\nAUTOMAP: Error writing automap database header!\n"); + + fileClose(stream); + + return -1; +} + +// Loads automap.db header. +// +// 0x41CB50 +int automapLoadHeader(File* stream) +{ + + if (fileReadUInt8(stream, &(gAutomapHeader.version)) == -1) { + return -1; + } + + if (_db_freadInt(stream, &(gAutomapHeader.dataSize)) == -1) { + return -1; + } + + if (_db_freadIntCount(stream, (int*)gAutomapHeader.offsets, AUTOMAP_OFFSET_COUNT) == -1) { + return -1; + } + + if (gAutomapHeader.version != 1) { + return -1; + } + + return 0; +} + +// 0x41CBA4 +void _decode_map_data(int elevation) +{ + memset(gAutomapEntry.data, 0, SQUARE_GRID_SIZE); + + _obj_process_seen(); + + Object* object = objectFindFirstAtElevation(elevation); + while (object != NULL) { + if (object->tile != -1 && (object->flags & OBJECT_FLAG_0x40000000) != 0) { + int contentType; + + int objectType = (object->fid & 0xF000000) >> 24; + 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); + gAutomapEntry.data[v2] &= ~(0x03 << v3); + gAutomapEntry.data[v2] |= (contentType << v3); + } + } + object = objectFindNextAtElevation(); + } +} + +// 0x41CC98 +int automapCreate() +{ + gAutomapHeader.version = 1; + gAutomapHeader.dataSize = 1925; + memcpy(gAutomapHeader.offsets, _defam, sizeof(_defam)); + + char path[MAX_PATH]; + sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); + + File* stream = fileOpen(path, "wb"); + if (stream == NULL) { + debugPrint("\nAUTOMAP: Error creating automap database file!\n"); + return -1; + } + + if (automapSaveHeader(stream) == -1) { + return -1; + } + + fileClose(stream); + + return 0; +} + +// Copy data from stream1 to stream2. +// +// 0x41CD6C +int _copy_file_data(File* stream1, File* stream2, int length) +{ + void* buffer = internal_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 (fileRead(buffer, chunkLength, 1, stream1) != 1) { + break; + } + + if (fileWrite(buffer, chunkLength, 1, stream2) != 1) { + break; + } + + length -= chunkLength; + } + + internal_free(buffer); + + if (length != 0) { + return -1; + } + + return 0; +} + +// 0x41CE74 +int automapGetHeader(AutomapHeader** automapHeaderPtr) +{ + char path[MAX_PATH]; + sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); + + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + debugPrint("\nAUTOMAP: Error opening database file for reading!\n"); + debugPrint("Error continued: ReadAMList: path: %s", path); + return -1; + } + + if (automapLoadHeader(stream) == -1) { + debugPrint("\nAUTOMAP: Error reading automap database header pt2!\n"); + fileClose(stream); + return -1; + } + + fileClose(stream); + + *automapHeaderPtr = &gAutomapHeader; + + return 0; +} diff --git a/src/automap.h b/src/automap.h new file mode 100644 index 0000000..9b65b99 --- /dev/null +++ b/src/automap.h @@ -0,0 +1,102 @@ +#ifndef AUTOMAP_H +#define AUTOMAP_H + +#include "db.h" +#include "map_defs.h" + +#include + +#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) + +#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 +// [gAutomapFlags] 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; + +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; + unsigned char* compressedData; + unsigned char* data; +} AutomapEntry; + +extern const int _defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT]; +extern const int _displayMapList[AUTOMAP_MAP_COUNT]; +extern const int gAutomapFrmIds[AUTOMAP_FRM_COUNT]; + +extern int gAutomapFlags; + +extern AutomapHeader gAutomapHeader; +extern AutomapEntry gAutomapEntry; + +int automapInit(); +int automapReset(); +void automapExit(); +int automapLoad(File* stream); +int automapSave(File* stream); +int _automapDisplayMap(int map); +void automapShow(bool isInGame, bool isUsingScanner); +void automapRenderInMapWindow(int window, int elevation, unsigned char* backgroundData, int flags); +int automapRenderInPipboyWindow(int win, int map, int elevation); +int automapSaveCurrent(); +int automapSaveEntry(File* stream); +int automapLoadEntry(int map, int elevation); +int automapSaveHeader(File* stream); +int automapLoadHeader(File* stream); +void _decode_map_data(int elevation); +int automapCreate(); +int _copy_file_data(File* stream1, File* stream2, int length); +int automapGetHeader(AutomapHeader** automapHeaderPtr); + +#endif /* AUTOMAP_H */ diff --git a/src/autorun.c b/src/autorun.c new file mode 100644 index 0000000..c5229f2 --- /dev/null +++ b/src/autorun.c @@ -0,0 +1,24 @@ +#include "autorun.h" + +// 0x530010 +HANDLE gInterplayGenericAutorunMutex; + +// 0x4139C0 +bool autorunMutexCreate() +{ + gInterplayGenericAutorunMutex = CreateMutexA(NULL, FALSE, "InterplayGenericAutorunMutex"); + if (GetLastError() == ERROR_ALREADY_EXISTS) { + CloseHandle(gInterplayGenericAutorunMutex); + return false; + } + + return true; +} + +// 0x413A00 +void autorunMutexClose() +{ + if (gInterplayGenericAutorunMutex != NULL) { + CloseHandle(gInterplayGenericAutorunMutex); + } +} diff --git a/src/autorun.h b/src/autorun.h new file mode 100644 index 0000000..44a718e --- /dev/null +++ b/src/autorun.h @@ -0,0 +1,14 @@ +#ifndef AUTORUN_H +#define AUTORUN_H + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +extern HANDLE gInterplayGenericAutorunMutex; + +bool autorunMutexCreate(); +void autorunMutexClose(); + +#endif /* AUTORUN_H */ diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..110ac4a --- /dev/null +++ b/src/cache.c @@ -0,0 +1,598 @@ +#include "cache.h" + +#include "debug.h" +#include "memory.h" +#include "sound.h" + +#include +#include +#include + +static_assert(sizeof(CacheEntry) == 32, "wrong size"); +static_assert(sizeof(Cache) == 84, "wrong size"); + +// 0x510938 +int _lock_sound_ticker = 0; + +// cache_init +// 0x41FCC0 +bool cacheInit(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize) +{ + if (!heapInit(&(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 = internal_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 cacheFree(Cache* cache) +{ + if (cache == NULL) { + return false; + } + + cacheClean(cache); + cacheFlush(cache); + heapFree(&(cache->heap)); + + cache->size = 0; + cache->maxSize = 0; + cache->entriesLength = 0; + cache->entriesCapacity = 0; + cache->hits = 0; + + if (cache->entries != NULL) { + internal_free(cache->entries); + cache->entries = NULL; + } + + cache->sizeProc = NULL; + cache->readProc = NULL; + cache->freeProc = NULL; + + return true; +} + +// 0x41FDE8 +bool cacheLock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr) +{ + if (cache == NULL || data == NULL || cacheEntryPtr == NULL) { + return false; + } + + *cacheEntryPtr = NULL; + + int index; + int rc = cacheFindIndexForKey(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 (!cacheFetchEntryForKey(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 (!heapLock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) { + return false; + } + } + + cacheEntry->referenceCount++; + + cache->hits++; + cacheEntry->mru = cache->hits; + + if (cache->hits == UINT_MAX) { + cacheResetStatistics(cache); + } + + *data = cacheEntry->data; + *cacheEntryPtr = cacheEntry; + + return true; +} + +// 0x4200B8 +bool cacheUnlock(Cache* cache, CacheEntry* cacheEntry) +{ + if (cache == NULL || cacheEntry == NULL) { + return false; + } + + if (cacheEntry->referenceCount == 0) { + return false; + } + + cacheEntry->referenceCount--; + + if (cacheEntry->referenceCount == 0) { + heapUnlock(&(cache->heap), cacheEntry->heapHandleIndex); + } + + return true; +} + +// cache_flush +// 0x42012C +bool cacheFlush(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. + cacheSweep(cache); + + // Shrink cache entries array if it's too big. + int optimalCapacity = cache->entriesLength + CACHE_ENTRIES_GROW_CAPACITY; + if (optimalCapacity < cache->entriesCapacity) { + cacheSetCapacity(cache, optimalCapacity); + } + + return true; +} + +// 0x42019C +bool cachePrintStats(Cache* cache, char* dest) +{ + if (cache == NULL || dest == NULL) { + return false; + } + + sprintf(dest, "Cache stats are disabled.%s", "\n"); + + return true; +} + +// Fetches entry for the specified key into the cache. +// +// 0x4203AC +bool cacheFetchEntryForKey(Cache* cache, int key, int* indexPtr) +{ + CacheEntry* cacheEntry = internal_malloc(sizeof(*cacheEntry)); + if (cacheEntry == NULL) { + return false; + } + + if (!cacheEntryInit(cacheEntry)) { + return false; + } + + do { + int size; + if (cache->sizeProc(key, &size) != 0) { + break; + } + + if (!cacheEnsureSize(cache, size)) { + break; + } + + bool allocated = false; + int cacheEntrySize = size; + for (int attempt = 0; attempt < 10; attempt++) { + if (heapBlockAllocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) { + allocated = true; + break; + } + + cacheEntrySize = (int)((double)cacheEntrySize + (double)size * 0.25); + if (cacheEntrySize > cache->maxSize) { + break; + } + + if (!cacheEnsureSize(cache, cacheEntrySize)) { + break; + } + } + + if (!allocated) { + cacheFlush(cache); + + allocated = true; + if (!heapBlockAllocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) { + if (!heapBlockAllocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 0)) { + allocated = false; + } + } + } + + if (!allocated) { + break; + } + + do { + if (!heapLock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) { + break; + } + + if (cache->readProc(key, &size, cacheEntry->data) != 0) { + break; + } + + heapUnlock(&(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 (cacheFindIndexForKey(cache, key, indexPtr) != 3) { + break; + } + } + + if (!cacheInsertEntryAtIndex(cache, cacheEntry, *indexPtr)) { + break; + } + + return true; + } while (0); + + heapUnlock(&(cache->heap), cacheEntry->heapHandleIndex); + } while (0); + + // NOTE: Uninline. + cacheEntryFree(cache, cacheEntry); + + return false; +} + +// 0x4205E8 +bool cacheInsertEntryAtIndex(Cache* cache, CacheEntry* cacheEntry, int index) +{ + // Ensure cache have enough space for new entry. + if (cache->entriesLength == cache->entriesCapacity - 1) { + if (!cacheSetCapacity(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 +int cacheFindIndexForKey(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; +} + +// 0x420708 +bool cacheEntryInit(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 +bool cacheEntryFree(Cache* cache, CacheEntry* cacheEntry) +{ + if (cacheEntry->data != NULL) { + heapBlockDeallocate(&(cache->heap), &(cacheEntry->heapHandleIndex)); + } + + internal_free(cacheEntry); + + return true; +} + +// 0x420764 +bool cacheClean(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) { + heapUnlock(heap, cacheEntry->heapHandleIndex); + cacheEntry->referenceCount = 0; + } + } + + return true; +} + +// 0x4207D4 +bool cacheResetStatistics(Cache* cache) +{ + if (cache == NULL) { + return false; + } + + CacheEntry** entries = internal_malloc(sizeof(*entries) * cache->entriesLength); + if (entries == NULL) { + return false; + } + + memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength); + + qsort(entries, cache->entriesLength, sizeof(*entries), cacheEntriesCompareByMostRecentHit); + + 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 +bool cacheEnsureSize(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 = internal_malloc(sizeof(*entries) * cache->entriesLength); + if (entries != NULL) { + memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength); + qsort(entries, cache->entriesLength, sizeof(*entries), cacheEntriesCompareByUsage); + + // 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; + } + } + } + + internal_free(entries); + } + + cacheSweep(cache); + + if (cache->maxSize - cache->size >= size) { + return true; + } + + return false; +} + +// 0x42099C +bool cacheSweep(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. + cacheEntryFree(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 +bool cacheSetCapacity(Cache* cache, int newCapacity) +{ + if (newCapacity < cache->entriesLength) { + return false; + } + + CacheEntry** entries = internal_realloc(cache->entries, sizeof(*cache->entries) * newCapacity); + if (entries == NULL) { + return false; + } + + cache->entries = entries; + cache->entriesCapacity = newCapacity; + + return true; +} + +// 0x420A74 +int cacheEntriesCompareByUsage(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 +int cacheEntriesCompareByMostRecentHit(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; + } +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 0000000..bb47a8a --- /dev/null +++ b/src/cache.h @@ -0,0 +1,89 @@ +#ifndef CACHE_H +#define CACHE_H + +#include "heap.h" + +#include + +#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 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; + + CacheEntryFlags 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 cacheInit(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize); +bool cacheFree(Cache* cache); +bool cacheLock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr); +bool cacheUnlock(Cache* cache, CacheEntry* cacheEntry); +bool cacheFlush(Cache* cache); +bool cachePrintStats(Cache* cache, char* dest); +bool cacheFetchEntryForKey(Cache* cache, int key, int* indexPtr); +bool cacheInsertEntryAtIndex(Cache* cache, CacheEntry* cacheEntry, int index); +int cacheFindIndexForKey(Cache* cache, int key, int* indexPtr); +bool cacheEntryInit(CacheEntry* cacheEntry); +bool cacheEntryFree(Cache* cache, CacheEntry* cacheEntry); +bool cacheClean(Cache* cache); +bool cacheResetStatistics(Cache* cache); +bool cacheEnsureSize(Cache* cache, int size); +bool cacheSweep(Cache* cache); +bool cacheSetCapacity(Cache* cache, int newCapacity); +int cacheEntriesCompareByUsage(const void* a1, const void* a2); +int cacheEntriesCompareByMostRecentHit(const void* a1, const void* a2); + +#endif /* CACHE_H */ diff --git a/src/character_editor.c b/src/character_editor.c new file mode 100644 index 0000000..55a495c --- /dev/null +++ b/src/character_editor.c @@ -0,0 +1,6680 @@ +#include "character_editor.h" + +#include "art.h" +#include "color.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "db.h" +#include "dbox.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_mouse.h" +#include "game_palette.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "palette.h" +#include "perk.h" +#include "proto.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "trait.h" +#include "window_manager.h" +#include "word_wrap.h" +#include "world_map.h" + +#include +#include +#include +#include + +#define RENDER_ALL_STATS 7 + +#define EDITOR_WIN_WIDTH 640 +#define EDITOR_WIN_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 ANIMATE 0x01 +#define RED_NUMBERS 0x02 +#define BIG_NUM_WIDTH 14 +#define BIG_NUM_HEIGHT 24 +#define BIG_NUM_ANIMATION_DELAY 123 + +// 0x431C40 +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 +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 +// NOTE: the type originally short +// +// 0x431D3A +const int word_431D3A[EDITOR_DERIVED_STAT_COUNT] = { + 18, + 19, + 20, + 21, + 22, + 23, + 83, + 24, + 25, + 26, +}; + +// y offsets for stats +/- buttons +// +// 0x431D50 +const int _StatYpos[7] = { + 37, + 70, + 103, + 136, + 169, + 202, + 235, +}; + +// stat ids for derived stats panel +// NOTE: the type is originally short +// +// 0x431D6 +const int word_431D6C[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, +}; + +// 0x431D93 +char byte_431D93[64]; + +// 0x431DD4 +const int dword_431DD4[7] = { + 1000000, + 100000, + 10000, + 1000, + 100, + 10, + 1, +}; + +// 0x5016E4 +char byte_5016E4[] = "------"; + +// 0x50170B +const double dbl_50170B = 14.4; + +// 0x501713 +const double dbl_501713 = 19.2; + +// 0x5018F0 +const double dbl_5018F0 = 19.2; + +// 0x5019BE +const double dbl_5019BE = 14.4; + +// 0x518528 +bool _bk_enable_0 = false; + +// 0x51852C +int _skill_cursor = 0; + +// 0x518534 +int _slider_y = 27; + +// 0x518538 +int characterEditorRemainingCharacterPoints = 0; + +// 0x51853C +KarmaEntry* gKarmaEntries = NULL; + +// 0x518540 +int gKarmaEntriesLength = 0; + +// 0x518544 +GenericReputationEntry* gGenericReputationEntries = NULL; + +// 0x518548 +int gGenericReputationEntriesLength = 0; + +// 0x51854C +const TownReputationEntry gTownReputationEntries[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 +const int gAddictionReputationVars[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 +const int gAddictionReputationFrmIds[ADDICTION_REPUTATION_COUNT] = { + 142, + 126, + 140, + 144, + 145, + 52, + 136, + 149, +}; + +// 0x518624 +int _folder_up_button = -1; + +// 0x518628 +int _folder_down_button = -1; + +// 0x56FB60 +char _folder_card_string[256]; + +// 0x56FC60 +int _skillsav[SKILL_COUNT]; + +// 0x56FCA8 +MessageList editorMessageList; + +// 0x56FCB0 +STRUCT_56FCB0 _name_sort_list[DIALOG_PICKER_NUM_OPTIONS]; + +// buttons for selecting traits +// +// 0x5700A8 +int _trait_bids[TRAIT_COUNT]; + +// 0x5700E8 +MessageListItem editorMessageListItem; + +// 0x5700F8 +char _old_str1[48]; + +// 0x570128 +char _old_str2[48]; + +// buttons for tagging skills +// +// 0x570158 +int _tag_bids[SKILL_COUNT]; + +// pc name +// +// 0x5701A0 +char _name_save[32]; + +// 0x5701C0 +Size _GInfo[EDITOR_GRAPHIC_COUNT]; + +// 0x570350 +CacheEntry* _grph_key[EDITOR_GRAPHIC_COUNT]; + +// 0x570418 +unsigned char* _grphcpy[EDITOR_GRAPHIC_COUNT]; + +// 0x5704E0 +unsigned char* _grphbmp[EDITOR_GRAPHIC_COUNT]; + +// 0x5705A8 +int _folder_max_lines; + +// 0x5705AC +int _folder_line; + +// 0x5705B0 +int _folder_card_fid; + +// 0x5705B4 +int _folder_top_line; + +// 0x5705B8 +char* _folder_card_title; + +// 0x5705BC +char* _folder_card_title2; + +// 0x5705C0 +int _folder_yoffset; + +// 0x5705C4 +int _folder_karma_top_line; + +// 0x5705C8 +int _folder_highlight_line; + +// 0x5705CC +char* _folder_card_desc; + +// 0x5705D0 +int _folder_ypos; + +// 0x5705D4 +int _folder_kills_top_line; + +// 0x5705D8 +int _folder_perk_top_line; + +// 0x5705DC +unsigned char* gEditorPerkBackgroundBuffer; + +// 0x5705E0 +int gEditorPerkWindow; + +// 0x5705E4 +int _SliderPlusID; + +// 0x5705E8 +int _SliderNegID; + +// - stats buttons +// +// 0x5705EC +int _stat_bids_minus[7]; + +// 0x570608 +unsigned char* characterEditorWindowBuf; + +// 0x57060C +int characterEditorWindowHandle; + +// + stats buttons +// +// 0x570610 +int _stat_bids_plus[7]; + +// 0x57062C +unsigned char* gEditorPerkWindowBuffer; + +// 0x570630 +CritterProtoData _dude_data; + +// 0x5707A4 +unsigned char* characterEditorWindowBackgroundBuf; + +// 0x5707A8 +int _cline; + +// 0x5707AC +int _oldsline; + +// unspent skill points +// +// 0x5707B0 +int _upsent_points_back; + +// 0x5707B4 +int _last_level; + +// 0x5707B8 +int characterEditorWindowOldFont; + +// 0x5707BC +int _kills_count; + +// character editor background +// +// 0x5707C0 +CacheEntry* _bck_key; + +// current hit points +// +// 0x5707C4 +int _hp_back; + +// 0x5707C8 +int _mouse_ypos; // mouse y + +// 0x5707CC +int _mouse_xpos; // mouse x + +// 0x5707D0 +int characterEditorSelectedItem; + +// 0x5707D4 +int characterEditorWindowSelectedFolder; + +// 0x5707D8 +int _frstc_draw1; + +// 0x5707DC +int _crow; + +// 0x5707E0 +int _frstc_draw2; + +// 0x5707E4 +int _perk_back[PERK_COUNT]; + +// 0x5709C0 +unsigned int _repFtime; + +// 0x5709C4 +unsigned int _frame_time; + +// 0x5709C8 +int _old_tags; + +// 0x5709CC +int _last_level_back; + +// 0x5709E8 +int _card_old_fid2; + +// 0x5709EC +int _card_old_fid1; + +// 0x5709D0 +bool gCharacterEditorIsCreationMode; + +// 0x5709D4 +int _tag_skill_back[NUM_TAGGED_SKILLS]; + +// 0x5709F0 +int _trait_back[3]; + +// current index for selecting new trait +// +// 0x5709FC +int _trait_count; + +// 0x570A00 +int _optrt_count; + +// 0x570A04 +int _temp_trait[3]; + +// 0x570A10 +int _tagskill_count; + +// 0x570A14 +int _temp_tag_skill[NUM_TAGGED_SKILLS]; + +// 0x570A28 +char _free_perk_back; + +// 0x570A29 +unsigned char _free_perk; + +// 0x570A2A +unsigned char _first_skill_list; + +// 0x431DF8 +int _editor_design(bool isCreationMode) +{ + char* messageListItemText; + char line1[128]; + char line2[128]; + const char* lines[] = { line2 }; + + gCharacterEditorIsCreationMode = isCreationMode; + + _SavePlayer(); + + if (characterEditorWindowInit() == -1) { + debugPrint("\n ** Error loading character editor data! **\n"); + return -1; + } + + if (!gCharacterEditorIsCreationMode) { + if (_UpdateLevel()) { + critterUpdateDerivedStats(gDude); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + editorRenderDetails(); + } + } + + 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; + soundPlayFile("ib1p1xx1"); + } + + if (done) { + if (gCharacterEditorIsCreationMode) { + if (characterEditorRemainingCharacterPoints != 0) { + soundPlayFile("iisxxxx1"); + + // You must use all character points + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 118); + strcpy(line1, messageListItemText); + + // before starting the game! + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 119); + strcpy(line2, messageListItemText); + + showDialogBox(line1, lines, 1, 192, 126, _colorTable[32328], 0, _colorTable[32328], 0); + windowRefresh(characterEditorWindowHandle); + + rc = -1; + continue; + } + + if (_tagskill_count > 0) { + soundPlayFile("iisxxxx1"); + + // You must select all tag skills + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 142); + strcpy(line1, messageListItemText); + + // before starting the game! + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 143); + strcpy(line2, messageListItemText); + + showDialogBox(line1, lines, 1, 192, 126, _colorTable[32328], 0, _colorTable[32328], 0); + windowRefresh(characterEditorWindowHandle); + + rc = -1; + continue; + } + + if (_is_supper_bonus()) { + soundPlayFile("iisxxxx1"); + + // All stats must be between 1 and 10 + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 157); + strcpy(line1, messageListItemText); + + // before starting the game! + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 158); + strcpy(line2, messageListItemText); + + showDialogBox(line1, lines, 1, 192, 126, _colorTable[32328], 0, _colorTable[32328], 0); + windowRefresh(characterEditorWindowHandle); + + rc = -1; + continue; + } + + if (stricmp(critterGetName(gDude), "None") == 0) { + soundPlayFile("iisxxxx1"); + + // Warning: You haven't changed your player + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 160); + strcpy(line1, messageListItemText); + + // name. Use this character any way? + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 161); + strcpy(line2, messageListItemText); + + if (showDialogBox(line1, lines, 1, 192, 126, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_YES_NO) == 0) { + windowRefresh(characterEditorWindowHandle); + + rc = -1; + continue; + } + } + } + + windowRefresh(characterEditorWindowHandle); + rc = 0; + } else if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { + showQuitConfirmationDialog(); + windowRefresh(characterEditorWindowHandle); + } else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || _game_user_wants_to_quit != 0) { + windowRefresh(characterEditorWindowHandle); + rc = 1; + } else if (gCharacterEditorIsCreationMode && (keyCode == 517 || keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N)) { + characterEditorEditName(); + windowRefresh(characterEditorWindowHandle); + } else if (gCharacterEditorIsCreationMode && (keyCode == 519 || keyCode == KEY_UPPERCASE_A || keyCode == KEY_LOWERCASE_A)) { + characterEditorRunEditAgeDialog(); + windowRefresh(characterEditorWindowHandle); + } else if (gCharacterEditorIsCreationMode && (keyCode == 520 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S)) { + characterEditorEditGender(); + windowRefresh(characterEditorWindowHandle); + } else if (gCharacterEditorIsCreationMode && (keyCode >= 503 && keyCode < 517)) { + characterEditorHandleIncDecPrimaryStat(keyCode); + windowRefresh(characterEditorWindowHandle); + } else if ((gCharacterEditorIsCreationMode && (keyCode == 501 || keyCode == KEY_UPPERCASE_O || keyCode == KEY_LOWERCASE_O)) + || (!gCharacterEditorIsCreationMode && (keyCode == 501 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P))) { + // _OptionWindow(); + windowRefresh(characterEditorWindowHandle); + } else if (keyCode >= 525 && keyCode < 535) { + _InfoButton(keyCode); + windowRefresh(characterEditorWindowHandle); + } else { + switch (keyCode) { + case KEY_TAB: + if (characterEditorSelectedItem >= 0 && characterEditorSelectedItem < 7) { + characterEditorSelectedItem = gCharacterEditorIsCreationMode ? 82 : 7; + } else if (characterEditorSelectedItem >= 7 && characterEditorSelectedItem < 9) { + if (gCharacterEditorIsCreationMode) { + characterEditorSelectedItem = 82; + } else { + characterEditorSelectedItem = 10; + characterEditorWindowSelectedFolder = 0; + } + } else if (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43) { + switch (characterEditorWindowSelectedFolder) { + case EDITOR_FOLDER_PERKS: + characterEditorSelectedItem = 10; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_KARMA; + break; + case EDITOR_FOLDER_KARMA: + characterEditorSelectedItem = 10; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_KILLS; + break; + case EDITOR_FOLDER_KILLS: + characterEditorSelectedItem = 43; + break; + } + } else if (characterEditorSelectedItem >= 43 && characterEditorSelectedItem < 51) { + characterEditorSelectedItem = 51; + } else if (characterEditorSelectedItem >= 51 && characterEditorSelectedItem < 61) { + characterEditorSelectedItem = 61; + } else if (characterEditorSelectedItem >= 61 && characterEditorSelectedItem < 82) { + characterEditorSelectedItem = 0; + } else if (characterEditorSelectedItem >= 82 && characterEditorSelectedItem < 98) { + characterEditorSelectedItem = 43; + } + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPcStats(); + editorRenderFolders(); + editorRenderSecondaryStats(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); + break; + case KEY_ARROW_LEFT: + case KEY_MINUS: + case KEY_UPPERCASE_J: + if (characterEditorSelectedItem >= 0 && characterEditorSelectedItem < 7) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_stat_bids_minus[characterEditorSelectedItem]); + windowRefresh(characterEditorWindowHandle); + } + } else if (characterEditorSelectedItem >= 61 && characterEditorSelectedItem < 79) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_tag_bids[gCharacterEditorIsCreationMode - 61]); + windowRefresh(characterEditorWindowHandle); + } else { + editorAdjustSkill(keyCode); + windowRefresh(characterEditorWindowHandle); + } + } else if (characterEditorSelectedItem >= 82 && characterEditorSelectedItem < 98) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_trait_bids[gCharacterEditorIsCreationMode - 82]); + windowRefresh(characterEditorWindowHandle); + } + } + break; + case KEY_ARROW_RIGHT: + case KEY_PLUS: + case KEY_UPPERCASE_N: + if (characterEditorSelectedItem >= 0 && characterEditorSelectedItem < 7) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_stat_bids_plus[characterEditorSelectedItem]); + windowRefresh(characterEditorWindowHandle); + } + } else if (characterEditorSelectedItem >= 61 && characterEditorSelectedItem < 79) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_tag_bids[gCharacterEditorIsCreationMode - 61]); + windowRefresh(characterEditorWindowHandle); + } else { + editorAdjustSkill(keyCode); + windowRefresh(characterEditorWindowHandle); + } + } else if (characterEditorSelectedItem >= 82 && characterEditorSelectedItem < 98) { + if (gCharacterEditorIsCreationMode) { + _win_button_press_and_release(_trait_bids[gCharacterEditorIsCreationMode - 82]); + windowRefresh(characterEditorWindowHandle); + } + } + break; + case KEY_ARROW_UP: + if (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43) { + if (characterEditorSelectedItem == 10) { + if (_folder_top_line > 0) { + _folder_scroll(-1); + characterEditorSelectedItem--; + editorRenderFolders(); + editorRenderDetails(); + } + } else { + characterEditorSelectedItem--; + editorRenderFolders(); + editorRenderDetails(); + } + + windowRefresh(characterEditorWindowHandle); + } else { + switch (characterEditorSelectedItem) { + case 0: + characterEditorSelectedItem = 6; + break; + case 7: + characterEditorSelectedItem = 9; + break; + case 43: + characterEditorSelectedItem = 50; + break; + case 51: + characterEditorSelectedItem = 60; + break; + case 61: + characterEditorSelectedItem = 78; + break; + case 82: + characterEditorSelectedItem = 97; + break; + default: + characterEditorSelectedItem -= 1; + break; + } + + if (characterEditorSelectedItem >= 61 && characterEditorSelectedItem < 79) { + _skill_cursor = characterEditorSelectedItem - 61; + } + + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPcStats(); + editorRenderFolders(); + editorRenderSecondaryStats(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); + } + break; + case KEY_ARROW_DOWN: + if (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43) { + if (characterEditorSelectedItem - 10 < _folder_line - _folder_top_line) { + if (characterEditorSelectedItem - 10 == _folder_max_lines - 1) { + _folder_scroll(1); + } + + characterEditorSelectedItem++; + + editorRenderFolders(); + editorRenderDetails(); + } + + windowRefresh(characterEditorWindowHandle); + } else { + switch (characterEditorSelectedItem) { + case 6: + characterEditorSelectedItem = 0; + break; + case 9: + characterEditorSelectedItem = 7; + break; + case 50: + characterEditorSelectedItem = 43; + break; + case 60: + characterEditorSelectedItem = 51; + break; + case 78: + characterEditorSelectedItem = 61; + break; + case 97: + characterEditorSelectedItem = 82; + break; + default: + characterEditorSelectedItem += 1; + break; + } + + if (characterEditorSelectedItem >= 61 && characterEditorSelectedItem < 79) { + _skill_cursor = characterEditorSelectedItem - 61; + } + + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPcStats(); + editorRenderFolders(); + editorRenderSecondaryStats(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); + } + break; + case 521: + case 523: + editorAdjustSkill(keyCode); + windowRefresh(characterEditorWindowHandle); + break; + case 535: + _FldrButton(); + windowRefresh(characterEditorWindowHandle); + break; + case 17000: + _folder_scroll(-1); + windowRefresh(characterEditorWindowHandle); + break; + case 17001: + _folder_scroll(1); + windowRefresh(characterEditorWindowHandle); + break; + default: + if (gCharacterEditorIsCreationMode && (keyCode >= 536 && keyCode < 554)) { + characterEditorToggleTaggedSkill(keyCode - 536); + windowRefresh(characterEditorWindowHandle); + } else if (gCharacterEditorIsCreationMode && (keyCode >= 555 && keyCode < 571)) { + characterEditorToggleOptionalTrait(keyCode - 555); + windowRefresh(characterEditorWindowHandle); + } else { + if (keyCode == 390) { + takeScreenshot(); + } + + windowRefresh(characterEditorWindowHandle); + } + } + } + } + + if (rc == 0) { + if (isCreationMode) { + _proto_dude_update_gender(); + paletteFadeTo(gPaletteBlack); + } + } + + characterEditorWindowFree(); + + if (rc == 1) { + _RestorePlayer(); + } + + if (dudeHasState(0x03)) { + dudeDisableState(0x03); + } + + interfaceRenderHitPoints(false); + + return rc; +} + +// 0x4329EC +int characterEditorWindowInit() +{ + int i; + int v1; + int v3; + 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]; + + characterEditorWindowOldFont = fontGetCurrent(); + _old_tags = 0; + _bk_enable_0 = 0; + _card_old_fid2 = -1; + _card_old_fid1 = -1; + _frstc_draw2 = 0; + _frstc_draw1 = 0; + _first_skill_list = 1; + _old_str2[0] = '\0'; + _old_str1[0] = '\0'; + + fontSetCurrent(101); + + _slider_y = _skill_cursor * (fontGetLineHeight() + 1) + 27; + + // skills + skillsGetTagged(_temp_tag_skill, NUM_TAGGED_SKILLS); + + v1 = 0; + for (i = 3; i >= 0; i--) { + if (_temp_tag_skill[i] != -1) { + break; + } + + v1++; + } + + if (gCharacterEditorIsCreationMode) { + v1--; + } + + _tagskill_count = v1; + + // traits + traitsGetSelected(&(_temp_trait[0]), &(_temp_trait[1])); + + v3 = 0; + for (i = 1; i >= 0; i--) { + if (_temp_trait[i] != -1) { + break; + } + + v3++; + } + + _trait_count = v3; + + if (!gCharacterEditorIsCreationMode) { + _bk_enable_0 = isoDisable(); + } + + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (!messageListInit(&editorMessageList)) { + return -1; + } + + sprintf(path, "%s%s", asc_5186C8, "editor.msg"); + + if (!messageListLoad(&editorMessageList, path)) { + return -1; + } + + fid = buildFid(6, (gCharacterEditorIsCreationMode ? 169 : 177), 0, 0, 0); + characterEditorWindowBackgroundBuf = artLockFrameDataReturningSize(fid, &_bck_key, &(_GInfo[0].width), &(_GInfo[0].height)); + if (characterEditorWindowBackgroundBuf == NULL) { + messageListFree(&editorMessageList); + return -1; + } + + if (karmaInit() == -1) { + return -1; + } + + if (genericReputationInit() == -1) { + return -1; + } + + soundContinueAll(); + + for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { + fid = buildFid(6, _grph_id[i], 0, 0, 0); + _grphbmp[i] = artLockFrameDataReturningSize(fid, &(_grph_key[i]), &(_GInfo[i].width), &(_GInfo[i].height)); + if (_grphbmp[i] == NULL) { + break; + } + } + + if (i != EDITOR_GRAPHIC_COUNT) { + while (--i >= 0) { + artUnlock(_grph_key[i]); + } + return -1; + + artUnlock(_bck_key); + + messageListFree(&editorMessageList); + + if (_bk_enable_0) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + + soundContinueAll(); + + for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { + if (_copyflag[i]) { + _grphcpy[i] = internal_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]) { + internal_free(_grphcpy[i]); + } + } + + for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { + artUnlock(_grph_key[i]); + } + + artUnlock(_bck_key); + + messageListFree(&editorMessageList); + if (_bk_enable_0) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + return -1; + } + + characterEditorWindowHandle = windowCreate(0, 0, 640, 480, 256, 18); + if (characterEditorWindowHandle == -1) { + for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { + if (_copyflag[i]) { + internal_free(_grphcpy[i]); + } + artUnlock(_grph_key[i]); + } + + artUnlock(_bck_key); + + messageListFree(&editorMessageList); + if (_bk_enable_0) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + return -1; + } + + characterEditorWindowBuf = windowGetBuffer(characterEditorWindowHandle); + memcpy(characterEditorWindowBuf, characterEditorWindowBackgroundBuf, 640 * 480); + + if (gCharacterEditorIsCreationMode) { + fontSetCurrent(103); + + // CHAR POINTS + str = getmsg(&editorMessageList, &editorMessageListItem, 116); + fontDrawText(characterEditorWindowBuf + (286 * 640) + 14, str, 640, 640, _colorTable[18979]); + characterEditorRenderBigNumber(126, 282, 0, characterEditorRemainingCharacterPoints, 0, characterEditorWindowHandle); + + // OPTIONS + str = getmsg(&editorMessageList, &editorMessageListItem, 101); + fontDrawText(characterEditorWindowBuf + (454 * 640) + 363, str, 640, 640, _colorTable[18979]); + + // OPTIONAL TRAITS + str = getmsg(&editorMessageList, &editorMessageListItem, 139); + fontDrawText(characterEditorWindowBuf + (326 * 640) + 52, str, 640, 640, _colorTable[18979]); + characterEditorRenderBigNumber(522, 228, 0, _optrt_count, 0, characterEditorWindowHandle); + + // TAG SKILLS + str = getmsg(&editorMessageList, &editorMessageListItem, 138); + fontDrawText(characterEditorWindowBuf + (233 * 640) + 422, str, 640, 640, _colorTable[18979]); + characterEditorRenderBigNumber(522, 228, 0, _tagskill_count, 0, characterEditorWindowHandle); + } else { + fontSetCurrent(103); + + str = getmsg(&editorMessageList, &editorMessageListItem, 109); + strcpy(perks, str); + + str = getmsg(&editorMessageList, &editorMessageListItem, 110); + strcpy(karma, str); + + str = getmsg(&editorMessageList, &editorMessageListItem, 111); + strcpy(kills, str); + + // perks selected + len = fontGetStringWidth(perks); + fontDrawText( + _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 = fontGetStringWidth(karma); + fontDrawText(_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 = fontGetStringWidth(kills); + fontDrawText(_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 = fontGetStringWidth(perks); + fontDrawText(_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 = fontGetStringWidth(karma); + fontDrawText(_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 = fontGetStringWidth(kills); + fontDrawText(_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 = fontGetStringWidth(perks); + fontDrawText(_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 = fontGetStringWidth(karma); + fontDrawText(_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 = fontGetStringWidth(kills); + fontDrawText(_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]); + + editorRenderFolders(); + + fontSetCurrent(103); + + // PRINT + str = getmsg(&editorMessageList, &editorMessageListItem, 103); + fontDrawText(characterEditorWindowBuf + (EDITOR_WIN_WIDTH * PRINT_BTN_Y) + PRINT_BTN_X, str, EDITOR_WIN_WIDTH, EDITOR_WIN_WIDTH, _colorTable[18979]); + + editorRenderPcStats(); + _folder_init(); + } + + fontSetCurrent(103); + + // CANCEL + str = getmsg(&editorMessageList, &editorMessageListItem, 102); + fontDrawText(characterEditorWindowBuf + (EDITOR_WIN_WIDTH * CANCEL_BTN_Y) + CANCEL_BTN_X, str, EDITOR_WIN_WIDTH, EDITOR_WIN_WIDTH, _colorTable[18979]); + + // DONE + str = getmsg(&editorMessageList, &editorMessageListItem, 100); + fontDrawText(characterEditorWindowBuf + (EDITOR_WIN_WIDTH * DONE_BTN_Y) + DONE_BTN_X, str, EDITOR_WIN_WIDTH, EDITOR_WIN_WIDTH, _colorTable[18979]); + + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + + if (!gCharacterEditorIsCreationMode) { + _SliderPlusID = buttonCreate( + characterEditorWindowHandle, + 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 = buttonCreate( + characterEditorWindowHandle, + 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); + buttonSetCallbacks(_SliderPlusID, _gsound_red_butt_press, NULL); + buttonSetCallbacks(_SliderNegID, _gsound_red_butt_press, NULL); + } + + editorRenderSkills(0); + editorRenderDetails(); + soundContinueAll(); + editorRenderName(); + editorRenderAge(); + editorRenderGender(); + + if (gCharacterEditorIsCreationMode) { + x = NAME_BUTTON_X; + btn = buttonCreate( + characterEditorWindowHandle, + 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) { + buttonSetMask(btn, _grphbmp[EDITOR_GRAPHIC_NAME_MASK]); + buttonSetCallbacks(btn, _gsound_lrg_butt_press, NULL); + } + + x += _GInfo[EDITOR_GRAPHIC_NAME_ON].width; + btn = buttonCreate( + characterEditorWindowHandle, + 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) { + buttonSetMask(btn, _grphbmp[EDITOR_GRAPHIC_AGE_MASK]); + buttonSetCallbacks(btn, _gsound_lrg_butt_press, NULL); + } + + x += _GInfo[EDITOR_GRAPHIC_AGE_ON].width; + btn = buttonCreate( + characterEditorWindowHandle, + 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) { + buttonSetMask(btn, _grphbmp[EDITOR_GRAPHIC_SEX_MASK]); + buttonSetCallbacks(btn, _gsound_lrg_butt_press, NULL); + } + + y = TAG_SKILLS_BUTTON_Y; + for (i = 0; i < SKILL_COUNT; i++) { + _tag_bids[i] = buttonCreate( + characterEditorWindowHandle, + 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] = buttonCreate( + characterEditorWindowHandle, + 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] = buttonCreate( + characterEditorWindowHandle, + 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; + } + + characterEditorWindowRenderTraits(); + } else { + x = NAME_BUTTON_X; + blitBufferToBufferTrans(_grphcpy[EDITOR_GRAPHIC_NAME_OFF], + _GInfo[EDITOR_GRAPHIC_NAME_ON].width, + _GInfo[EDITOR_GRAPHIC_NAME_ON].height, + _GInfo[EDITOR_GRAPHIC_NAME_ON].width, + characterEditorWindowBuf + (EDITOR_WIN_WIDTH * NAME_BUTTON_Y) + x, + EDITOR_WIN_WIDTH); + + x += _GInfo[EDITOR_GRAPHIC_NAME_ON].width; + blitBufferToBufferTrans(_grphcpy[EDITOR_GRAPHIC_AGE_OFF], + _GInfo[EDITOR_GRAPHIC_AGE_ON].width, + _GInfo[EDITOR_GRAPHIC_AGE_ON].height, + _GInfo[EDITOR_GRAPHIC_AGE_ON].width, + characterEditorWindowBuf + (EDITOR_WIN_WIDTH * NAME_BUTTON_Y) + x, + EDITOR_WIN_WIDTH); + + x += _GInfo[EDITOR_GRAPHIC_AGE_ON].width; + blitBufferToBufferTrans(_grphcpy[EDITOR_GRAPHIC_SEX_OFF], + _GInfo[EDITOR_GRAPHIC_SEX_ON].width, + _GInfo[EDITOR_GRAPHIC_SEX_ON].height, + _GInfo[EDITOR_GRAPHIC_SEX_ON].width, + characterEditorWindowBuf + (EDITOR_WIN_WIDTH * NAME_BUTTON_Y) + x, + EDITOR_WIN_WIDTH); + + btn = buttonCreate(characterEditorWindowHandle, + 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) { + buttonSetMask(btn, _grphbmp[EDITOR_GRAPHIC_FOLDER_MASK]); + } + } + + if (gCharacterEditorIsCreationMode) { + // +/- buttons for stats + for (i = 0; i < 7; i++) { + _stat_bids_plus[i] = buttonCreate(characterEditorWindowHandle, + 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) { + buttonSetCallbacks(_stat_bids_plus[i], _gsound_red_butt_press, NULL); + } + + _stat_bids_minus[i] = buttonCreate(characterEditorWindowHandle, + 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) { + buttonSetCallbacks(_stat_bids_minus[i], _gsound_red_butt_press, NULL); + } + } + } + + _RegInfoAreas(); + soundContinueAll(); + + btn = buttonCreate( + characterEditorWindowHandle, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate( + characterEditorWindowHandle, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate( + characterEditorWindowHandle, + 455, + 454, + _GInfo[23].width, + _GInfo[23].height, + -1, + -1, + -1, + 500, + _grphbmp[23], + _grphbmp[24], + 0, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + windowRefresh(characterEditorWindowHandle); + indicatorBarHide(); + + return 0; +} + +// 0x433AA8 +void characterEditorWindowFree() +{ + if (_folder_down_button != -1) { + buttonDestroy(_folder_down_button); + _folder_down_button = -1; + } + + if (_folder_up_button != -1) { + buttonDestroy(_folder_up_button); + _folder_up_button = -1; + } + + windowDestroy(characterEditorWindowHandle); + + for (int index = 0; index < EDITOR_GRAPHIC_COUNT; index++) { + artUnlock(_grph_key[index]); + + if (_copyflag[index]) { + internal_free(_grphcpy[index]); + } + } + + artUnlock(_bck_key); + + // NOTE: Uninline. + genericReputationFree(); + + // NOTE: Uninline. + karmaFree(); + + messageListFree(&editorMessageList); + + interfaceBarRefresh(); + + if (_bk_enable_0) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + fontSetCurrent(characterEditorWindowOldFont); + + if (gCharacterEditorIsCreationMode == 1) { + skillsSetTagged(_temp_tag_skill, 3); + traitsSetSelected(_temp_trait[0], _temp_trait[1]); + characterEditorSelectedItem = 0; + critterAdjustHitPoints(gDude, 1000); + } + + indicatorBarShow(); +} + +// CharEditInit +// 0x433C0C +void _CharEditInit() +{ + int i; + + characterEditorSelectedItem = 0; + _skill_cursor = 0; + _slider_y = 27; + _free_perk = 0; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_PERKS; + + for (i = 0; i < 2; i++) { + _temp_trait[i] = -1; + _trait_back[i] = -1; + } + + characterEditorRemainingCharacterPoints = 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 = fontGetStringWidth("_") - 4; + int windowWidth = windowGetWidth(win); + int v60 = fontGetLineHeight(); + unsigned char* windowBuffer = windowGetBuffer(win); + if (maxLength > 255) { + maxLength = 255; + } + + char copy[257]; + strcpy(copy, text); + + int nameLength = strlen(text); + copy[nameLength] = ' '; + copy[nameLength + 1] = '\0'; + + int nameWidth = fontGetStringWidth(copy); + + bufferFill(windowBuffer + windowWidth * y + x, nameWidth, fontGetLineHeight(), windowWidth, backgroundColor); + fontDrawText(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); + + windowRefresh(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) { + soundPlayFile("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) { + bufferFill(windowBuffer + windowWidth * y + x, fontGetStringWidth(copy), v60, windowWidth, backgroundColor); + copy[nameLength - 1] = ' '; + copy[nameLength] = '\0'; + fontDrawText(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); + nameLength--; + + windowRefresh(win); + } else if ((keyCode >= KEY_FIRST_INPUT_CHARACTER && keyCode <= KEY_LAST_INPUT_CHARACTER) && nameLength < maxLength) { + if ((flags & 0x01) != 0) { + if (!_isdoschar(keyCode)) { + break; + } + } + + bufferFill(windowBuffer + windowWidth * y + x, fontGetStringWidth(copy), v60, windowWidth, backgroundColor); + + copy[nameLength] = keyCode & 0xFF; + copy[nameLength + 1] = ' '; + copy[nameLength + 2] = '\0'; + fontDrawText(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); + nameLength++; + + windowRefresh(win); + } + } + + blinkingCounter -= 1; + if (blinkingCounter == 0) { + blinkingCounter = 3; + + int color = blink ? backgroundColor : textColor; + blink = !blink; + + bufferFill(windowBuffer + windowWidth * y + x + fontGetStringWidth(copy) - cursorWidth, cursorWidth, v60 - 2, windowWidth, color); + } + + windowRefresh(win); + + while (getTicksSince(_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 +void editorRenderFolders() +{ + if (gCharacterEditorIsCreationMode) { + return; + } + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + (360 * 640) + 34, 280, 120, 640, characterEditorWindowBuf + (360 * 640) + 34, 640); + + fontSetCurrent(101); + + switch (characterEditorWindowSelectedFolder) { + case EDITOR_FOLDER_PERKS: + blitBufferToBuffer(_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, + characterEditorWindowBuf + (327 * 640) + 11, + 640); + editorRenderPerks(); + break; + case EDITOR_FOLDER_KARMA: + blitBufferToBuffer(_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, + characterEditorWindowBuf + (327 * 640) + 11, + 640); + editorRenderKarma(); + break; + case EDITOR_FOLDER_KILLS: + blitBufferToBuffer(_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, + characterEditorWindowBuf + (327 * 640) + 11, + 640); + _kills_count = editorRenderKills(); + break; + default: + debugPrint("\n ** Unknown folder type! **\n"); + break; + } +} + +// 0x434238 +void editorRenderPerks() +{ + const char* string; + char perkName[80]; + int perk; + int perkLevel; + bool hasContent = false; + + _folder_clear(); + + if (_temp_trait[0] != -1) { + // TRAITS + string = getmsg(&editorMessageList, &editorMessageListItem, 156); + if (_folder_print_seperator(string)) { + _folder_card_fid = 54; + // Optional Traits + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 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(&editorMessageList, &editorMessageListItem, 147); + hasContent = true; + } + + if (_temp_trait[0] != -1) { + string = traitGetName(_temp_trait[0]); + if (_folder_print_line(string)) { + _folder_card_fid = traitGetFrmId(_temp_trait[0]); + _folder_card_title = traitGetName(_temp_trait[0]); + _folder_card_title2 = NULL; + _folder_card_desc = traitGetDescription(_temp_trait[0]); + hasContent = true; + } + } + + if (_temp_trait[1] != -1) { + string = traitGetName(_temp_trait[1]); + if (_folder_print_line(string)) { + _folder_card_fid = traitGetFrmId(_temp_trait[1]); + _folder_card_title = traitGetName(_temp_trait[1]); + _folder_card_title2 = NULL; + _folder_card_desc = traitGetDescription(_temp_trait[1]); + hasContent = true; + } + } + } + + for (perk = 0; perk < PERK_COUNT; perk++) { + if (perkGetRank(gDude, perk) != 0) { + break; + } + } + + if (perk != PERK_COUNT) { + // PERKS + string = getmsg(&editorMessageList, &editorMessageListItem, 109); + _folder_print_seperator(string); + + for (perk = 0; perk < PERK_COUNT; perk++) { + perkLevel = perkGetRank(gDude, perk); + if (perkLevel != 0) { + string = perkGetName(perk); + + if (perkLevel == 1) { + strcpy(perkName, string); + } else { + sprintf(perkName, "%s (%d)", string, perkLevel); + } + + if (_folder_print_line(perkName)) { + _folder_card_fid = perkGetFrmId(perk); + _folder_card_title = perkGetName(perk); + _folder_card_title2 = NULL; + _folder_card_desc = perkGetDescription(perk); + hasContent = true; + } + } + } + } + + if (!hasContent) { + _folder_card_fid = 71; + // Perks + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 124); + _folder_card_title2 = NULL; + // Perks add additional abilities. Every third experience level, you can choose one perk. + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 127); + } +} + +// 0x434498 +int _kills_list_comp(const KillInfo* a, const KillInfo* b) +{ + return stricmp(a->name, b->name); +} + +// 0x4344A4 +int editorRenderKills() +{ + int i; + int killsCount; + KillInfo kills[19]; + int usedKills = 0; + bool hasContent = false; + + _folder_clear(); + + for (i = 0; i < KILL_TYPE_COUNT; i++) { + killsCount = killsGetByType(i); + if (killsCount != 0) { + KillInfo* killInfo = &(kills[usedKills]); + killInfo->name = killTypeGetName(i); + killInfo->killTypeId = i; + killInfo->kills = killsCount; + usedKills++; + } + } + + if (usedKills != 0) { + qsort(kills, usedKills, 12, (int (*)(const void*, const void*))_kills_list_comp); + + for (i = 0; i < usedKills; i++) { + KillInfo* killInfo = &(kills[i]); + if (editorDrawKillsEntry(killInfo->name, killInfo->kills)) { + _folder_card_fid = 46; + _folder_card_title = _folder_card_string; + _folder_card_title2 = NULL; + _folder_card_desc = killTypeGetDescription(kills[i].killTypeId); + sprintf(_folder_card_string, "%s %s", killInfo->name, getmsg(&editorMessageList, &editorMessageListItem, 126)); + hasContent = true; + } + } + } + + if (!hasContent) { + _folder_card_fid = 46; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 126); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 129); + } + + return usedKills; +} + +// 0x4345DC +void characterEditorRenderBigNumber(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 = windowGetWidth(windowHandle); + windowBuf = windowGetBuffer(windowHandle); + + rect.left = x; + rect.top = y; + rect.right = x + BIG_NUM_WIDTH * 2; + rect.bottom = 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(); + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + onesBufferPtr, + windowWidth); + windowRefreshRect(windowHandle, &rect); + while (getTicksSince(_frame_time) < BIG_NUM_ANIMATION_DELAY) + ; + } + + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + onesBufferPtr, + windowWidth); + windowRefreshRect(windowHandle, &rect); + + if (previousValue / 10 != tens) { + _frame_time = _get_time(); + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + tensBufferPtr, + windowWidth); + windowRefreshRect(windowHandle, &rect); + while (getTicksSince(_frame_time) < BIG_NUM_ANIMATION_DELAY) + ; + } + + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + tensBufferPtr, + windowWidth); + windowRefreshRect(windowHandle, &rect); + } else { + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + tensBufferPtr, + windowWidth); + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + onesBufferPtr, + windowWidth); + } + } else { + + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + tensBufferPtr, + windowWidth); + blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9, + BIG_NUM_WIDTH, + BIG_NUM_HEIGHT, + _GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, + onesBufferPtr, + windowWidth); + } +} + +// 0x434920 +void editorRenderPcStats() +{ + 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 (gCharacterEditorIsCreationMode == 1) { + return; + } + + fontSetCurrent(101); + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + 640 * 280 + 32, 124, 32, 640, characterEditorWindowBuf + 640 * 280 + 32, 640); + + // LEVEL + y = 280; + if (characterEditorSelectedItem != 7) { + color = _colorTable[992]; + } else { + color = _colorTable[32747]; + } + + int level = pcGetStat(PC_STAT_LEVEL); + sprintf(stringBuffer, "%s %d", + getmsg(&editorMessageList, &editorMessageListItem, 113), + level); + fontDrawText(characterEditorWindowBuf + 640 * y + 32, stringBuffer, 640, 640, color); + + // EXPERIENCE + y += fontGetLineHeight() + 1; + if (characterEditorSelectedItem != 8) { + color = _colorTable[992]; + } else { + color = _colorTable[32747]; + } + + int exp = pcGetStat(PC_STAT_EXPERIENCE); + sprintf(stringBuffer, "%s %s", + getmsg(&editorMessageList, &editorMessageListItem, 114), + _itostndn(exp, formattedValueBuffer)); + fontDrawText(characterEditorWindowBuf + 640 * y + 32, stringBuffer, 640, 640, color); + + // EXP NEEDED TO NEXT LEVEL + y += fontGetLineHeight() + 1; + if (characterEditorSelectedItem != 9) { + color = _colorTable[992]; + } else { + color = _colorTable[32747]; + } + + int expToNextLevel = pcGetExperienceForNextLevel(); + 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(&editorMessageList, &editorMessageListItem, expMsgId), + formattedValue); + fontDrawText(characterEditorWindowBuf + 640 * y + 32, stringBuffer, 640, 640, color); +} + +// 0x434B38 +void editorRenderPrimaryStat(int stat, bool animate, int previousValue) +{ + int off; + int color; + const char* description; + int value; + int flags; + int messageListItemId; + + fontSetCurrent(101); + + if (stat == RENDER_ALL_STATS) { + // NOTE: Original code is different, looks like tail recursion + // optimization. + for (stat = 0; stat < 7; stat++) { + editorRenderPrimaryStat(stat, 0, 0); + } + return; + } + + if (characterEditorSelectedItem == stat) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + off = 640 * (_StatYpos[stat] + 8) + 103; + + // TODO: The original code is different. + if (gCharacterEditorIsCreationMode) { + value = critterGetBaseStatWithTraitModifier(gDude, stat) + critterGetBonusStat(gDude, stat); + + flags = 0; + + if (animate) { + flags |= ANIMATE; + } + + if (value > 10) { + flags |= RED_NUMBERS; + } + + characterEditorRenderBigNumber(58, _StatYpos[stat], flags, value, previousValue, characterEditorWindowHandle); + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + off, 40, fontGetLineHeight(), 640, characterEditorWindowBuf + off, 640); + + messageListItemId = critterGetStat(gDude, stat) + 199; + if (messageListItemId > 210) { + messageListItemId = 210; + } + + description = getmsg(&editorMessageList, &editorMessageListItem, messageListItemId); + fontDrawText(characterEditorWindowBuf + 640 * (_StatYpos[stat] + 8) + 103, description, 640, 640, color); + } else { + value = critterGetStat(gDude, stat); + characterEditorRenderBigNumber(58, _StatYpos[stat], 0, value, 0, characterEditorWindowHandle); + blitBufferToBuffer(characterEditorWindowBackgroundBuf + off, 40, fontGetLineHeight(), 640, characterEditorWindowBuf + off, 640); + + value = critterGetStat(gDude, stat); + if (value > 10) { + value = 10; + } + + description = statGetValueDescription(value); + fontDrawText(characterEditorWindowBuf + off, description, 640, 640, color); + } +} + +// 0x434F18 +void editorRenderGender() +{ + int gender; + char* str; + char text[32]; + int x, width; + + fontSetCurrent(103); + + gender = critterGetStat(gDude, STAT_GENDER); + str = getmsg(&editorMessageList, &editorMessageListItem, 107 + gender); + + strcpy(text, str); + + width = _GInfo[EDITOR_GRAPHIC_SEX_ON].width; + x = (width / 2) - (fontGetStringWidth(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; + fontDrawText(_grphcpy[EDITOR_GRAPHIC_SEX_ON] + x, text, width, width, _colorTable[14723]); + fontDrawText(_grphcpy[EDITOR_GRAPHIC_SEX_OFF] + x, text, width, width, _colorTable[18979]); +} + +// 0x43501C +void editorRenderAge() +{ + int age; + char* str; + char text[32]; + int x, width; + + fontSetCurrent(103); + + age = critterGetStat(gDude, STAT_AGE); + str = getmsg(&editorMessageList, &editorMessageListItem, 104); + + sprintf(text, "%s %d", str, age); + + width = _GInfo[EDITOR_GRAPHIC_AGE_ON].width; + x = (width / 2) + 1 - (fontGetStringWidth(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; + fontDrawText(_grphcpy[EDITOR_GRAPHIC_AGE_ON] + x, text, width, width, _colorTable[14723]); + fontDrawText(_grphcpy[EDITOR_GRAPHIC_AGE_OFF] + x, text, width, width, _colorTable[18979]); +} + +// 0x435118 +void editorRenderName() +{ + char* str; + char text[32]; + int x, width; + char *pch, tmp; + bool has_space; + + fontSetCurrent(103); + + str = critterGetName(gDude); + strcpy(text, str); + + if (fontGetStringWidth(text) > 100) { + pch = text; + has_space = false; + while (*pch != '\0') { + tmp = *pch; + *pch = '\0'; + if (tmp == ' ') { + has_space = true; + } + + if (fontGetStringWidth(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 - (fontGetStringWidth(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; + fontDrawText(_grphcpy[EDITOR_GRAPHIC_NAME_ON] + x, text, width, width, _colorTable[14723]); + fontDrawText(_grphcpy[EDITOR_GRAPHIC_NAME_OFF] + x, text, width, width, _colorTable[18979]); +} + +// 0x43527C +void editorRenderSecondaryStats() +{ + int conditions; + int color; + const char* messageListItemText; + char t[420]; // TODO: Size is wrong. + int y; + + conditions = gDude->data.critter.combat.results; + + fontSetCurrent(101); + + y = 46; + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + 640 * y + 194, 118, 108, 640, characterEditorWindowBuf + 640 * y + 194, 640); + + // Hit Points + if (characterEditorSelectedItem == EDITOR_HIT_POINTS) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + int currHp; + int maxHp; + if (gCharacterEditorIsCreationMode) { + maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + currHp = maxHp; + } else { + maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + currHp = critterGetHitPoints(gDude); + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 300); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + sprintf(t, "%d/%d", currHp, maxHp); + fontDrawText(characterEditorWindowBuf + 640 * y + 263, t, 640, 640, color); + + // Poisoned + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_POISONED) { + color = critterGetPoison(gDude) != 0 ? _colorTable[32747] : _colorTable[15845]; + } else { + color = critterGetPoison(gDude) != 0 ? _colorTable[992] : _colorTable[1313]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 312); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Radiated + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_RADIATED) { + color = critterGetRadiation(gDude) != 0 ? _colorTable[32747] : _colorTable[15845]; + } else { + color = critterGetRadiation(gDude) != 0 ? _colorTable[992] : _colorTable[1313]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 313); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Eye Damage + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_EYE_DAMAGE) { + color = (conditions & DAM_BLIND) ? _colorTable[32747] : _colorTable[15845]; + } else { + color = (conditions & DAM_BLIND) ? _colorTable[992] : _colorTable[1313]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 314); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Crippled Right Arm + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == 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(&editorMessageList, &editorMessageListItem, 315); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Crippled Left Arm + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == 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(&editorMessageList, &editorMessageListItem, 316); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Crippled Right Leg + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == 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(&editorMessageList, &editorMessageListItem, 317); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + // Crippled Left Leg + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == 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(&editorMessageList, &editorMessageListItem, 318); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + y = 179; + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + 640 * y + 194, 116, 130, 640, characterEditorWindowBuf + 640 * y + 194, 640); + + // Armor Class + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ARMOR_CLASS) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 302); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_ARMOR_CLASS), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Action Points + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ACTION_POINTS) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 301); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Carry Weight + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CARRY_WEIGHT) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 311); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_CARRY_WEIGHT), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, critterIsEncumbered(gDude) ? _colorTable[31744] : color); + + // Melee Damage + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_MELEE_DAMAGE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 304); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_MELEE_DAMAGE), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Damage Resistance + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_DAMAGE_RESISTANCE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 305); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + sprintf(t, "%d%%", critterGetStat(gDude, STAT_DAMAGE_RESISTANCE)); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Poison Resistance + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_POISON_RESISTANCE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 306); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + sprintf(t, "%d%%", critterGetStat(gDude, STAT_POISON_RESISTANCE)); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Radiation Resistance + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_RADIATION_RESISTANCE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 307); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + sprintf(t, "%d%%", critterGetStat(gDude, STAT_RADIATION_RESISTANCE)); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Sequence + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_SEQUENCE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 308); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_SEQUENCE), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Healing Rate + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_HEALING_RATE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 309); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + itoa(critterGetStat(gDude, STAT_HEALING_RATE), t, 10); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); + + // Critical Chance + y += fontGetLineHeight() + 3; + + if (characterEditorSelectedItem == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CRITICAL_CHANCE) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 310); + sprintf(t, "%s", messageListItemText); + fontDrawText(characterEditorWindowBuf + 640 * y + 194, t, 640, 640, color); + + sprintf(t, "%d%%", critterGetStat(gDude, STAT_CRITICAL_CHANCE)); + fontDrawText(characterEditorWindowBuf + 640 * y + 288, t, 640, 640, color); +} + +// 0x436154 +void editorRenderSkills(int a1) +{ + int selectedSkill = -1; + const char* str; + int i; + int color; + int y; + int value; + char valueString[12]; // TODO: Size might be wrong. + + if (characterEditorSelectedItem >= EDITOR_FIRST_SKILL && characterEditorSelectedItem < 79) { + selectedSkill = characterEditorSelectedItem - EDITOR_FIRST_SKILL; + } + + if (gCharacterEditorIsCreationMode == 0 && a1 == 0) { + buttonDestroy(_SliderPlusID); + buttonDestroy(_SliderNegID); + _SliderNegID = -1; + _SliderPlusID = -1; + } + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + 370, 270, 252, 640, characterEditorWindowBuf + 370, 640); + + fontSetCurrent(103); + + // SKILLS + str = getmsg(&editorMessageList, &editorMessageListItem, 117); + fontDrawText(characterEditorWindowBuf + 640 * 5 + 380, str, 640, 640, _colorTable[18979]); + + if (!gCharacterEditorIsCreationMode) { + // SKILL POINTS + str = getmsg(&editorMessageList, &editorMessageListItem, 112); + fontDrawText(characterEditorWindowBuf + 640 * 233 + 400, str, 640, 640, _colorTable[18979]); + + value = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + characterEditorRenderBigNumber(522, 228, 0, value, 0, characterEditorWindowHandle); + } else { + // TAG SKILLS + str = getmsg(&editorMessageList, &editorMessageListItem, 138); + fontDrawText(characterEditorWindowBuf + 640 * 233 + 422, str, 640, 640, _colorTable[18979]); + + // TODO: Check. + if (a1 == 2 && !_first_skill_list) { + characterEditorRenderBigNumber(522, 228, ANIMATE, _tagskill_count, _old_tags, characterEditorWindowHandle); + } else { + characterEditorRenderBigNumber(522, 228, 0, _tagskill_count, 0, characterEditorWindowHandle); + _first_skill_list = 0; + } + } + + skillsSetTagged(_temp_tag_skill, NUM_TAGGED_SKILLS); + + fontSetCurrent(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 = skillGetName(i); + fontDrawText(characterEditorWindowBuf + 640 * y + 380, str, 640, 640, color); + + value = skillGetValue(gDude, i); + sprintf(valueString, "%d%%", value); + + // TODO: Check text position. + fontDrawText(characterEditorWindowBuf + 640 * y + 573, valueString, 640, 640, color); + + y += fontGetLineHeight() + 1; + } + + if (!gCharacterEditorIsCreationMode) { + y = _skill_cursor * (fontGetLineHeight() + 1); + _slider_y = y + 27; + + blitBufferToBufferTrans( + _grphbmp[EDITOR_GRAPHIC_SLIDER], + _GInfo[EDITOR_GRAPHIC_SLIDER].width, + _GInfo[EDITOR_GRAPHIC_SLIDER].height, + _GInfo[EDITOR_GRAPHIC_SLIDER].width, + characterEditorWindowBuf + 640 * (y + 16) + 592, + 640); + + if (a1 == 0) { + if (_SliderPlusID == -1) { + _SliderPlusID = buttonCreate( + characterEditorWindowHandle, + 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); + buttonSetCallbacks(_SliderPlusID, _gsound_red_butt_press, NULL); + } + + if (_SliderNegID == -1) { + _SliderNegID = buttonCreate( + characterEditorWindowHandle, + 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); + buttonSetCallbacks(_SliderNegID, _gsound_red_butt_press, NULL); + } + } + } +} + +// 0x4365AC +void editorRenderDetails() +{ + int graphicId; + char* title; + char* description; + + if (characterEditorSelectedItem < 0 || characterEditorSelectedItem >= 98) { + return; + } + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + (640 * 267) + 345, 277, 170, 640, characterEditorWindowBuf + (267 * 640) + 345, 640); + + if (characterEditorSelectedItem >= 0 && characterEditorSelectedItem < 7) { + description = statGetDescription(characterEditorSelectedItem); + title = statGetName(characterEditorSelectedItem); + graphicId = statGetFrmId(characterEditorSelectedItem); + _DrawCard(graphicId, title, NULL, description); + } else if (characterEditorSelectedItem >= 7 && characterEditorSelectedItem < 10) { + if (gCharacterEditorIsCreationMode) { + switch (characterEditorSelectedItem) { + case 7: + // Character Points + description = getmsg(&editorMessageList, &editorMessageListItem, 121); + title = getmsg(&editorMessageList, &editorMessageListItem, 120); + _DrawCard(7, title, NULL, description); + break; + } + } else { + switch (characterEditorSelectedItem) { + case 7: + description = pcStatGetDescription(PC_STAT_LEVEL); + title = pcStatGetName(PC_STAT_LEVEL); + _DrawCard(7, title, NULL, description); + break; + case 8: + description = pcStatGetDescription(PC_STAT_EXPERIENCE); + title = pcStatGetName(PC_STAT_EXPERIENCE); + _DrawCard(8, title, NULL, description); + break; + case 9: + // Next Level + description = getmsg(&editorMessageList, &editorMessageListItem, 123); + title = getmsg(&editorMessageList, &editorMessageListItem, 122); + _DrawCard(9, title, NULL, description); + break; + } + } + } else if ((characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43) || (characterEditorSelectedItem >= 82 && characterEditorSelectedItem < 98)) { + _DrawCard(_folder_card_fid, _folder_card_title, _folder_card_title2, _folder_card_desc); + } else if (characterEditorSelectedItem >= 43 && characterEditorSelectedItem < 51) { + switch (characterEditorSelectedItem) { + case EDITOR_HIT_POINTS: + description = statGetDescription(STAT_MAXIMUM_HIT_POINTS); + title = getmsg(&editorMessageList, &editorMessageListItem, 300); + graphicId = statGetFrmId(STAT_MAXIMUM_HIT_POINTS); + _DrawCard(graphicId, title, NULL, description); + break; + case EDITOR_POISONED: + description = getmsg(&editorMessageList, &editorMessageListItem, 400); + title = getmsg(&editorMessageList, &editorMessageListItem, 312); + _DrawCard(11, title, NULL, description); + break; + case EDITOR_RADIATED: + description = getmsg(&editorMessageList, &editorMessageListItem, 401); + title = getmsg(&editorMessageList, &editorMessageListItem, 313); + _DrawCard(12, title, NULL, description); + break; + case EDITOR_EYE_DAMAGE: + description = getmsg(&editorMessageList, &editorMessageListItem, 402); + title = getmsg(&editorMessageList, &editorMessageListItem, 314); + _DrawCard(13, title, NULL, description); + break; + case EDITOR_CRIPPLED_RIGHT_ARM: + description = getmsg(&editorMessageList, &editorMessageListItem, 403); + title = getmsg(&editorMessageList, &editorMessageListItem, 315); + _DrawCard(14, title, NULL, description); + break; + case EDITOR_CRIPPLED_LEFT_ARM: + description = getmsg(&editorMessageList, &editorMessageListItem, 404); + title = getmsg(&editorMessageList, &editorMessageListItem, 316); + _DrawCard(15, title, NULL, description); + break; + case EDITOR_CRIPPLED_RIGHT_LEG: + description = getmsg(&editorMessageList, &editorMessageListItem, 405); + title = getmsg(&editorMessageList, &editorMessageListItem, 317); + _DrawCard(16, title, NULL, description); + break; + case EDITOR_CRIPPLED_LEFT_LEG: + description = getmsg(&editorMessageList, &editorMessageListItem, 406); + title = getmsg(&editorMessageList, &editorMessageListItem, 318); + _DrawCard(17, title, NULL, description); + break; + } + } else if (characterEditorSelectedItem >= EDITOR_FIRST_DERIVED_STAT && characterEditorSelectedItem < 61) { + int derivedStatIndex = characterEditorSelectedItem - 51; + int stat = word_431D6C[derivedStatIndex]; + description = statGetDescription(stat); + title = statGetName(stat); + graphicId = word_431D3A[derivedStatIndex]; + _DrawCard(graphicId, title, NULL, description); + } else if (characterEditorSelectedItem >= EDITOR_FIRST_SKILL && characterEditorSelectedItem < 79) { + int skill = characterEditorSelectedItem - 61; + const char* attributesDescription = skillGetAttributes(skill); + + char formatted[150]; // TODO: Size is probably wrong. + const char* base = getmsg(&editorMessageList, &editorMessageListItem, 137); + int defaultValue = skillGetDefaultValue(skill); + sprintf(formatted, "%s %d%% %s", base, defaultValue, attributesDescription); + + graphicId = skillGetFrmId(skill); + title = skillGetName(skill); + description = skillGetDescription(skill); + _DrawCard(graphicId, title, formatted, description); + } else if (characterEditorSelectedItem >= 79 && characterEditorSelectedItem < 82) { + switch (characterEditorSelectedItem) { + case EDITOR_TAG_SKILL: + if (gCharacterEditorIsCreationMode) { + // Tag Skill + description = getmsg(&editorMessageList, &editorMessageListItem, 145); + title = getmsg(&editorMessageList, &editorMessageListItem, 144); + _DrawCard(27, title, NULL, description); + } else { + // Skill Points + description = getmsg(&editorMessageList, &editorMessageListItem, 131); + title = getmsg(&editorMessageList, &editorMessageListItem, 130); + _DrawCard(27, title, NULL, description); + } + break; + case EDITOR_SKILLS: + // Skills + description = getmsg(&editorMessageList, &editorMessageListItem, 151); + title = getmsg(&editorMessageList, &editorMessageListItem, 150); + _DrawCard(27, title, NULL, description); + break; + case EDITOR_OPTIONAL_TRAITS: + // Optional Traits + description = getmsg(&editorMessageList, &editorMessageListItem, 147); + title = getmsg(&editorMessageList, &editorMessageListItem, 146); + _DrawCard(27, title, NULL, description); + break; + } + } +} + +// 0x436C4C +int characterEditorEditName() +{ + char* text; + + int windowWidth = _GInfo[EDITOR_GRAPHIC_CHARWIN].width; + int windowHeight = _GInfo[EDITOR_GRAPHIC_CHARWIN].height; + + int win = windowCreate(17, 0, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (win == -1) { + return -1; + } + + unsigned char* windowBuf = windowGetBuffer(win); + + // Copy background + memcpy(windowBuf, _grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); + + blitBufferToBufferTrans( + _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); + blitBufferToBufferTrans(_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); + + fontSetCurrent(103); + + text = getmsg(&editorMessageList, &editorMessageListItem, 100); + fontDrawText(windowBuf + windowWidth * 44 + 50, text, windowWidth, windowWidth, _colorTable[18979]); + + int doneBtn = buttonCreate(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) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + windowRefresh(win); + + fontSetCurrent(101); + + char name[64]; + strcpy(name, critterGetName(gDude)); + + 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') { + dudeSetName(nameCopy); + editorRenderName(); + windowDestroy(win); + return 0; + } + } + + // NOTE: original code is a bit different, the following chunk of code written two times. + + fontSetCurrent(101); + blitBufferToBuffer(_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); + + windowDestroy(win); + + return 0; +} + +// 0x436F70 +void _PrintName(unsigned char* buf, int a2) +{ + char str[64]; + char* v4; + + memcpy(str, byte_431D93, 64); + + fontSetCurrent(101); + + v4 = critterGetName(gDude); + + // TODO: Check. + strcpy(str, v4); + + fontDrawText(buf + 19 * a2 + 21, str, a2, a2, _colorTable[992]); +} + +// 0x436FEC +int characterEditorRunEditAgeDialog() +{ + 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(gDude, STAT_AGE); + + windowWidth = _GInfo[EDITOR_GRAPHIC_CHARWIN].width; + windowHeight = _GInfo[EDITOR_GRAPHIC_CHARWIN].height; + + win = windowCreate(_GInfo[EDITOR_GRAPHIC_NAME_ON].width + 9, 0, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (win == -1) { + return -1; + } + + windowBuf = windowGetBuffer(win); + + memcpy(windowBuf, _grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); + + blitBufferToBufferTrans( + _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); + blitBufferToBufferTrans( + _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); + + fontSetCurrent(103); + + messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 100); + fontDrawText(windowBuf + windowWidth * 44 + 50, messageListItemText, windowWidth, windowWidth, _colorTable[18979]); + + age = critterGetStat(gDude, STAT_AGE); + characterEditorRenderBigNumber(55, 10, 0, age, 0, win); + + doneBtn = buttonCreate(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) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + nextBtn = buttonCreate(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) { + buttonSetCallbacks(nextBtn, _gsound_med_butt_press, NULL); + } + + prevBtn = buttonCreate(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) { + buttonSetCallbacks(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) { + soundPlayFile("ib1p1xx1"); + } + + windowDestroy(win); + return 0; + } else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) { + break; + } else if (keyCode == 501) { + age = critterGetStat(gDude, STAT_AGE); + if (age < 35) { + change = 1; + } + } else if (keyCode == 502) { + age = critterGetStat(gDude, STAT_AGE); + if (age > 16) { + change = -1; + } + } else if (keyCode == KEY_PLUS || keyCode == KEY_UPPERCASE_N || keyCode == KEY_ARROW_UP) { + previousAge = critterGetStat(gDude, STAT_AGE); + if (previousAge < 35) { + flags = ANIMATE; + if (critterIncBaseStat(gDude, STAT_AGE) != 0) { + flags = 0; + } + age = critterGetStat(gDude, STAT_AGE); + characterEditorRenderBigNumber(55, 10, flags, age, previousAge, win); + } + } else if (keyCode == KEY_MINUS || keyCode == KEY_UPPERCASE_J || keyCode == KEY_ARROW_DOWN) { + previousAge = critterGetStat(gDude, STAT_AGE); + if (previousAge > 16) { + flags = ANIMATE; + if (critterDecBaseStat(gDude, STAT_AGE) != 0) { + flags = 0; + } + age = critterGetStat(gDude, STAT_AGE); + + characterEditorRenderBigNumber(55, 10, flags, age, previousAge, win); + } + } + + if (flags == ANIMATE) { + editorRenderAge(); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + windowRefresh(characterEditorWindowHandle); + windowRefresh(win); + } + + if (change != 0) { + int v33 = 0; + + _repFtime = 4; + + while (true) { + _frame_time = _get_time(); + + v33++; + + if ((!v32 && v33 == 1) || (v32 && v33 > dbl_50170B)) { + v32 = true; + + if (v33 > dbl_50170B) { + _repFtime++; + if (_repFtime > 24) { + _repFtime = 24; + } + } + + flags = ANIMATE; + previousAge = critterGetStat(gDude, STAT_AGE); + + if (change == 1) { + if (previousAge < 35) { + if (critterIncBaseStat(gDude, STAT_AGE) != 0) { + flags = 0; + } + } + } else { + if (previousAge >= 16) { + if (critterDecBaseStat(gDude, STAT_AGE) != 0) { + flags = 0; + } + } + } + + age = critterGetStat(gDude, STAT_AGE); + characterEditorRenderBigNumber(55, 10, flags, age, previousAge, win); + if (flags == ANIMATE) { + editorRenderAge(); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + windowRefresh(characterEditorWindowHandle); + windowRefresh(win); + } + } + + if (v33 > dbl_50170B) { + while (getTicksSince(_frame_time) < 1000 / _repFtime) + ; + } else { + while (getTicksSince(_frame_time) < 1000 / 24) + ; + } + + keyCode = _get_input(); + if (keyCode == 503 || keyCode == 504 || _game_user_wants_to_quit != 0) { + break; + } + } + } else { + windowRefresh(win); + + while (getTicksSince(_frame_time) < 1000 / 24) + ; + } + } + + critterSetBaseStat(gDude, STAT_AGE, savedAge); + editorRenderAge(); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + windowRefresh(characterEditorWindowHandle); + windowRefresh(win); + windowDestroy(win); + return 0; +} + +// 0x437664 +void characterEditorEditGender() +{ + char* text; + + int windowWidth = _GInfo[EDITOR_GRAPHIC_CHARWIN].width; + int windowHeight = _GInfo[EDITOR_GRAPHIC_CHARWIN].height; + + int x = 9; + x += _GInfo[EDITOR_GRAPHIC_NAME_ON].width; + x += _GInfo[EDITOR_GRAPHIC_AGE_ON].width; + int win = windowCreate(x, 0, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + + if (win == -1) { + return; + } + + unsigned char* windowBuf = windowGetBuffer(win); + + // Copy background + memcpy(windowBuf, _grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); + + blitBufferToBufferTrans(_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); + + fontSetCurrent(103); + + text = getmsg(&editorMessageList, &editorMessageListItem, 100); + fontDrawText(windowBuf + windowWidth * 48 + 52, text, windowWidth, windowWidth, _colorTable[18979]); + + int doneBtn = buttonCreate(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) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int btns[2]; + btns[0] = buttonCreate(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) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, NULL); + } + + btns[1] = buttonCreate(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); + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, NULL); + } + + int savedGender = critterGetStat(gDude, 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) { + soundPlayFile("ib1p1xx1"); + } + break; + } + + if (eventCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) { + critterSetBaseStat(gDude, STAT_GENDER, savedGender); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + windowRefresh(characterEditorWindowHandle); + 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. + critterSetBaseStat(gDude, STAT_GENDER, eventCode - 501); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + break; + } + + windowRefresh(win); + + while (getTicksSince(_frame_time) < 41) + ; + } + + editorRenderGender(); + windowDestroy(win); +} + +// 0x4379BC +void characterEditorHandleIncDecPrimaryStat(int eventCode) +{ + _repFtime = 4; + + int savedRemainingCharacterPoints = characterEditorRemainingCharacterPoints; + + if (!gCharacterEditorIsCreationMode) { + 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(gDude, decrementingStat); + if (critterDecBaseStat(gDude, decrementingStat) == 0) { + characterEditorRemainingCharacterPoints++; + } else { + cont = false; + } + + editorRenderPrimaryStat(decrementingStat, cont ? ANIMATE : 0, previousValue); + characterEditorRenderBigNumber(126, 282, cont ? ANIMATE : 0, characterEditorRemainingCharacterPoints, savedRemainingCharacterPoints, characterEditorWindowHandle); + critterUpdateDerivedStats(gDude); + editorRenderSecondaryStats(); + editorRenderSkills(0); + characterEditorSelectedItem = decrementingStat; + } else { + int previousValue = critterGetBaseStatWithTraitModifier(gDude, incrementingStat); + previousValue += critterGetBonusStat(gDude, incrementingStat); + if (characterEditorRemainingCharacterPoints > 0 && previousValue < 10 && critterIncBaseStat(gDude, incrementingStat) == 0) { + characterEditorRemainingCharacterPoints--; + } else { + cont = false; + } + + editorRenderPrimaryStat(incrementingStat, cont ? ANIMATE : 0, previousValue); + characterEditorRenderBigNumber(126, 282, cont ? ANIMATE : 0, characterEditorRemainingCharacterPoints, savedRemainingCharacterPoints, characterEditorWindowHandle); + critterUpdateDerivedStats(gDude); + editorRenderSecondaryStats(); + editorRenderSkills(0); + characterEditorSelectedItem = incrementingStat; + } + + windowRefresh(characterEditorWindowHandle); + } + + if (v11 >= 19.2) { + unsigned int delay = 1000 / _repFtime; + while (getTicksSince(_frame_time) < delay) { + } + } else { + while (getTicksSince(_frame_time) < 1000 / 24) { + } + } + } while (_get_input() != 518 && cont); + + editorRenderDetails(); +} + +// handle options dialog +// +// 0x437C08 +int _OptionWindow() +{ + int width = _GInfo[43].width; + int height = _GInfo[43].height; + + if (gCharacterEditorIsCreationMode) { + int win = windowCreate(238, 90, _GInfo[41].width, _GInfo[41].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (win == -1) { + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(win); + memcpy(windowBuffer, _grphbmp[41], _GInfo[41].width * _GInfo[41].height); + + fontSetCurrent(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] = internal_malloc(size); + if (down[index] == NULL) { + err = 1; + break; + } + + up[index] = internal_malloc(size); + if (up[index] == NULL) { + err = 2; + break; + } + + memcpy(down[index], _grphbmp[43], size); + memcpy(up[index], _grphbmp[42], size); + + const char* msg = getmsg(&editorMessageList, &editorMessageListItem, 600 + index); + + char dest[512]; + strcpy(dest, msg); + + int length = fontGetStringWidth(dest); + int v60 = width / 2 - length / 2; + fontDrawText(up[index] + v60, dest, width, width, _colorTable[18979]); + fontDrawText(down[index] + v60, dest, width, width, _colorTable[14723]); + + int btn = buttonCreate(win, 13, y, width, height, -1, -1, -1, 500 + index, up[index], down[index], NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_lrg_butt_press, NULL); + } + } while (0); + + y += height + 3; + } + + if (err != 0) { + if (err == 2) { + internal_free(down[index]); + } + + while (--index >= 0) { + internal_free(up[index]); + internal_free(down[index]); + } + + return -1; + } + + fontSetCurrent(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; + soundPlayFile("ib1p1xx1"); + } else if (keyCode == KEY_ESCAPE) { + rc = 2; + } else if (keyCode == 503 || keyCode == KEY_UPPERCASE_E || keyCode == KEY_LOWERCASE_E) { + // ERASE + char line1[512]; + strcpy(line1, getmsg(&editorMessageList, &editorMessageListItem, 605)); + + char line2[512]; + strcpy(line2, getmsg(&editorMessageList, &editorMessageListItem, 606)); + + const char* lines[] = { line1, line2 }; + if (showDialogBox(NULL, lines, 2, 169, 126, _colorTable[992], NULL, _colorTable[992], 0x10) != 0) { + _ResetPlayer(); + skillsGetTagged(_temp_tag_skill, NUM_TAGGED_SKILLS); + + int v224 = 3; + int v225 = 0; + do { + if (_temp_tag_skill[v224] != -1) { + break; + } + --v224; + ++v225; + } while (v224 > -1); + + if (gCharacterEditorIsCreationMode) { + v225--; + } + + _tagskill_count = v225; + + traitsGetSelected(&_temp_trait[0], &_temp_trait[1]); + + int v226 = 1; + int v227 = 0; + do { + if (_temp_trait[v226] != -1) { + break; + } + --v226; + ++v227; + } while (v226 > -1); + + _trait_count = v227; + critterUpdateDerivedStats(gDude); + _ResetScreen(); + } + } else if (keyCode == 502 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P) { + // PRINT TO FILE + char dest[512]; + dest[0] = '\0'; + + strcat(dest, "*.TXT"); + + char** fileList; + int fileListLength = fileNameListInit(dest, &fileList, 0, 0); + if (fileListLength != -1) { + char v236[512]; + + // PRINT + strcpy(v236, getmsg(&editorMessageList, &editorMessageListItem, 616)); + + // PRINT TO FILE + strcpy(dest, getmsg(&editorMessageList, &editorMessageListItem, 602)); + + if (_save_file_dialog(dest, fileList, v236, fileListLength, 168, 80, 0) == 0) { + strcat(v236, ".TXT"); + + dest[0] = '\0'; + + if (!characterFileExists(dest)) { + // already exists + sprintf(dest, + "%s %s", + strupr(v236), + getmsg(&editorMessageList, &editorMessageListItem, 609)); + + char v240[512]; + strcpy(v240, getmsg(&editorMessageList, &editorMessageListItem, 610)); + + const char* lines[] = { v240 }; + if (showDialogBox(dest, lines, 1, 169, 126, _colorTable[32328], NULL, _colorTable[32328], 0x10) != 0) { + rc = 1; + } else { + rc = 0; + } + } else { + rc = 1; + } + + if (rc != 0) { + dest[0] = '\0'; + strcat(dest, v236); + + if (characterPrintToFile(dest) == 0) { + sprintf(dest, + "%s%s", + strupr(v236), + getmsg(&editorMessageList, &editorMessageListItem, 607)); + showDialogBox(dest, NULL, 0, 169, 126, _colorTable[992], NULL, _colorTable[992], 0); + } else { + soundPlayFile("iisxxxx1"); + + sprintf(dest, + "%s%s%s", + getmsg(&editorMessageList, &editorMessageListItem, 611), + strupr(v236), + "!"); + showDialogBox(dest, NULL, 0, 169, 126, _colorTable[32328], NULL, _colorTable[992], 0x01); + } + } + } + + fileNameListFree(&fileList, 0); + } else { + soundPlayFile("iisxxxx1"); + + strcpy(dest, getmsg(&editorMessageList, &editorMessageListItem, 615)); + showDialogBox(dest, 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 + char path[MAX_PATH]; + path[0] = '\0'; + strcat(path, "*."); + strcat(path, "GCD"); + + char** fileNames; + int filesCount = fileNameListInit(path, &fileNames, 0, 0); + if (filesCount != -1) { + + } else { + soundPlayFile("iisxxxx1"); + + // Error reading file list! + strcpy(path, getmsg(&editorMessageList, &editorMessageListItem, 615)); + rc = 0; + + showDialogBox(path, NULL, 0, 169, 126, _colorTable[32328], NULL, _colorTable[32328], 0); + } + } else if (keyCode == 500 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S) { + // TODO: Incomplete. + } + + windowRefresh(win); + } + + windowDestroy(win); + + for (index = 0; index < 5; index++) { + internal_free(up[index]); + internal_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 = fileNameListInit(pattern, &fileNames, 0, 0); + if (filesCount == -1) { + soundPlayFile("iisxxxx1"); + + // Error reading file list! + strcpy(pattern, getmsg(&editorMessageList, &editorMessageListItem, 615)); + showDialogBox(pattern, NULL, 0, 169, 126, _colorTable[32328], NULL, _colorTable[32328], 0); + return 0; + } + + // PRINT + char fileName[512]; + strcpy(fileName, getmsg(&editorMessageList, &editorMessageListItem, 616)); + + char title[512]; + strcpy(title, getmsg(&editorMessageList, &editorMessageListItem, 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 (characterFileExists(title)) { + sprintf(title, + "%s %s", + strupr(fileName), + getmsg(&editorMessageList, &editorMessageListItem, 609)); + + char line2[512]; + strcpy(line2, getmsg(&editorMessageList, &editorMessageListItem, 610)); + + const char* lines[] = { line2 }; + v42 = showDialogBox(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 (characterPrintToFile(title) != 0) { + soundPlayFile("iisxxxx1"); + + sprintf(title, + "%s%s%s", + getmsg(&editorMessageList, &editorMessageListItem, 611), + strupr(fileName), + "!"); + showDialogBox(title, NULL, 0, 169, 126, _colorTable[32328], NULL, _colorTable[32328], 1); + } + } + } + + fileNameListFree(&fileNames, 0); + + return 0; +} + +// 0x4390B4 +bool characterFileExists(const char* fname) +{ + File* stream = fileOpen(fname, "rb"); + if (stream == NULL) { + return false; + } + + fileClose(stream); + return true; +} + +// 0x4390D0 +int characterPrintToFile(const char* fileName) +{ + File* stream = fileOpen(fileName, "wt"); + if (stream == NULL) { + return -1; + } + + fileWriteString("\n", stream); + fileWriteString("\n", stream); + + char title1[256]; + char title2[256]; + char title3[256]; + char padding[256]; + + // FALLOUT + strcpy(title1, getmsg(&editorMessageList, &editorMessageListItem, 620)); + + // NOTE: Uninline. + padding[0] = '\0'; + _AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); + + strcat(padding, title1); + strcat(padding, "\n"); + fileWriteString(padding, stream); + + // VAULT-13 PERSONNEL RECORD + strcpy(title1, getmsg(&editorMessageList, &editorMessageListItem, 621)); + + // NOTE: Uninline. + padding[0] = '\0'; + _AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); + + strcat(padding, title1); + strcat(padding, "\n"); + fileWriteString(padding, stream); + + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + sprintf(title1, "%.2d %s %d %.4d %s", + day, + getmsg(&editorMessageList, &editorMessageListItem, 500 + month - 1), + year, + gameTimeGetHour(), + getmsg(&editorMessageList, &editorMessageListItem, 622)); + + // NOTE: Uninline. + padding[0] = '\0'; + _AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); + + strcat(padding, title1); + strcat(padding, "\n"); + fileWriteString(padding, stream); + + // Blank line + fileWriteString("\n", stream); + + // Name + sprintf(title1, + "%s %s", + getmsg(&editorMessageList, &editorMessageListItem, 642), + critterGetName(gDude)); + + 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(&editorMessageList, &editorMessageListItem, 643), + critterGetStat(gDude, STAT_AGE)); + + // Gender + sprintf(title3, + "%s%s %s", + title2, + getmsg(&editorMessageList, &editorMessageListItem, 644), + getmsg(&editorMessageList, &editorMessageListItem, 645 + critterGetStat(gDude, STAT_GENDER))); + + fileWriteString(title3, stream); + fileWriteString("\n", stream); + + sprintf(title1, + "%s %.2d %s %s ", + getmsg(&editorMessageList, &editorMessageListItem, 647), + pcGetStat(PC_STAT_LEVEL), + getmsg(&editorMessageList, &editorMessageListItem, 648), + _itostndn(pcGetStat(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(&editorMessageList, &editorMessageListItem, 649), + _itostndn(pcGetExperienceForNextLevel(), title3)); + fileWriteString(title2, stream); + fileWriteString("\n", stream); + fileWriteString("\n", stream); + + // Statistics + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 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(&editorMessageList, &editorMessageListItem, 624), + critterGetStat(gDude, STAT_STRENGTH), + getmsg(&editorMessageList, &editorMessageListItem, 625), + critterGetHitPoints(gDude), + critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS), + getmsg(&editorMessageList, &editorMessageListItem, 626), + critterGetStat(gDude, STAT_STRENGTH)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Perception / Armor Class / Healing Rate + sprintf(title1, + "%s %.2d %s %.3d %s %.2d", + getmsg(&editorMessageList, &editorMessageListItem, 627), + critterGetStat(gDude, STAT_PERCEPTION), + getmsg(&editorMessageList, &editorMessageListItem, 628), + critterGetStat(gDude, STAT_ARMOR_CLASS), + getmsg(&editorMessageList, &editorMessageListItem, 629), + critterGetStat(gDude, STAT_HEALING_RATE)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Endurance / Action Points / Critical Chance + sprintf(title1, + "%s %.2d %s %.2d %s %.3d%%", + getmsg(&editorMessageList, &editorMessageListItem, 630), + critterGetStat(gDude, STAT_ENDURANCE), + getmsg(&editorMessageList, &editorMessageListItem, 631), + critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS), + getmsg(&editorMessageList, &editorMessageListItem, 632), + critterGetStat(gDude, STAT_CRITICAL_CHANCE)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Charisma / Melee Damage / Carry Weight + sprintf(title1, + "%s %.2d %s %.2d %s %.3d lbs.", + getmsg(&editorMessageList, &editorMessageListItem, 633), + critterGetStat(gDude, STAT_CHARISMA), + getmsg(&editorMessageList, &editorMessageListItem, 634), + critterGetStat(gDude, STAT_MELEE_DAMAGE), + getmsg(&editorMessageList, &editorMessageListItem, 635), + critterGetStat(gDude, STAT_CARRY_WEIGHT)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Intelligence / Damage Resistance + sprintf(title1, + "%s %.2d %s %.3d%%", + getmsg(&editorMessageList, &editorMessageListItem, 636), + critterGetStat(gDude, STAT_INTELLIGENCE), + getmsg(&editorMessageList, &editorMessageListItem, 637), + critterGetStat(gDude, STAT_DAMAGE_RESISTANCE)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Agility / Radiation Resistance + sprintf(title1, + "%s %.2d %s %.3d%%", + getmsg(&editorMessageList, &editorMessageListItem, 638), + critterGetStat(gDude, STAT_AGILITY), + getmsg(&editorMessageList, &editorMessageListItem, 639), + critterGetStat(gDude, STAT_RADIATION_RESISTANCE)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + // Luck / Poison Resistance + sprintf(title1, + "%s %.2d %s %.3d%%", + getmsg(&editorMessageList, &editorMessageListItem, 640), + critterGetStat(gDude, STAT_LUCK), + getmsg(&editorMessageList, &editorMessageListItem, 641), + critterGetStat(gDude, STAT_POISON_RESISTANCE)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + + fileWriteString("\n", stream); + fileWriteString("\n", stream); + + if (_temp_trait[0] != -1) { + // ::: Traits ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 650)); + fileWriteString(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", traitGetName(_temp_trait[index])); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } + } + + int perk = 0; + for (; perk < PERK_COUNT; perk++) { + if (perkGetRank(gDude, perk) != 0) { + break; + } + } + + if (perk < PERK_COUNT) { + // ::: Perks ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 651)); + fileWriteString(title1, stream); + + for (perk = 0; perk < PERK_COUNT; perk++) { + int rank = perkGetRank(gDude, perk); + if (rank != 0) { + if (rank == 1) { + sprintf(title1, " %s", perkGetName(perk)); + } else { + sprintf(title1, " %s (%d)", perkGetName(perk), rank); + } + + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } + } + + fileWriteString("\n", stream); + + // ::: Karma ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 652)); + fileWriteString(title1, stream); + + for (int index = 0; index < gKarmaEntriesLength; index++) { + KarmaEntry* karmaEntry = &(gKarmaEntries[index]); + if (karmaEntry->gvar == GVAR_PLAYER_REPUTATION) { + int reputation = 0; + for (; reputation < gGenericReputationEntriesLength; reputation++) { + GenericReputationEntry* reputationDescription = &(gGenericReputationEntries[reputation]); + if (gGameGlobalVars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) { + break; + } + } + + if (reputation < gGenericReputationEntriesLength) { + GenericReputationEntry* reputationDescription = &(gGenericReputationEntries[reputation]); + sprintf(title1, + " %s: %s (%s)", + getmsg(&editorMessageList, &editorMessageListItem, 125), + itoa(gGameGlobalVars[GVAR_PLAYER_REPUTATION], title2, 10), + getmsg(&editorMessageList, &editorMessageListItem, reputationDescription->name)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } else { + if (gGameGlobalVars[karmaEntry->gvar] != 0) { + sprintf(title1, " %s", getmsg(&editorMessageList, &editorMessageListItem, karmaEntry->name)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } + } + + bool hasTownReputationHeading = false; + for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) { + const TownReputationEntry* pair = &(gTownReputationEntries[index]); + if (_wmAreaIsKnown(pair->city)) { + if (!hasTownReputationHeading) { + fileWriteString("\n", stream); + + // ::: Reputation ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 657)); + fileWriteString(title1, stream); + hasTownReputationHeading = true; + } + + _wmGetAreaIdxName(pair->city, title2); + + int townReputation = gGameGlobalVars[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(&editorMessageList, &editorMessageListItem, townReputationMessageId)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } + + bool hasAddictionsHeading = false; + for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) { + if (gGameGlobalVars[gAddictionReputationVars[index]] != 0) { + if (!hasAddictionsHeading) { + fileWriteString("\n", stream); + + // ::: Addictions ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 656)); + fileWriteString(title1, stream); + hasAddictionsHeading = true; + } + + sprintf(title1, + " %s", + getmsg(&editorMessageList, &editorMessageListItem, 1004 + index)); + fileWriteString(title1, stream); + fileWriteString("\n", stream); + } + } + + fileWriteString("\n", stream); + + // ::: Skills ::: / ::: Kills ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 653)); + fileWriteString(title1, stream); + + int killType = 0; + for (int skill = 0; skill < SKILL_COUNT; skill++) { + sprintf(title1, "%s ", skillGetName(skill)); + + // NOTE: Uninline. + _AddDots(title1 + strlen(title1), 16 - strlen(title1)); + + bool hasKillType = false; + + for (; killType < KILL_TYPE_COUNT; killType++) { + int killsCount = killsGetByType(killType); + if (killsCount > 0) { + sprintf(title2, "%s ", killTypeGetName(killType)); + + // NOTE: Uninline. + _AddDots(title2 + strlen(title2), 16 - strlen(title2)); + + sprintf(title3, + " %s %.3d%% %s %.3d\n", + title1, + skillGetValue(gDude, skill), + title2, + killsCount); + hasKillType = true; + break; + } + } + + if (!hasKillType) { + sprintf(title3, + " %s %.3d%%\n", + title1, + skillGetValue(gDude, skill)); + } + } + + fileWriteString("\n", stream); + fileWriteString("\n", stream); + + // ::: Inventory ::: + sprintf(title1, "%s\n", getmsg(&editorMessageList, &editorMessageListItem, 654)); + fileWriteString(title1, stream); + + Inventory* inventory = &(gDude->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), + objectGetName(inventoryItem->item)); + + int length = 25 - strlen(title2); + if (length < 0) { + length = 0; + } + + _AddSpaces(title2, length); + + strcat(title1, title2); + } + + strcat(title1, "\n"); + fileWriteString(title1, stream); + } + + fileWriteString("\n", stream); + + // Total Weight: + sprintf(title1, + "%s %d lbs.", + getmsg(&editorMessageList, &editorMessageListItem, 655), + objectGetInventoryWeight(gDude)); + fileWriteString(title1, stream); + + fileWriteString("\n", stream); + fileWriteString("\n", stream); + fileWriteString("\n", stream); + fileClose(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 +char* _AddDots(char* string, int length) +{ + char* pch = string + strlen(string); + + for (int index = 0; index < length; index++) { + *pch++ = '.'; + } + + *pch = '\0'; + + return string; +} + +// 0x43A4BC +void _ResetScreen() +{ + characterEditorSelectedItem = 0; + _skill_cursor = 0; + _slider_y = 27; + characterEditorWindowSelectedFolder = 0; + + if (gCharacterEditorIsCreationMode) { + characterEditorRenderBigNumber(126, 282, 0, characterEditorRemainingCharacterPoints, 0, characterEditorWindowHandle); + } else { + editorRenderFolders(); + editorRenderPcStats(); + } + + editorRenderName(); + editorRenderAge(); + editorRenderGender(); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPrimaryStat(7, 0, 0); + editorRenderSecondaryStats(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); +} + +// 0x43A5BC +void _RegInfoAreas() +{ + buttonCreate(characterEditorWindowHandle, 19, 38, 125, 227, -1, -1, 525, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 28, 280, 124, 32, -1, -1, 526, -1, NULL, NULL, NULL, 0); + + if (gCharacterEditorIsCreationMode) { + buttonCreate(characterEditorWindowHandle, 52, 324, 169, 20, -1, -1, 533, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 47, 353, 245, 100, -1, -1, 534, -1, NULL, NULL, NULL, 0); + } else { + buttonCreate(characterEditorWindowHandle, 28, 363, 283, 105, -1, -1, 527, -1, NULL, NULL, NULL, 0); + } + + buttonCreate(characterEditorWindowHandle, 191, 41, 122, 110, -1, -1, 528, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 191, 175, 122, 135, -1, -1, 529, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 376, 5, 223, 20, -1, -1, 530, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 370, 27, 223, 195, -1, -1, 531, -1, NULL, NULL, NULL, 0); + buttonCreate(characterEditorWindowHandle, 396, 228, 171, 25, -1, -1, 532, -1, NULL, NULL, NULL, 0); +} + +// copy character to editor +// +// 0x43A7DC +void _SavePlayer() +{ + Proto* proto; + protoGetProto(gDude->pid, &proto); + critterProtoDataCopy(&_dude_data, &(proto->critter.data)); + + _hp_back = critterGetHitPoints(gDude); + + strncpy(_name_save, critterGetName(gDude), 32); + + _last_level_back = _last_level; + for (int perk = 0; perk < PERK_COUNT; perk++) { + _perk_back[perk] = perkGetRank(gDude, perk); + } + + _free_perk_back = _free_perk; + + _upsent_points_back = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + + skillsGetTagged(_tag_skill_back, NUM_TAGGED_SKILLS); + + traitsGetSelected(&(_trait_back[0]), &(_trait_back[1])); + + for (int skill = 0; skill < SKILL_COUNT; skill++) { + _skillsav[skill] = skillGetValue(gDude, skill); + } +} + +// copy editor to character +// +// 0x43A8BC +void _RestorePlayer() +{ + Proto* proto; + int i; + int v3; + int cur_hp; + + _pop_perks(); + + protoGetProto(gDude->pid, &proto); + critterProtoDataCopy(&(proto->critter.data), &_dude_data); + + dudeSetName(_name_save); + + _last_level = _last_level_back; + _free_perk = _free_perk_back; + + pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, _upsent_points_back); + + skillsSetTagged(_tag_skill_back, NUM_TAGGED_SKILLS); + + traitsSetSelected(_trait_back[0], _trait_back[1]); + + skillsGetTagged(_temp_tag_skill, NUM_TAGGED_SKILLS); + + i = 4; + v3 = 0; + for (v3 = 0; v3 < 4; v3++) { + if (_temp_tag_skill[--i] != -1) { + break; + } + } + + if (gCharacterEditorIsCreationMode == 1) { + v3 -= gCharacterEditorIsCreationMode; + } + + _tagskill_count = v3; + + traitsGetSelected(&(_temp_trait[0]), &(_temp_trait[1])); + + i = 2; + v3 = 0; + for (v3 = 0; v3 < 2; v3++) { + if (_temp_trait[v3] != -1) { + break; + } + } + + _trait_count = v3; + + critterUpdateDerivedStats(gDude); + + cur_hp = critterGetHitPoints(gDude); + critterAdjustHitPoints(gDude, _hp_back - cur_hp); +} + +// 0x43A9CC +char* _itostndn(int value, char* dest) +{ + int v16[7]; + static_assert(sizeof(v16) == sizeof(dword_431DD4), "wrong size"); + memcpy(v16, dword_431DD4, sizeof(v16)); + + 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 +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 = buildFid(10, graphicId, 0, 0, 0); + buf = artLockFrameDataReturningSize(fid, &graphicHandle, &(size.width), &(size.height)); + if (buf == NULL) { + return -1; + } + + blitBufferToBuffer(buf, size.width, size.height, size.width, characterEditorWindowBuf + 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; + } + + fontSetCurrent(102); + + fontDrawText(characterEditorWindowBuf + 640 * 272 + 348, name, 640, 640, _colorTable[0]); + int nameFontLineHeight = fontGetLineHeight(); + if (attributes != NULL) { + int nameWidth = fontGetStringWidth(name); + + fontSetCurrent(101); + int attributesFontLineHeight = fontGetLineHeight(); + fontDrawText(characterEditorWindowBuf + 640 * (268 + nameFontLineHeight - attributesFontLineHeight) + 348 + nameWidth + 8, attributes, 640, 640, _colorTable[0]); + } + + y = nameFontLineHeight; + windowDrawLine(characterEditorWindowHandle, 348, y + 272, 613, y + 272, _colorTable[0]); + windowDrawLine(characterEditorWindowHandle, 348, y + 273, 613, y + 273, _colorTable[0]); + + fontSetCurrent(101); + + int descriptionFontLineHeight = fontGetLineHeight(); + + if (wordWrap(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'; + fontDrawText(characterEditorWindowBuf + 640 * y + 348, description + beginning, 640, 640, _colorTable[0]); + description[ending] = c; + y += descriptionFontLineHeight; + } + + if ((graphicId != _card_old_fid1 || strcmp(name, _old_str1) != 0) && _frstc_draw1) { + soundPlayFile("isdxxxx1"); + } + + strcpy(_old_str1, name); + + _card_old_fid1 = graphicId; + _frstc_draw1 = 1; + + artUnlock(graphicHandle); + + return 0; +} + +// 0x43AE8 +void _FldrButton() +{ + mouseGetPosition(&_mouse_xpos, &_mouse_ypos); + soundPlayFile("ib3p1xx1"); + + if (_mouse_xpos >= 208) { + characterEditorSelectedItem = 41; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_KILLS; + } else if (_mouse_xpos > 110) { + characterEditorSelectedItem = 42; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_KARMA; + } else { + characterEditorSelectedItem = 40; + characterEditorWindowSelectedFolder = EDITOR_FOLDER_PERKS; + } + + editorRenderFolders(); + editorRenderDetails(); +} + +// 0x43AF40 +void _InfoButton(int eventCode) +{ + mouseGetPosition(&_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) { + characterEditorSelectedItem = index; + break; + } + } + } + break; + case 526: + if (gCharacterEditorIsCreationMode) { + characterEditorSelectedItem = 7; + } else { + int offset = _mouse_ypos - 280; + if (offset < 0) { + offset = 0; + } + + characterEditorSelectedItem = offset / 10 + 7; + } + break; + case 527: + if (!gCharacterEditorIsCreationMode) { + fontSetCurrent(101); + int offset = _mouse_ypos - 364; + if (offset < 0) { + offset = 0; + } + characterEditorSelectedItem = offset / (fontGetLineHeight() + 1) + 10; + } + break; + case 528: + if (1) { + int offset = _mouse_ypos - 41; + if (offset < 0) { + offset = 0; + } + + characterEditorSelectedItem = offset / 13 + 43; + } + break; + case 529: { + int offset = _mouse_ypos - 175; + if (offset < 0) { + offset = 0; + } + + characterEditorSelectedItem = offset / 13 + 51; + break; + } + case 530: + characterEditorSelectedItem = 80; + break; + case 531: + if (1) { + int offset = _mouse_ypos - 27; + if (offset < 0) { + offset = 0; + } + + _skill_cursor = offset * 0.092307694; + if (_skill_cursor >= 18) { + _skill_cursor = 17; + } + + characterEditorSelectedItem = _skill_cursor + 61; + } + break; + case 532: + characterEditorSelectedItem = 79; + break; + case 533: + characterEditorSelectedItem = 81; + break; + case 534: + if (1) { + fontSetCurrent(101); + + // TODO: Original code is slightly different. + double mouseY = _mouse_ypos; + double fontLineHeight = fontGetLineHeight(); + double y = 353.0; + double step = fontGetLineHeight() + 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; + } + + characterEditorSelectedItem = index + 82; + if (_mouse_xpos >= 169) { + characterEditorSelectedItem += 8; + } + } + break; + } + + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + editorRenderPcStats(); + editorRenderFolders(); + editorRenderSecondaryStats(); + editorRenderDetails(); +} + +// 0x43B230 +void editorAdjustSkill(int keyCode) +{ + if (gCharacterEditorIsCreationMode) { + return; + } + + int unspentSp = pcGetStat(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 <= dbl_5018F0) { + repeatDelay++; + } + + if (repeatDelay == 1 || repeatDelay > dbl_5018F0) { + if (repeatDelay > dbl_5018F0) { + _repFtime++; + if (_repFtime > 24) { + _repFtime = 24; + } + } + + rc = 1; + if (keyCode == 521) { + if (pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS) > 0) { + if (skillAdd(gDude, _skill_cursor) == -3) { + soundPlayFile("iisxxxx1"); + + sprintf(title, "%s:", skillGetName(_skill_cursor)); + // At maximum level. + strcpy(body1, getmsg(&editorMessageList, &editorMessageListItem, 132)); + // Unable to increment it. + strcpy(body2, getmsg(&editorMessageList, &editorMessageListItem, 133)); + showDialogBox(title, body, 2, 192, 126, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + rc = -1; + } + } else { + soundPlayFile("iisxxxx1"); + + // Not enough skill points available. + strcpy(title, getmsg(&editorMessageList, &editorMessageListItem, 136)); + showDialogBox(title, NULL, 0, 192, 126, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + rc = -1; + } + } else if (keyCode == 523) { + if (skillGetValue(gDude, _skill_cursor) <= _skillsav[_skill_cursor]) { + rc = 0; + } else { + if (skillSub(gDude, _skill_cursor) == -2) { + rc = 0; + } + } + + if (rc == 0) { + soundPlayFile("iisxxxx1"); + + sprintf(title, "%s:", skillGetName(_skill_cursor)); + // At minimum level. + strcpy(body1, getmsg(&editorMessageList, &editorMessageListItem, 134)); + // Unable to decrement it. + strcpy(body2, getmsg(&editorMessageList, &editorMessageListItem, 135)); + showDialogBox(title, body, 2, 192, 126, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + rc = -1; + } + } + + characterEditorSelectedItem = _skill_cursor + 61; + editorRenderDetails(); + editorRenderSkills(1); + + int flags; + if (rc == 1) { + flags = ANIMATE; + } else { + flags = 0; + } + + characterEditorRenderBigNumber(522, 228, flags, pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS), unspentSp, characterEditorWindowHandle); + + windowRefresh(characterEditorWindowHandle); + } + + if (!isUsingKeyboard) { + unspentSp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + if (repeatDelay >= dbl_5018F0) { + while (getTicksSince(_frame_time) < 1000 / _repFtime) { + } + } else { + while (getTicksSince(_frame_time) < 1000 / 24) { + } + } + + int keyCode = _get_input(); + if (keyCode != 522 && keyCode != 524 && rc != -1) { + continue; + } + } + return; + } +} + +// 0x43B67C +void characterEditorToggleTaggedSkill(int skill) +{ + int insertionIndex; + + insertionIndex = 0; + for (int index = 3; index >= 0; index--) { + if (_temp_tag_skill[index] != -1) { + break; + } + insertionIndex++; + } + + if (gCharacterEditorIsCreationMode) { + insertionIndex -= 1; + } + + _old_tags = insertionIndex; + + 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 { + soundPlayFile("iisxxxx1"); + + char line1[128]; + strcpy(line1, getmsg(&editorMessageList, &editorMessageListItem, 140)); + + char line2[128]; + strcpy(line2, getmsg(&editorMessageList, &editorMessageListItem, 141)); + + const char* lines[] = { line2 }; + showDialogBox(line1, lines, 1, 192, 126, _colorTable[32328], 0, _colorTable[32328], 0); + } + } + + insertionIndex = 0; + for (int index = 3; index >= 0; index--) { + if (_temp_tag_skill[index] != -1) { + break; + } + insertionIndex++; + } + + if (gCharacterEditorIsCreationMode) { + insertionIndex -= 1; + } + + _tagskill_count = insertionIndex; + + characterEditorSelectedItem = skill + 61; + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderSecondaryStats(); + editorRenderSkills(2); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); +} + +// 0x43B8A8 +void characterEditorWindowRenderTraits() +{ + int v0 = -1; + int i; + int color; + const char* traitName; + double step; + double y; + + if (gCharacterEditorIsCreationMode != 1) { + return; + } + + if (characterEditorSelectedItem >= 82 && characterEditorSelectedItem < 98) { + v0 = characterEditorSelectedItem - 82; + } + + blitBufferToBuffer(characterEditorWindowBackgroundBuf + 640 * 353 + 47, 245, 100, 640, characterEditorWindowBuf + 640 * 353 + 47, 640); + + fontSetCurrent(101); + + traitsSetSelected(_temp_trait[0], _temp_trait[1]); + + step = fontGetLineHeight() + 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 = traitGetFrmId(i); + _folder_card_title = traitGetName(i); + _folder_card_title2 = NULL; + _folder_card_desc = traitGetDescription(i); + } else { + if (i != _temp_trait[0] && i != _temp_trait[1]) { + color = _colorTable[992]; + } else { + color = _colorTable[21140]; + } + } + + traitName = traitGetName(i); + fontDrawText(characterEditorWindowBuf + 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 = traitGetFrmId(i); + _folder_card_title = traitGetName(i); + _folder_card_title2 = NULL; + _folder_card_desc = traitGetDescription(i); + } else { + if (i != _temp_trait[0] && i != _temp_trait[1]) { + color = _colorTable[992]; + } else { + color = _colorTable[21140]; + } + } + + traitName = traitGetName(i); + fontDrawText(characterEditorWindowBuf + 640 * (int)y + 199, traitName, 640, 640, color); + y += step; + } +} + +// 0x43BB0C +void characterEditorToggleOptionalTrait(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) { + soundPlayFile("iisxxxx1"); + + char line1[128]; + strcpy(line1, getmsg(&editorMessageList, &editorMessageListItem, 148)); + + char line2[128]; + strcpy(line2, getmsg(&editorMessageList, &editorMessageListItem, 149)); + + const char* lines = { line2 }; + showDialogBox(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; + } + } + } + } + + _trait_count = 0; + for (int index = 1; index != 0; index--) { + if (_temp_trait[index] != -1) { + break; + } + _trait_count++; + } + + characterEditorSelectedItem = trait + EDITOR_FIRST_TRAIT; + + characterEditorWindowRenderTraits(); + editorRenderSkills(0); + critterUpdateDerivedStats(gDude); + characterEditorRenderBigNumber(126, 282, 0, characterEditorRemainingCharacterPoints, 0, characterEditorWindowHandle); + editorRenderPrimaryStat(RENDER_ALL_STATS, false, 0); + editorRenderSecondaryStats(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); +} + +// 0x43BCE0 +void editorRenderKarma() +{ + char* msg; + char formattedText[256]; + + _folder_clear(); + + bool hasSelection = false; + for (int index = 0; index < gKarmaEntriesLength; index++) { + KarmaEntry* karmaDescription = &(gKarmaEntries[index]); + if (karmaDescription->gvar == GVAR_PLAYER_REPUTATION) { + int reputation; + for (reputation = 0; reputation < gGenericReputationEntriesLength; reputation++) { + GenericReputationEntry* reputationDescription = &(gGenericReputationEntries[reputation]); + if (gGameGlobalVars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) { + break; + } + } + + if (reputation != gGenericReputationEntriesLength) { + GenericReputationEntry* reputationDescription = &(gGenericReputationEntries[reputation]); + + char reputationValue[32]; + itoa(gGameGlobalVars[GVAR_PLAYER_REPUTATION], reputationValue, 10); + + sprintf(formattedText, + "%s: %s (%s)", + getmsg(&editorMessageList, &editorMessageListItem, 125), + reputationValue, + getmsg(&editorMessageList, &editorMessageListItem, reputationDescription->name)); + + if (_folder_print_line(formattedText)) { + _folder_card_fid = karmaDescription->art_num; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 125); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, karmaDescription->description); + hasSelection = true; + } + } + } else { + if (gGameGlobalVars[karmaDescription->gvar] != 0) { + msg = getmsg(&editorMessageList, &editorMessageListItem, karmaDescription->name); + if (_folder_print_line(msg)) { + _folder_card_fid = karmaDescription->art_num; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, karmaDescription->name); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, karmaDescription->description); + hasSelection = true; + } + } + } + } + + bool hasTownReputationHeading = false; + for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) { + const TownReputationEntry* pair = &(gTownReputationEntries[index]); + if (_wmAreaIsKnown(pair->city)) { + if (!hasTownReputationHeading) { + msg = getmsg(&editorMessageList, &editorMessageListItem, 4000); + if (_folder_print_seperator(msg)) { + _folder_card_fid = 48; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 4000); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 4100); + } + hasTownReputationHeading = true; + } + + char cityShortName[40]; + _wmGetAreaIdxName(pair->city, cityShortName); + + int townReputation = gGameGlobalVars[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(&editorMessageList, &editorMessageListItem, townReputationBaseMessageId); + sprintf(formattedText, + "%s: %s", + cityShortName, + msg); + + if (_folder_print_line(formattedText)) { + _folder_card_fid = townReputationGraphicId; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, townReputationBaseMessageId); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, townReputationBaseMessageId + 100); + hasSelection = 1; + } + } + } + + bool hasAddictionsHeading = false; + for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) { + if (gGameGlobalVars[gAddictionReputationVars[index]] != 0) { + if (!hasAddictionsHeading) { + // Addictions + msg = getmsg(&editorMessageList, &editorMessageListItem, 4001); + if (_folder_print_seperator(msg)) { + _folder_card_fid = 53; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 4001); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 4101); + hasSelection = 1; + } + hasAddictionsHeading = true; + } + + msg = getmsg(&editorMessageList, &editorMessageListItem, 1004 + index); + if (_folder_print_line(msg)) { + _folder_card_fid = gAddictionReputationFrmIds[index]; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 1004 + index); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 1104 + index); + hasSelection = 1; + } + } + } + + if (!hasSelection) { + _folder_card_fid = 47; + _folder_card_title = getmsg(&editorMessageList, &editorMessageListItem, 125); + _folder_card_title2 = NULL; + _folder_card_desc = getmsg(&editorMessageList, &editorMessageListItem, 128); + } +} + +// 0x43C1B0 +int _editor_save(File* stream) +{ + if (fileWriteInt32(stream, _last_level) == -1) + return -1; + if (fileWriteUInt8(stream, _free_perk) == -1) + return -1; + + return 0; +} + +// 0x43C1E0 +int _editor_load(File* stream) +{ + if (fileReadInt32(stream, &_last_level) == -1) + return -1; + if (fileReadUInt8(stream, &_free_perk) == -1) + return -1; + + return 0; +} + +// 0x43C20C +void _editor_reset() +{ + characterEditorRemainingCharacterPoints = 5; + _last_level = 1; +} + +// level up if needed +// +// 0x43C228 +int _UpdateLevel() +{ + int level = pcGetStat(PC_STAT_LEVEL); + if (level != _last_level && level <= PC_LEVEL_MAX) { + for (int nextLevel = _last_level + 1; nextLevel <= level; nextLevel++) { + int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + sp += 5; + sp += critterGetBaseStatWithTraitModifier(gDude, STAT_INTELLIGENCE) * 2; + sp += perkGetRank(gDude, PERK_EDUCATED) * 2; + sp += traitIsSelected(TRAIT_SKILLED) * 5; + if (traitIsSelected(TRAIT_GIFTED)) { + sp -= 5; + if (sp < 0) { + sp = 0; + } + } + if (sp > 99) { + sp = 99; + } + + pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp); + + int selectedPerksCount = 0; + for (int perk = 0; perk < PERK_COUNT; perk++) { + if (perkGetRank(gDude, perk) != 0) { + selectedPerksCount += 1; + if (selectedPerksCount >= 37) { + break; + } + } + } + + if (selectedPerksCount < 37) { + int progression = 3; + if (traitIsSelected(TRAIT_SKILLED)) { + progression += 1; + } + + if (nextLevel % progression == 0) { + _free_perk = 1; + } + } + } + } + + if (_free_perk != 0) { + characterEditorWindowSelectedFolder = 0; + editorRenderFolders(); + windowRefresh(characterEditorWindowHandle); + + int rc = editorSelectPerk(); + if (rc == -1) { + debugPrint("\n *** Error running perks dialog! ***\n"); + return -1; + } else if (rc == 0) { + editorRenderFolders(); + } else if (rc == 1) { + editorRenderFolders(); + _free_perk = 0; + } + } + + _last_level = level; + + return 1; +} + +// 0x43C398 +void _RedrwDPrks() +{ + blitBufferToBuffer( + gEditorPerkBackgroundBuffer + 280, + 293, + PERK_WINDOW_HEIGHT, + PERK_WINDOW_WIDTH, + gEditorPerkWindowBuffer + 280, + PERK_WINDOW_WIDTH); + + _ListDPerks(); + + // NOTE: Original code is slightly different, but basically does the same thing. + int perk = _name_sort_list[_crow + _cline].field_0; + int perkFrmId = perkGetFrmId(perk); + char* perkName = perkGetName(perk); + char* perkDescription = perkGetDescription(perk); + char* perkRank = NULL; + char perkRankBuffer[32]; + + int rank = perkGetRank(gDude, perk); + if (rank != 0) { + sprintf(perkRankBuffer, "(%d)", rank); + perkRank = perkRankBuffer; + } + + _DrawCard2(perkFrmId, perkName, perkRank, perkDescription); + + windowRefresh(gEditorPerkWindow); +} + +// 0x43C4F0 +int editorSelectPerk() +{ + _crow = 0; + _cline = 0; + _card_old_fid2 = -1; + _old_str2[0] = '\0'; + _frstc_draw2 = 0; + + CacheEntry* backgroundFrmHandle; + int backgroundWidth; + int backgroundHeight; + int fid = buildFid(6, 86, 0, 0, 0); + gEditorPerkBackgroundBuffer = artLockFrameDataReturningSize(fid, &backgroundFrmHandle, &backgroundWidth, &backgroundHeight); + if (gEditorPerkBackgroundBuffer == NULL) { + debugPrint("\n *** Error running perks dialog window ***\n"); + return -1; + } + + gEditorPerkWindow = windowCreate(PERK_WINDOW_X, PERK_WINDOW_Y, PERK_WINDOW_WIDTH, PERK_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (gEditorPerkWindow == -1) { + artUnlock(backgroundFrmHandle); + debugPrint("\n *** Error running perks dialog window ***\n"); + return -1; + } + + gEditorPerkWindowBuffer = windowGetBuffer(gEditorPerkWindow); + memcpy(gEditorPerkWindowBuffer, gEditorPerkBackgroundBuffer, PERK_WINDOW_WIDTH * PERK_WINDOW_HEIGHT); + + int btn; + + btn = buttonCreate(gEditorPerkWindow, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate(gEditorPerkWindow, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate(gEditorPerkWindow, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, NULL); + } + + btn = buttonCreate(gEditorPerkWindow, + 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) { + buttonSetCallbacks(btn, _gsound_red_butt_press, NULL); + } + + buttonCreate(gEditorPerkWindow, + 45, + 43, + 192, + 129, + -1, + -1, + -1, + 501, + NULL, + NULL, + NULL, + BUTTON_FLAG_TRANSPARENT); + + fontSetCurrent(103); + + const char* msg; + + // PICK A NEW PERK + msg = getmsg(&editorMessageList, &editorMessageListItem, 152); + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[18979]); + + // DONE + msg = getmsg(&editorMessageList, &editorMessageListItem, 100); + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 186 + 69, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[18979]); + + // CANCEL + msg = getmsg(&editorMessageList, &editorMessageListItem, 102); + fontDrawText(gEditorPerkWindowBuffer + 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].field_0; + int perkFrmId = perkGetFrmId(perk); + char* perkName = perkGetName(perk); + char* perkDescription = perkGetDescription(perk); + char* perkRank = NULL; + char perkRankBuffer[32]; + + int rank = perkGetRank(gDude, perk); + if (rank != 0) { + sprintf(perkRankBuffer, "(%d)", rank); + perkRank = perkRankBuffer; + } + + _DrawCard2(perkFrmId, perkName, perkRank, perkDescription); + windowRefresh(gEditorPerkWindow); + + int rc = _InputPDLoop(count, _RedrwDPrks); + + if (rc == 1) { + if (perkAdd(gDude, _name_sort_list[_crow + _cline].field_0) == -1) { + debugPrint("\n*** Unable to add perk! ***\n"); + rc = 2; + } + } + + rc &= 1; + + if (rc != 0) { + if (perkGetRank(gDude, PERK_TAG) != 0 && _perk_back[PERK_TAG] == 0) { + if (!editorHandleTag()) { + perkRemove(gDude, PERK_TAG); + } + } else if (perkGetRank(gDude, PERK_MUTATE) != 0 && _perk_back[PERK_MUTATE] == 0) { + if (!editorHandleMutate()) { + perkRemove(gDude, PERK_MUTATE); + } + } else if (perkGetRank(gDude, PERK_LIFEGIVER) != _perk_back[PERK_LIFEGIVER]) { + int maxHp = critterGetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS); + critterSetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS, maxHp + 4); + critterAdjustHitPoints(gDude, 4); + } else if (perkGetRank(gDude, PERK_EDUCATED) != _perk_back[PERK_EDUCATED]) { + int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2); + } + } + + editorRenderSkills(0); + editorRenderPrimaryStat(RENDER_ALL_STATS, 0, 0); + editorRenderPcStats(); + editorRenderSecondaryStats(); + editorRenderFolders(); + editorRenderDetails(); + windowRefresh(characterEditorWindowHandle); + + artUnlock(backgroundFrmHandle); + + windowDestroy(gEditorPerkWindow); + + return rc; +} + +// 0x43CACC +int _InputPDLoop(int count, void (*refreshProc)()) +{ + fontSetCurrent(101); + + int v3 = count - 11; + + int height = fontGetLineHeight(); + _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) { + soundPlayFile("ib1p1xx1"); + rc = 1; + } else if (keyCode == 501) { + mouseGetPosition(&_mouse_xpos, &_mouse_ypos); + _cline = (_mouse_ypos - 134) / v16; + if ((_mouse_ypos - 134) / v16 >= 0) { + if (count - 1 < (_mouse_ypos - 134) / v16) + _cline = count - 1; + } else { + _cline = 0; + } + + if (_cline == _oldsline) { + soundPlayFile("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 <= dbl_5019BE) { + v19++; + } + + if (v19 == 1 || v19 > dbl_5019BE) { + if (v19 > dbl_5019BE) { + _repFtime++; + if (_repFtime > 24) { + _repFtime = 24; + } + } + + _crow--; + if (_crow < 0) { + _crow = 0; + + _cline--; + if (_cline < 0) { + _cline = 0; + } + } + refreshProc(); + } + + if (v19 < dbl_5019BE) { + while (getTicksSince(_frame_time) < 1000 / 24) { + } + } else { + while (getTicksSince(_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 <= dbl_5019BE) { + v19++; + } + + if (v19 == 1 || v19 > dbl_5019BE) { + if (v19 > dbl_5019BE) { + _repFtime++; + if (_repFtime > 24) { + _repFtime = 24; + } + } + + _crow++; + if (_crow > count - 11) { + _crow = count - 11; + + _cline++; + if (_cline > 10) { + _cline = 10; + } + } + + refreshProc(); + } + + if (v19 < dbl_5019BE) { + while (getTicksSince(_frame_time) < 1000 / 24) { + } + } else { + while (getTicksSince(_frame_time) < 1000 / _repFtime) { + } + } + } while (_get_input() != 575); + } else { + do { + _frame_time = _get_time(); + if (v19 <= dbl_5019BE) { + v19++; + } + + if (v19 == 1 || v19 > dbl_5019BE) { + if (v19 > dbl_5019BE) { + _repFtime++; + if (_repFtime > 24) { + _repFtime = 24; + } + } + + _cline++; + if (_cline > count - 1) { + _cline = count - 1; + } + + refreshProc(); + } + + if (v19 < dbl_5019BE) { + while (getTicksSince(_frame_time) < 1000 / 24) { + } + } else { + while (getTicksSince(_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 (getTicksSince(_frame_time) > 700) { + _frame_time = _get_time(); + _oldsline = -2; + } + break; + } + } + } + + return rc; +} + +// 0x43D0BC +int _ListDPerks() +{ + blitBufferToBuffer( + gEditorPerkBackgroundBuffer + PERK_WINDOW_WIDTH * 43 + 45, + 192, + 129, + PERK_WINDOW_WIDTH, + gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 43 + 45, + PERK_WINDOW_WIDTH); + + fontSetCurrent(101); + + int perks[PERK_COUNT]; + int count = perkGetAvailablePerks(gDude, perks); + if (count == 0) { + return 0; + } + + for (int perk = 0; perk < PERK_COUNT; perk++) { + _name_sort_list[perk].field_0 = 0; + _name_sort_list[perk].field_4 = NULL; + } + + for (int index = 0; index < count; index++) { + _name_sort_list[index].field_0 = perks[index]; + _name_sort_list[index].field_4 = perkGetName(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 = fontGetLineHeight() + 2; + for (int index = _crow; index < v16; index++) { + int color; + if (index == _crow + _cline) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 45, _name_sort_list[index].field_4, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); + + if (perkGetRank(gDude, _name_sort_list[index].field_0) != 0) { + char rankString[256]; + sprintf(rankString, "(%d)", perkGetRank(gDude, _name_sort_list[index].field_0)); + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 207, rankString, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); + } + + y += yStep; + } + + return count; +} + +// 0x43D2F8 +void _RedrwDMPrk() +{ + blitBufferToBuffer(gEditorPerkBackgroundBuffer + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + 280, PERK_WINDOW_WIDTH); + + _ListMyTraits(_optrt_count); + + char* traitName = _name_sort_list[_crow + _cline].field_4; + char* tratDescription = traitGetDescription(_name_sort_list[_crow + _cline].field_0); + int frmId = traitGetFrmId(_name_sort_list[_crow + _cline].field_0); + _DrawCard2(frmId, traitName, NULL, tratDescription); + + windowRefresh(gEditorPerkWindow); +} + +// 0x43D38C +bool editorHandleMutate() +{ + _card_old_fid2 = -1; + _old_str2[0] = '\0'; + _frstc_draw2 = 0; + + int traitCount = TRAITS_MAX_SELECTED_COUNT - 1; + int traitIndex = 0; + while (traitCount >= 0) { + if (_temp_trait[traitIndex] != -1) { + break; + } + traitCount--; + traitIndex++; + } + + _trait_count = TRAITS_MAX_SELECTED_COUNT - traitIndex; + + bool result = true; + if (_trait_count >= 1) { + fontSetCurrent(103); + + blitBufferToBuffer(gEditorPerkBackgroundBuffer + PERK_WINDOW_WIDTH * 14 + 49, 206, fontGetLineHeight() + 2, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH); + + // LOSE A TRAIT + char* msg = getmsg(&editorMessageList, &editorMessageListItem, 154); + fontDrawText(gEditorPerkWindowBuffer + 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].field_0 == _temp_trait[0]) { + _temp_trait[0] = _temp_trait[1]; + _temp_trait[1] = -1; + } else { + _temp_trait[1] = -1; + } + } + } else { + if (_name_sort_list[0].field_0 == _temp_trait[0]) { + _temp_trait[1] = -1; + } else { + _temp_trait[0] = _temp_trait[1]; + _temp_trait[1] = -1; + } + } + } else { + result = false; + } + } + + if (result) { + fontSetCurrent(103); + + blitBufferToBuffer(gEditorPerkBackgroundBuffer + PERK_WINDOW_WIDTH * 14 + 49, 206, fontGetLineHeight() + 2, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH); + + // PICK A NEW TRAIT + char* msg = getmsg(&editorMessageList, &editorMessageListItem, 153); + fontDrawText(gEditorPerkWindowBuffer + 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].field_0; + } else { + _temp_trait[0] = _name_sort_list[_cline + _crow].field_0; + _temp_trait[1] = -1; + } + + traitsSetSelected(_temp_trait[0], _temp_trait[1]); + } else { + result = false; + } + } + + if (!result) { + memcpy(_temp_trait, _trait_back, sizeof(_temp_trait)); + } + + return result; +} + +// 0x43D668 +void _RedrwDMTagSkl() +{ + blitBufferToBuffer(gEditorPerkBackgroundBuffer + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + 280, PERK_WINDOW_WIDTH); + + _ListNewTagSkills(); + + char* name = _name_sort_list[_crow + _cline].field_4; + char* description = skillGetDescription(_name_sort_list[_crow + _cline].field_0); + int frmId = skillGetFrmId(_name_sort_list[_crow + _cline].field_0); + _DrawCard2(frmId, name, NULL, description); + + windowRefresh(gEditorPerkWindow); +} + +// 0x43D6F8 +bool editorHandleTag() +{ + fontSetCurrent(103); + + blitBufferToBuffer(gEditorPerkBackgroundBuffer + 573 * 14 + 49, 206, fontGetLineHeight() + 2, 573, gEditorPerkWindowBuffer + 573 * 15 + 49, 573); + + // PICK A NEW TAG SKILL + char* messageListItemText = getmsg(&editorMessageList, &editorMessageListItem, 155); + fontDrawText(gEditorPerkWindowBuffer + 573 * 16 + 49, messageListItemText, 573, 573, _colorTable[18979]); + + _cline = 0; + _crow = 0; + _card_old_fid2 = -1; + _old_str2[0] = '\0'; + _frstc_draw2 = 0; + _RedrwDMTagSkl(); + + int rc = _InputPDLoop(_optrt_count, _RedrwDMTagSkl); + if (rc != 1) { + memcpy(_temp_tag_skill, _tag_skill_back, sizeof(_temp_tag_skill)); + skillsSetTagged(_tag_skill_back, NUM_TAGGED_SKILLS); + return false; + } + + _temp_tag_skill[3] = _name_sort_list[_crow + _cline].field_0; + skillsSetTagged(_temp_tag_skill, NUM_TAGGED_SKILLS); + + return true; +} + +// 0x43D81C +void _ListNewTagSkills() +{ + blitBufferToBuffer(gEditorPerkBackgroundBuffer + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH); + + fontSetCurrent(101); + + _optrt_count = 0; + + int y = 43; + int yStep = fontGetLineHeight() + 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].field_0 = skill; + _name_sort_list[_optrt_count].field_4 = skillGetName(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]; + } + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 45, _name_sort_list[index].field_4, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); + y += yStep; + } +} + +// 0x43D960 +int _ListMyTraits(int a1) +{ + blitBufferToBuffer(gEditorPerkBackgroundBuffer + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH); + + fontSetCurrent(101); + + int y = 43; + int yStep = fontGetLineHeight() + 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].field_0 = trait; + _name_sort_list[count].field_4 = traitGetName(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]; + } + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 45, _name_sort_list[index].field_4, 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].field_0 = _temp_trait[index]; + _name_sort_list[index].field_4 = traitGetName(_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]; + } + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 45, _name_sort_list[index].field_4, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); + y += yStep; + } + } + return 0; +} + +// 0x43DB48 +int _name_sort_comp(const void* a1, const void* a2) +{ + STRUCT_56FCB0* v1 = (STRUCT_56FCB0*)a1; + STRUCT_56FCB0* v2 = (STRUCT_56FCB0*)a2; + return strcmp(v1->field_4, v2->field_4); +} + +// 0x43DB54 +int _DrawCard2(int frmId, const char* name, const char* rank, char* description) +{ + int fid = buildFid(10, frmId, 0, 0, 0); + + CacheEntry* handle; + int width; + int height; + unsigned char* data = artLockFrameDataReturningSize(fid, &handle, &width, &height); + if (data == NULL) { + return -1; + } + + blitBufferToBuffer(data, width, height, width, gEditorPerkWindowBuffer + 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; + } + + fontSetCurrent(102); + int nameHeight = fontGetLineHeight(); + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * 27 + 280, name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[0]); + + if (rank != NULL) { + int rankX = fontGetStringWidth(name) + 280 + 8; + fontSetCurrent(101); + + int rankHeight = fontGetLineHeight(); + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * (23 + nameHeight - rankHeight) + rankX, rank, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[0]); + } + + windowDrawLine(gEditorPerkWindow, 280, 27 + nameHeight, 545, 27 + nameHeight, _colorTable[0]); + windowDrawLine(gEditorPerkWindow, 280, 28 + nameHeight, 545, 28 + nameHeight, _colorTable[0]); + + fontSetCurrent(101); + + int yStep = fontGetLineHeight() + 1; + int y = 70; + + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(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'; + + fontDrawText(gEditorPerkWindowBuffer + PERK_WINDOW_WIDTH * y + 280, beginning, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[0]); + + *ending = ch; + + y += yStep; + } + + if (frmId != _card_old_fid2 || strcmp(_old_str2, name) != 0) { + if (_frstc_draw2) { + soundPlayFile("isdxxxx1"); + } + } + + strcpy(_old_str2, name); + + _card_old_fid2 = frmId; + _frstc_draw2 = 1; + artUnlock(handle); + + return 0; +} + +// copy editor perks to character +// +// 0x43DEBC +void _pop_perks() +{ + for (int perk = 0; perk < PERK_COUNT; perk++) { + for (;;) { + int rank = perkGetRank(gDude, perk); + if (rank <= _perk_back[perk]) { + break; + } + + perkRemove(gDude, perk); + } + } + + for (int i = 0; i < PERK_COUNT; i++) { + for (;;) { + int rank = perkGetRank(gDude, i); + if (rank >= _perk_back[i]) { + break; + } + + perkAdd(gDude, i); + } + } +} + +// validate SPECIAL stats are <= 10 +// +// 0x43DF50 +int _is_supper_bonus() +{ + for (int stat = 0; stat < 7; stat++) { + int v1 = critterGetBaseStatWithTraitModifier(gDude, stat); + int v2 = critterGetBonusStat(gDude, stat); + if (v1 + v2 > 10) { + return 1; + } + } + + return 0; +} + +// 0x43DF8C +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 = buttonCreate(characterEditorWindowHandle, 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; + } + + buttonSetCallbacks(_folder_up_button, _gsound_red_butt_press, NULL); + } + + if (_folder_down_button == -1) { + _folder_down_button = buttonCreate(characterEditorWindowHandle, + 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) { + buttonDestroy(_folder_up_button); + return -1; + } + + buttonSetCallbacks(_folder_down_button, _gsound_red_butt_press, NULL); + } + + return 0; +} + +// 0x43E0D4 +void _folder_scroll(int direction) +{ + int* v1; + + switch (characterEditorWindowSelectedFolder) { + 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 (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43 && characterEditorSelectedItem != 10) { + characterEditorSelectedItem--; + } + } + } else { + if (_folder_top_line > 0) { + _folder_top_line--; + if (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43 && _folder_max_lines + 9 > characterEditorSelectedItem) { + characterEditorSelectedItem++; + } + } + } + + *v1 = _folder_top_line; + editorRenderFolders(); + + if (characterEditorSelectedItem >= 10 && characterEditorSelectedItem < 43) { + blitBufferToBuffer( + characterEditorWindowBackgroundBuf + 640 * 267 + 345, + 277, + 170, + 640, + characterEditorWindowBuf + 640 * 267 + 345, + 640); + _DrawCard(_folder_card_fid, _folder_card_title, _folder_card_title2, _folder_card_desc); + } +} + +// 0x43E200 +void _folder_clear() +{ + int v0; + + _folder_line = 0; + _folder_ypos = 364; + + v0 = fontGetLineHeight(); + + _folder_max_lines = 9; + _folder_yoffset = v0 + 1; + + if (characterEditorSelectedItem < 10 || characterEditorSelectedItem >= 43) + _folder_highlight_line = -1; + else + _folder_highlight_line = characterEditorSelectedItem - 10; + + if (characterEditorWindowSelectedFolder < 1) { + if (characterEditorWindowSelectedFolder) + return; + + _folder_top_line = _folder_perk_top_line; + } else if (characterEditorWindowSelectedFolder == 1) { + _folder_top_line = _folder_karma_top_line; + } else if (characterEditorWindowSelectedFolder == 2) { + _folder_top_line = _folder_kills_top_line; + } +} + +// render heading string with line +// +// 0x43E28C +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 = fontGetLineHeight(); + x = 280; + y = _folder_ypos + lineHeight / 2; + if (string != NULL) { + gap = fontGetLetterSpacing(); + // TODO: Not sure about this. + lineLen = fontGetStringWidth(string) + gap * 4; + x = (x - lineLen) / 2; + fontDrawText(characterEditorWindowBuf + 640 * _folder_ypos + 34 + x + gap * 2, string, 640, 640, _colorTable[992]); + windowDrawLine(characterEditorWindowHandle, 34 + x + lineLen, y, 34 + 280, y, _colorTable[992]); + } + windowDrawLine(characterEditorWindowHandle, 34, y, 34 + x, y, _colorTable[992]); + _folder_ypos += _folder_yoffset; + } + _folder_line++; + return v8; + } else { + return 0; + } +} + +// 0x43E3D8 +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]; + } + + fontDrawText(characterEditorWindowBuf + 640 * _folder_ypos + 34, string, 640, 640, color); + _folder_ypos += _folder_yoffset; + } + + _folder_line++; + } + + return success; +} + +// 0x43E470 +bool editorDrawKillsEntry(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 = fontGetStringWidth(killsString); + + // TODO: Check. + gap = fontGetLetterSpacing(); + int v11 = _folder_ypos + fontGetLineHeight() / 2; + + fontDrawText(characterEditorWindowBuf + 640 * _folder_ypos + 34, name, 640, 640, color); + + int v12 = fontGetStringWidth(name); + windowDrawLine(characterEditorWindowHandle, 34 + v12 + gap, v11, 314 - v6 - gap, v11, color); + + fontDrawText(characterEditorWindowBuf + 640 * _folder_ypos + 314 - v6, killsString, 640, 640, color); + _folder_ypos += _folder_yoffset; + } + + _folder_line++; + } + + return success; +} + +// 0x43E5C4 +int karmaInit() +{ + const char* delim = " \t,"; + + if (gKarmaEntries != NULL) { + internal_free(gKarmaEntries); + gKarmaEntries = NULL; + } + + gKarmaEntriesLength = 0; + + File* stream = fileOpen("data\\karmavar.txt", "rt"); + if (stream == NULL) { + return -1; + } + + char string[256]; + while (fileReadString(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 = internal_realloc(gKarmaEntries, sizeof(*entries) * (gKarmaEntriesLength + 1)); + if (entries == NULL) { + fileClose(stream); + + return -1; + } + + memcpy(&(entries[gKarmaEntriesLength]), &entry, sizeof(entry)); + + gKarmaEntries = entries; + gKarmaEntriesLength++; + } + + qsort(gKarmaEntries, gKarmaEntriesLength, sizeof(*gKarmaEntries), karmaEntryCompare); + + fileClose(stream); + + return 0; +} + +// NOTE: Inlined. +// +// 0x43E764 +void karmaFree() +{ + if (gKarmaEntries != NULL) { + internal_free(gKarmaEntries); + gKarmaEntries = NULL; + } + + gKarmaEntriesLength = 0; +} + +// 0x43E78C +int karmaEntryCompare(const void* a1, const void* a2) +{ + KarmaEntry* v1 = (KarmaEntry*)a1; + KarmaEntry* v2 = (KarmaEntry*)a2; + return v1->gvar - v2->gvar; +} + +// 0x43E798 +int genericReputationInit() +{ + const char* delim = " \t,"; + + if (gGenericReputationEntries != NULL) { + internal_free(gGenericReputationEntries); + gGenericReputationEntries = NULL; + } + + gGenericReputationEntriesLength = 0; + + File* stream = fileOpen("data\\genrep.txt", "rt"); + if (stream == NULL) { + return -1; + } + + char string[256]; + while (fileReadString(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 = internal_realloc(gGenericReputationEntries, sizeof(*entries) * (gGenericReputationEntriesLength + 1)); + if (entries == NULL) { + fileClose(stream); + + return -1; + } + + memcpy(&(entries[gGenericReputationEntriesLength]), &entry, sizeof(entry)); + + gGenericReputationEntries = entries; + gGenericReputationEntriesLength++; + } + + qsort(gGenericReputationEntries, gGenericReputationEntriesLength, sizeof(*gGenericReputationEntries), genericReputationCompare); + + fileClose(stream); + + return 0; +} + +// NOTE: Inlined. +// +// 0x43E914 +void genericReputationFree() +{ + if (gGenericReputationEntries != NULL) { + internal_free(gGenericReputationEntries); + gGenericReputationEntries = NULL; + } + + gGenericReputationEntriesLength = 0; +} + +// 0x43E93C +int genericReputationCompare(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; +} diff --git a/src/character_editor.h b/src/character_editor.h new file mode 100644 index 0000000..b863e47 --- /dev/null +++ b/src/character_editor.h @@ -0,0 +1,313 @@ +#ifndef CHARACTER_EDITOR_H +#define CHARACTER_EDITOR_H + +#include "art.h" +#include "db.h" +#include "geometry.h" +#include "message.h" +#include "perk_defs.h" +#include "proto_types.h" +#include "skill_defs.h" +#include "stat_defs.h" +#include "trait_defs.h" + +#define DIALOG_PICKER_NUM_OPTIONS max(PERK_COUNT, TRAIT_COUNT) +#define TOWN_REPUTATION_COUNT 19 +#define ADDICTION_REPUTATION_COUNT 8 + +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 TownReputationEntry { + int gvar; + int city; +} TownReputationEntry; + +typedef struct STRUCT_56FCB0 { + int field_0; + char* field_4; +} STRUCT_56FCB0; + +// TODO: Field order is probably wrong. +typedef struct KillInfo { + const char* name; + int killTypeId; + int kills; +} KillInfo; + +extern int _grph_id[50]; +extern const unsigned char _copyflag[EDITOR_GRAPHIC_COUNT]; +extern const int word_431D3A[EDITOR_DERIVED_STAT_COUNT]; +extern const int _StatYpos[7]; +extern const int word_431D6C[EDITOR_DERIVED_STAT_COUNT]; +extern char byte_431D93[64]; +extern const int dword_431DD4[7]; + +extern const double dbl_50170B; +extern const double dbl_501713; +extern const double dbl_5018F0; +extern const double dbl_5019BE; + +extern bool _bk_enable_0; +extern int _skill_cursor; +extern int _slider_y; +extern int characterEditorRemainingCharacterPoints; +extern KarmaEntry* gKarmaEntries; +extern int gKarmaEntriesLength; +extern GenericReputationEntry* gGenericReputationEntries; +extern int gGenericReputationEntriesLength; +extern const TownReputationEntry gTownReputationEntries[TOWN_REPUTATION_COUNT]; +extern const int gAddictionReputationVars[ADDICTION_REPUTATION_COUNT]; +extern const int gAddictionReputationFrmIds[ADDICTION_REPUTATION_COUNT]; +extern int _folder_up_button; +extern int _folder_down_button; + +extern char _folder_card_string[256]; +extern int _skillsav[SKILL_COUNT]; +extern MessageList editorMessageList; +extern STRUCT_56FCB0 _name_sort_list[DIALOG_PICKER_NUM_OPTIONS]; +extern int _trait_bids[TRAIT_COUNT]; +extern MessageListItem editorMessageListItem; +extern char _old_str1[48]; +extern char _old_str2[48]; +extern int _tag_bids[SKILL_COUNT]; +extern char _name_save[32]; +extern Size _GInfo[EDITOR_GRAPHIC_COUNT]; +extern CacheEntry* _grph_key[EDITOR_GRAPHIC_COUNT]; +extern unsigned char* _grphcpy[EDITOR_GRAPHIC_COUNT]; +extern unsigned char* _grphbmp[EDITOR_GRAPHIC_COUNT]; +extern int _folder_max_lines; +extern int _folder_line; +extern int _folder_card_fid; +extern int _folder_top_line; +extern char* _folder_card_title; +extern char* _folder_card_title2; +extern int _folder_yoffset; +extern int _folder_karma_top_line; +extern int _folder_highlight_line; +extern char* _folder_card_desc; +extern int _folder_ypos; +extern int _folder_kills_top_line; +extern int _folder_perk_top_line; +extern unsigned char* gEditorPerkBackgroundBuffer; +extern int gEditorPerkWindow; +extern int _SliderPlusID; +extern int _SliderNegID; +extern int _stat_bids_minus[7]; +extern unsigned char* characterEditorWindowBuf; +extern int characterEditorWindowHandle; +extern int _stat_bids_plus[7]; +extern unsigned char* gEditorPerkWindowBuffer; +extern CritterProtoData _dude_data; +extern unsigned char* characterEditorWindowBackgroundBuf; +extern int _cline; +extern int _oldsline; +extern int _upsent_points_back; +extern int _last_level; +extern int characterEditorWindowOldFont; +extern int _kills_count; +extern CacheEntry* _bck_key; +extern int _hp_back; +extern int _mouse_ypos; +extern int _mouse_xpos; +extern int characterEditorSelectedItem; +extern int characterEditorWindowSelectedFolder; +extern int _frstc_draw1; +extern int _crow; +extern int _frstc_draw2; +extern int _perk_back[PERK_COUNT]; +extern unsigned int _repFtime; +extern unsigned int _frame_time; +extern int _old_tags; +extern int _last_level_back; +extern bool gCharacterEditorIsCreationMode; +extern int _tag_skill_back[NUM_TAGGED_SKILLS]; +extern int _card_old_fid2; +extern int _card_old_fid1; +extern int _trait_back[3]; +extern int _trait_count; +extern int _optrt_count; +extern int _temp_trait[3]; +extern int _tagskill_count; +extern int _temp_tag_skill[NUM_TAGGED_SKILLS]; +extern char _free_perk_back; +extern unsigned char _free_perk; +extern unsigned char _first_skill_list; + +int _editor_design(bool isCreationMode); +int characterEditorWindowInit(); +void characterEditorWindowFree(); +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); +void editorRenderFolders(); +void editorRenderPerks(); +int _kills_list_comp(const KillInfo* a, const KillInfo* b); +int editorRenderKills(); +void characterEditorRenderBigNumber(int x, int y, int flags, int value, int previousValue, int windowHandle); +void editorRenderPcStats(); +void editorRenderPrimaryStat(int stat, bool animate, int previousValue); +void editorRenderGender(); +void editorRenderAge(); +void editorRenderName(); +void editorRenderSecondaryStats(); +void editorRenderSkills(int a1); +void editorRenderDetails(); +int characterEditorEditName(); +void _PrintName(unsigned char* buf, int a2); +int characterEditorRunEditAgeDialog(); +void characterEditorEditGender(); +void characterEditorHandleIncDecPrimaryStat(int eventCode); +int _OptionWindow(); +bool characterFileExists(const char* fname); +int characterPrintToFile(const char* fileName); +char* _AddSpaces(char* string, int length); +char* _AddDots(char* string, int length); +void _ResetScreen(); +void _RegInfoAreas(); +void _SavePlayer(); +void _RestorePlayer(); +char* _itostndn(int value, char* dest); +int _DrawCard(int graphicId, const char* name, const char* attributes, char* description); +void _FldrButton(); +void _InfoButton(int eventCode); +void editorAdjustSkill(int a1); +void characterEditorToggleTaggedSkill(int skill); +void characterEditorWindowRenderTraits(); +void characterEditorToggleOptionalTrait(int trait); +void editorRenderKarma(); +int _editor_save(File* stream); +int _editor_load(File* stream); +void _editor_reset(); +int _UpdateLevel(); +void _RedrwDPrks(); +int editorSelectPerk(); +int _InputPDLoop(int count, void (*refreshProc)()); +int _ListDPerks(); +void _RedrwDMPrk(); +bool editorHandleMutate(); +void _RedrwDMTagSkl(); +bool editorHandleTag(); +void _ListNewTagSkills(); +int _ListMyTraits(int a1); +int _name_sort_comp(const void* a1, const void* a2); +int _DrawCard2(int frmId, const char* name, const char* rank, char* description); +void _pop_perks(); +int _is_supper_bonus(); +int _folder_init(); +void _folder_scroll(int direction); +void _folder_clear(); +int _folder_print_seperator(const char* string); +bool _folder_print_line(const char* string); +bool editorDrawKillsEntry(const char* name, int kills); +int karmaInit(); +void karmaFree(); +int karmaEntryCompare(const void* a1, const void* a2); +int genericReputationInit(); +void genericReputationFree(); +int genericReputationCompare(const void* a1, const void* a2); + +#endif /* CHARACTER_EDITOR_H */ diff --git a/src/character_selector.c b/src/character_selector.c new file mode 100644 index 0000000..1c4bca0 --- /dev/null +++ b/src/character_selector.c @@ -0,0 +1,940 @@ +#include "character_selector.h" + +#include "character_editor.h" +#include "color.h" +#include "core.h" +#include "critter.h" +#include "db.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_sound.h" +#include "memory.h" +#include "message.h" +#include "object.h" +#include "options.h" +#include "palette.h" +#include "proto.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "trait.h" +#include "window_manager.h" + +#include +#include + +// 0x51C84C +int gCurrentPremadeCharacter = PREMADE_CHARACTER_NARG; + +// 0x51C850 +PremadeCharacterDescription gPremadeCharacterDescriptions[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 +const int gPremadeCharacterCount = PREMADE_CHARACTER_COUNT; + +// 0x51C7F8 +int gCharacterSelectorWindow = -1; + +// 0x51C7FC +unsigned char* gCharacterSelectorWindowBuffer = NULL; + +// 0x51C800 +unsigned char* gCharacterSelectorBackground = NULL; + +// 0x51C804 +int gCharacterSelectorWindowPreviousButton = -1; + +// 0x51C808 +CacheEntry* gCharacterSelectorWindowPreviousButtonUpFrmHandle = NULL; + +// 0x51C80C +CacheEntry* gCharacterSelectorWindowPreviousButtonDownFrmHandle = NULL; + +// 0x51C810 +int gCharacterSelectorWindowNextButton = -1; + +// 0x51C814 +CacheEntry* gCharacterSelectorWindowNextButtonUpFrmHandle = NULL; + +// 0x51C818 +CacheEntry* gCharacterSelectorWindowNextButtonDownFrmHandle = NULL; + +// 0x51C81C +int gCharacterSelectorWindowTakeButton = -1; + +// 0x51C820 +CacheEntry* gCharacterSelectorWindowTakeButtonUpFrmHandle = NULL; + +// 0x51C824 +CacheEntry* gCharacterSelectorWindowTakeButtonDownFrmHandle = NULL; + +// 0x51C828 +int gCharacterSelectorWindowModifyButton = -1; + +// 0x51C82C +CacheEntry* gCharacterSelectorWindowModifyButtonUpFrmHandle = NULL; + +// 0x51C830 +CacheEntry* gCharacterSelectorWindowModifyButtonDownFrmHandle = NULL; + +// 0x51C834 +int gCharacterSelectorWindowCreateButton = -1; + +// 0x51C838 +CacheEntry* gCharacterSelectorWindowCreateButtonUpFrmHandle = NULL; + +// 0x51C83C +CacheEntry* gCharacterSelectorWindowCreateButtonDownFrmHandle = NULL; + +// 0x51C840 +int gCharacterSelectorWindowBackButton = -1; + +// 0x51C844 +CacheEntry* gCharacterSelectorWindowBackButtonUpFrmHandle = NULL; + +// 0x51C848 +CacheEntry* gCharacterSelectorWindowBackButtonDownFrmHandle = NULL; + +// 0x667764 +unsigned char* gCharacterSelectorWindowTakeButtonUpFrmData; + +// 0x667768 +unsigned char* gCharacterSelectorWindowModifyButtonDownFrmData; + +// 0x66776C +unsigned char* gCharacterSelectorWindowBackButtonUpFrmData; + +// 0x667770 +unsigned char* gCharacterSelectorWindowCreateButtonUpFrmData; + +// 0x667774 +unsigned char* gCharacterSelectorWindowModifyButtonUpFrmData; + +// 0x667778 +unsigned char* gCharacterSelectorWindowBackButtonDownFrmData; + +// 0x66777C +unsigned char* gCharacterSelectorWindowCreateButtonDownFrmData; + +// 0x667780 +unsigned char* gCharacterSelectorWindowTakeButtonDownFrmData; + +// 0x667784 +unsigned char* gCharacterSelectorWindowNextButtonDownFrmData; + +// 0x667788 +unsigned char* gCharacterSelectorWindowNextButtonUpFrmData; + +// 0x66778C +unsigned char* gCharacterSelectorWindowPreviousButtonUpFrmData; + +// 0x667790 +unsigned char* gCharacterSelectorWindowPreviousButtonDownFrmData; + +// 0x4A71D0 +int characterSelectorOpen() +{ + if (!characterSelectorWindowInit()) { + return 0; + } + + bool cursorWasHidden = cursorIsHidden(); + if (cursorWasHidden) { + mouseShowCursor(); + } + + colorPaletteLoad("color.pal"); + paletteFadeTo(_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: + brightnessDecrease(); + break; + case KEY_EQUAL: + case KEY_PLUS: + brightnessIncrease(); + 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; + } + + break; + case KEY_UPPERCASE_M: + case KEY_LOWERCASE_M: + if (!_editor_design(1)) { + rc = 2; + done = true; + } + + break; + case KEY_UPPERCASE_T: + case KEY_LOWERCASE_T: + rc = 2; + done = true; + + break; + case KEY_F10: + showQuitConfirmationDialog(); + break; + case KEY_ARROW_LEFT: + soundPlayFile("ib2p1xx1"); + // FALLTHROUGH + case 500: + gCurrentPremadeCharacter -= 1; + if (gCurrentPremadeCharacter < 0) { + gCurrentPremadeCharacter = gPremadeCharacterCount - 1; + } + + characterSelectorWindowRefresh(); + break; + case KEY_ARROW_RIGHT: + soundPlayFile("ib2p1xx1"); + // FALLTHROUGH + case 501: + gCurrentPremadeCharacter += 1; + if (gCurrentPremadeCharacter >= gPremadeCharacterCount) { + gCurrentPremadeCharacter = 0; + } + + characterSelectorWindowRefresh(); + break; + } + } + + paletteFadeTo(gPaletteBlack); + characterSelectorWindowFree(); + + if (cursorWasHidden) { + mouseHideCursor(); + } + + return rc; +} + +// 0x4A7468 +bool characterSelectorWindowInit() +{ + if (gCharacterSelectorWindow != -1) { + return false; + } + + gCharacterSelectorWindow = windowCreate(0, 0, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, _colorTable[0], 0); + if (gCharacterSelectorWindow == -1) { + goto err; + } + + gCharacterSelectorWindowBuffer = windowGetBuffer(gCharacterSelectorWindow); + if (gCharacterSelectorWindowBuffer == NULL) { + goto err; + } + + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 174, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData == NULL) { + goto err; + } + + blitBufferToBuffer(backgroundFrmData, + CS_WINDOW_WIDTH, + CS_WINDOW_HEIGHT, + CS_WINDOW_WIDTH, + gCharacterSelectorWindowBuffer, + CS_WINDOW_WIDTH); + + gCharacterSelectorBackground = internal_malloc(CS_WINDOW_BACKGROUND_WIDTH * CS_WINDOW_BACKGROUND_HEIGHT); + if (gCharacterSelectorBackground == NULL) + goto err; + + blitBufferToBuffer(backgroundFrmData + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X, + CS_WINDOW_BACKGROUND_WIDTH, + CS_WINDOW_BACKGROUND_HEIGHT, + CS_WINDOW_WIDTH, + gCharacterSelectorBackground, + CS_WINDOW_BACKGROUND_WIDTH); + + artUnlock(backgroundFrmHandle); + + int fid; + + // Setup "Previous" button. + fid = buildFid(6, 122, 0, 0, 0); + gCharacterSelectorWindowPreviousButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowPreviousButtonUpFrmHandle); + if (gCharacterSelectorWindowPreviousButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 123, 0, 0, 0); + gCharacterSelectorWindowPreviousButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowPreviousButtonDownFrmHandle); + if (gCharacterSelectorWindowPreviousButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowPreviousButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_PREVIOUS_BUTTON_X, + CS_WINDOW_PREVIOUS_BUTTON_Y, + 20, + 18, + -1, + -1, + -1, + 500, + gCharacterSelectorWindowPreviousButtonUpFrmData, + gCharacterSelectorWindowPreviousButtonDownFrmData, + NULL, + 0); + if (gCharacterSelectorWindowPreviousButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowPreviousButton, _gsound_med_butt_press, _gsound_med_butt_release); + + // Setup "Next" button. + fid = buildFid(6, 124, 0, 0, 0); + gCharacterSelectorWindowNextButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowNextButtonUpFrmHandle); + if (gCharacterSelectorWindowNextButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 125, 0, 0, 0); + gCharacterSelectorWindowNextButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowNextButtonDownFrmHandle); + if (gCharacterSelectorWindowNextButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowNextButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_NEXT_BUTTON_X, + CS_WINDOW_NEXT_BUTTON_Y, + 20, + 18, + -1, + -1, + -1, + 501, + gCharacterSelectorWindowNextButtonUpFrmData, + gCharacterSelectorWindowNextButtonDownFrmData, + NULL, + 0); + if (gCharacterSelectorWindowNextButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowNextButton, _gsound_med_butt_press, _gsound_med_butt_release); + + // Setup "Take" button. + fid = buildFid(6, 8, 0, 0, 0); + gCharacterSelectorWindowTakeButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowTakeButtonUpFrmHandle); + if (gCharacterSelectorWindowTakeButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 9, 0, 0, 0); + gCharacterSelectorWindowTakeButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowTakeButtonDownFrmHandle); + if (gCharacterSelectorWindowTakeButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowTakeButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_TAKE_BUTTON_X, + CS_WINDOW_TAKE_BUTTON_Y, + 15, + 16, + -1, + -1, + -1, + KEY_LOWERCASE_T, + gCharacterSelectorWindowTakeButtonUpFrmData, + gCharacterSelectorWindowTakeButtonDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (gCharacterSelectorWindowTakeButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowTakeButton, _gsound_red_butt_press, _gsound_red_butt_release); + + // Setup "Modify" button. + fid = buildFid(6, 8, 0, 0, 0); + gCharacterSelectorWindowModifyButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowModifyButtonUpFrmHandle); + if (gCharacterSelectorWindowModifyButtonUpFrmData == NULL) + goto err; + + fid = buildFid(6, 9, 0, 0, 0); + gCharacterSelectorWindowModifyButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowModifyButtonDownFrmHandle); + if (gCharacterSelectorWindowModifyButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowModifyButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_MODIFY_BUTTON_X, + CS_WINDOW_MODIFY_BUTTON_Y, + 15, + 16, + -1, + -1, + -1, + KEY_LOWERCASE_M, + gCharacterSelectorWindowModifyButtonUpFrmData, + gCharacterSelectorWindowModifyButtonDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (gCharacterSelectorWindowModifyButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowModifyButton, _gsound_red_butt_press, _gsound_red_butt_release); + + // Setup "Create" button. + fid = buildFid(6, 8, 0, 0, 0); + gCharacterSelectorWindowCreateButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowCreateButtonUpFrmHandle); + if (gCharacterSelectorWindowCreateButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 9, 0, 0, 0); + gCharacterSelectorWindowCreateButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowCreateButtonDownFrmHandle); + if (gCharacterSelectorWindowCreateButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowCreateButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_CREATE_BUTTON_X, + CS_WINDOW_CREATE_BUTTON_Y, + 15, + 16, + -1, + -1, + -1, + KEY_LOWERCASE_C, + gCharacterSelectorWindowCreateButtonUpFrmData, + gCharacterSelectorWindowCreateButtonDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (gCharacterSelectorWindowCreateButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowCreateButton, _gsound_red_butt_press, _gsound_red_butt_release); + + // Setup "Back" button. + fid = buildFid(6, 8, 0, 0, 0); + gCharacterSelectorWindowBackButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowBackButtonUpFrmHandle); + if (gCharacterSelectorWindowBackButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 9, 0, 0, 0); + gCharacterSelectorWindowBackButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterSelectorWindowBackButtonDownFrmHandle); + if (gCharacterSelectorWindowBackButtonDownFrmData == NULL) { + goto err; + } + + gCharacterSelectorWindowBackButton = buttonCreate(gCharacterSelectorWindow, + CS_WINDOW_BACK_BUTTON_X, + CS_WINDOW_BACK_BUTTON_Y, + 15, + 16, + -1, + -1, + -1, + KEY_ESCAPE, + gCharacterSelectorWindowBackButtonUpFrmData, + gCharacterSelectorWindowBackButtonDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (gCharacterSelectorWindowBackButton == -1) { + goto err; + } + + buttonSetCallbacks(gCharacterSelectorWindowBackButton, _gsound_red_butt_press, _gsound_red_butt_release); + + gCurrentPremadeCharacter = PREMADE_CHARACTER_NARG; + + windowRefresh(gCharacterSelectorWindow); + + if (!characterSelectorWindowRefresh()) { + goto err; + } + + return true; + +err: + + characterSelectorWindowFree(); + + return false; +} + +// 0x4A7AD4 +void characterSelectorWindowFree() +{ + if (gCharacterSelectorWindow == -1) { + return; + } + + if (gCharacterSelectorWindowPreviousButton != -1) { + buttonDestroy(gCharacterSelectorWindowPreviousButton); + gCharacterSelectorWindowPreviousButton = -1; + } + + if (gCharacterSelectorWindowPreviousButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowPreviousButtonDownFrmHandle); + gCharacterSelectorWindowPreviousButtonDownFrmHandle = NULL; + gCharacterSelectorWindowPreviousButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowPreviousButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowPreviousButtonUpFrmHandle); + gCharacterSelectorWindowPreviousButtonUpFrmHandle = NULL; + gCharacterSelectorWindowPreviousButtonUpFrmData = NULL; + } + + if (gCharacterSelectorWindowNextButton != -1) { + buttonDestroy(gCharacterSelectorWindowNextButton); + gCharacterSelectorWindowNextButton = -1; + } + + if (gCharacterSelectorWindowNextButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowNextButtonDownFrmHandle); + gCharacterSelectorWindowNextButtonDownFrmHandle = NULL; + gCharacterSelectorWindowNextButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowNextButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowNextButtonUpFrmHandle); + gCharacterSelectorWindowNextButtonUpFrmHandle = NULL; + gCharacterSelectorWindowNextButtonUpFrmData = NULL; + } + + if (gCharacterSelectorWindowTakeButton != -1) { + buttonDestroy(gCharacterSelectorWindowTakeButton); + gCharacterSelectorWindowTakeButton = -1; + } + + if (gCharacterSelectorWindowTakeButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowTakeButtonDownFrmHandle); + gCharacterSelectorWindowTakeButtonDownFrmHandle = NULL; + gCharacterSelectorWindowTakeButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowTakeButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowTakeButtonUpFrmHandle); + gCharacterSelectorWindowTakeButtonUpFrmHandle = NULL; + gCharacterSelectorWindowTakeButtonUpFrmData = NULL; + } + + if (gCharacterSelectorWindowModifyButton != -1) { + buttonDestroy(gCharacterSelectorWindowModifyButton); + gCharacterSelectorWindowModifyButton = -1; + } + + if (gCharacterSelectorWindowModifyButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowModifyButtonDownFrmHandle); + gCharacterSelectorWindowModifyButtonDownFrmHandle = NULL; + gCharacterSelectorWindowModifyButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowModifyButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowModifyButtonUpFrmHandle); + gCharacterSelectorWindowModifyButtonUpFrmHandle = NULL; + gCharacterSelectorWindowModifyButtonUpFrmData = NULL; + } + + if (gCharacterSelectorWindowCreateButton != -1) { + buttonDestroy(gCharacterSelectorWindowCreateButton); + gCharacterSelectorWindowCreateButton = -1; + } + + if (gCharacterSelectorWindowCreateButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowCreateButtonDownFrmHandle); + gCharacterSelectorWindowCreateButtonDownFrmHandle = NULL; + gCharacterSelectorWindowCreateButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowCreateButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowCreateButtonUpFrmHandle); + gCharacterSelectorWindowCreateButtonUpFrmHandle = NULL; + gCharacterSelectorWindowCreateButtonUpFrmData = NULL; + } + + if (gCharacterSelectorWindowBackButton != -1) { + buttonDestroy(gCharacterSelectorWindowBackButton); + gCharacterSelectorWindowBackButton = -1; + } + + if (gCharacterSelectorWindowBackButtonDownFrmData != NULL) { + artUnlock(gCharacterSelectorWindowBackButtonDownFrmHandle); + gCharacterSelectorWindowBackButtonDownFrmHandle = NULL; + gCharacterSelectorWindowBackButtonDownFrmData = NULL; + } + + if (gCharacterSelectorWindowBackButtonUpFrmData != NULL) { + artUnlock(gCharacterSelectorWindowBackButtonUpFrmHandle); + gCharacterSelectorWindowBackButtonUpFrmHandle = NULL; + gCharacterSelectorWindowBackButtonUpFrmData = NULL; + } + + if (gCharacterSelectorBackground != NULL) { + internal_free(gCharacterSelectorBackground); + gCharacterSelectorBackground = NULL; + } + + windowDestroy(gCharacterSelectorWindow); + gCharacterSelectorWindow = -1; +} + +// 0x4A7D58 +bool characterSelectorWindowRefresh() +{ + char path[FILENAME_MAX]; + sprintf(path, "%s.gcd", gPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName); + if (_proto_dude_init(path) == -1) { + debugPrint("\n ** Error in dude init! **\n"); + return false; + } + + blitBufferToBuffer(gCharacterSelectorBackground, + CS_WINDOW_BACKGROUND_WIDTH, + CS_WINDOW_BACKGROUND_HEIGHT, + CS_WINDOW_BACKGROUND_WIDTH, + gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X, + CS_WINDOW_WIDTH); + + bool success = false; + if (characterSelectorWindowRenderFace()) { + if (characterSelectorWindowRenderStats()) { + success = characterSelectorWindowRenderBio(); + } + } + + windowRefresh(gCharacterSelectorWindow); + + return success; +} + +// 0x4A7E08 +bool characterSelectorWindowRenderFace() +{ + bool success = false; + + CacheEntry* faceFrmHandle; + int faceFid = buildFid(6, gPremadeCharacterDescriptions[gCurrentPremadeCharacter].face, 0, 0, 0); + Art* frm = artLock(faceFid, &faceFrmHandle); + if (frm != NULL) { + unsigned char* data = artGetFrameData(frm, 0, 0); + if (data != NULL) { + int width = artGetWidth(frm, 0, 0); + int height = artGetHeight(frm, 0, 0); + blitBufferToBufferTrans(data, width, height, width, (gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * 23 + 27), CS_WINDOW_WIDTH); + success = true; + } + artUnlock(faceFrmHandle); + } + + return success; +} + +// 0x4A7EA8 +bool characterSelectorWindowRenderStats() +{ + char* str; + char text[260]; + int length; + int value; + MessageListItem messageListItem; + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + fontGetCharacterWidth(0x20); + + int vh = fontGetLineHeight(); + int y = 40; + + // NAME + str = objectGetName(gDude); + strcpy(text, str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + 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(gDude, STAT_STRENGTH); + str = statGetName(STAT_STRENGTH); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // PERCEPTION + y += vh; + + value = critterGetStat(gDude, STAT_PERCEPTION); + str = statGetName(STAT_PERCEPTION); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // ENDURANCE + y += vh; + + value = critterGetStat(gDude, STAT_ENDURANCE); + str = statGetName(STAT_PERCEPTION); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // CHARISMA + y += vh; + + value = critterGetStat(gDude, STAT_CHARISMA); + str = statGetName(STAT_CHARISMA); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // INTELLIGENCE + y += vh; + + value = critterGetStat(gDude, STAT_INTELLIGENCE); + str = statGetName(STAT_INTELLIGENCE); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // AGILITY + y += vh; + + value = critterGetStat(gDude, STAT_AGILITY); + str = statGetName(STAT_AGILITY); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // LUCK + y += vh; + + value = critterGetStat(gDude, STAT_LUCK); + str = statGetName(STAT_LUCK); + + sprintf(text, "%s %02d", str, value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + str = statGetValueDescription(value); + sprintf(text, " %s", str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + 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 (messageListGetItem(&gMiscMessageList, &messageListItem)) { + strcpy(text, messageListItem.text); + } + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + value = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + sprintf(text, " %d/%d", critterGetHitPoints(gDude), value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // ARMOR CLASS + y += vh; + + str = statGetName(STAT_ARMOR_CLASS); + strcpy(text, str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + value = critterGetStat(gDude, STAT_ARMOR_CLASS); + sprintf(text, " %d", value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + 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 (messageListGetItem(&gMiscMessageList, &messageListItem)) { + strcpy(text, messageListItem.text); + } + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + value = critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS); + sprintf(text, " %d", value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + // MELEE DAMAGE + y += vh; + + str = statGetName(STAT_ARMOR_CLASS); + strcpy(text, str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + value = critterGetStat(gDude, STAT_ARMOR_CLASS); + sprintf(text, " %d", value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + 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]; + skillsGetTagged(skills, DEFAULT_TAGGED_SKILLS); + + for (int index = 0; index < DEFAULT_TAGGED_SKILLS; index++) { + y += vh; + + str = skillGetName(skills[index]); + strcpy(text, str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + + value = skillGetValue(gDude, skills[index]); + sprintf(text, " %d%%", value); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + } + + // TRAITS + int traits[TRAITS_MAX_SELECTED_COUNT]; + traitsGetSelected(&(traits[0]), &(traits[1])); + + for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { + y += vh; + + str = traitGetName(traits[index]); + strcpy(text, str); + + length = fontGetStringWidth(text); + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]); + } + + fontSetCurrent(oldFont); + + return true; +} + +// 0x4A8AE4 +bool characterSelectorWindowRenderBio() +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + char path[FILENAME_MAX]; + sprintf(path, "%s.bio", gPremadeCharacterDescriptions[gCurrentPremadeCharacter].fileName); + + File* stream = fileOpen(path, "rt"); + if (stream != NULL) { + int y = 40; + int lineHeight = fontGetLineHeight(); + + char string[256]; + while (fileReadString(string, 256, stream) && y < 260) { + fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_BIO_X, string, CS_WINDOW_WIDTH - CS_WINDOW_BIO_X, CS_WINDOW_WIDTH, _colorTable[992]); + y += lineHeight; + } + + fileClose(stream); + } + + fontSetCurrent(oldFont); + + return true; +} diff --git a/src/character_selector.h b/src/character_selector.h new file mode 100644 index 0000000..257bf7e --- /dev/null +++ b/src/character_selector.h @@ -0,0 +1,99 @@ +#ifndef CHARACTER_SELECTOR_H +#define CHARACTER_SELECTOR_H + +#include "art.h" + +#include + +#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; + +extern int gCurrentPremadeCharacter; +extern PremadeCharacterDescription gPremadeCharacterDescriptions[PREMADE_CHARACTER_COUNT]; +extern const int gPremadeCharacterCount; + +extern int gCharacterSelectorWindow; +extern unsigned char* gCharacterSelectorWindowBuffer; +extern unsigned char* gCharacterSelectorBackground; +extern int gCharacterSelectorWindowPreviousButton; +extern CacheEntry* gCharacterSelectorWindowPreviousButtonDownFrmHandle; +extern CacheEntry* gCharacterSelectorWindowPreviousButtonUpFrmHandle; +extern int gCharacterSelectorWindowNextButton; +extern CacheEntry* gCharacterSelectorWindowNextButtonUpFrmHandle; +extern CacheEntry* gCharacterSelectorWindowNextButtonDownFrmHandle; +extern int gCharacterSelectorWindowTakeButton; +extern CacheEntry* gCharacterSelectorWindowTakeButtonUpFrmHandle; +extern CacheEntry* gCharacterSelectorWindowTakeButtonDownFrmHandle; +extern int gCharacterSelectorWindowModifyButton; +extern CacheEntry* gCharacterSelectorWindowModifyButtonUpFrmHandle; +extern CacheEntry* gCharacterSelectorWindowModifyButtonDownFrmHandle; +extern int gCharacterSelectorWindowCreateButton; +extern CacheEntry* gCharacterSelectorWindowCreateButtonUpFrmHandle; +extern CacheEntry* gCharacterSelectorWindowCreateButtonDownFrmHandle; +extern int gCharacterSelectorWindowBackButton; +extern CacheEntry* gCharacterSelectorWindowBackButtonUpFrmHandle; +extern CacheEntry* gCharacterSelectorWindowBackButtonDownFrmHandle; + +extern unsigned char* gCharacterSelectorWindowTakeButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowModifyButtonDownFrmData; +extern unsigned char* gCharacterSelectorWindowBackButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowCreateButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowModifyButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowBackButtonDownFrmData; +extern unsigned char* gCharacterSelectorWindowCreateButtonDownFrmData; +extern unsigned char* gCharacterSelectorWindowTakeButtonDownFrmData; +extern unsigned char* gCharacterSelectorWindowNextButtonDownFrmData; +extern unsigned char* gCharacterSelectorWindowNextButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowPreviousButtonUpFrmData; +extern unsigned char* gCharacterSelectorWindowPreviousButtonDownFrmData; + +int characterSelectorOpen(); +bool characterSelectorWindowInit(); +void characterSelectorWindowFree(); +bool characterSelectorWindowRefresh(); +bool characterSelectorWindowRenderFace(); +bool characterSelectorWindowRenderStats(); +bool characterSelectorWindowRenderBio(); + +#endif /* CHARACTER_SELECTOR_H */ diff --git a/src/color.c b/src/color.c new file mode 100644 index 0000000..a98b8a2 --- /dev/null +++ b/src/color.c @@ -0,0 +1,589 @@ +#include "color.h" + +#include "core.h" + +#include +#include + +// 0x50F930 +char _aColor_cNoError[] = "color.c: No errors\n"; + +// 0x50F95C +char _aColor_cColorTa[] = "color.c: color table not found\n"; + +// 0x51DF10 +char* _errorStr = _aColor_cNoError; + +// 0x51DF14 +bool _colorsInited = false; + +// 0x51DF18 +double gBrightness = 1.0; + +// 0x51DF20 +ColorTransitionCallback* gColorPaletteTransitionCallback = NULL; + +// 0x51DF24 +MallocProc* gColorPaletteMallocProc = colorPaletteMallocDefaultImpl; + +// 0x51DF28 +ReallocProc* gColorPaletteReallocProc = colorPaletteReallocDefaultImpl; + +// 0x51DF2C +FreeProc* gColorPaletteFreeProc = colorPaletteFreeDefaultImpl; + +// 0x51DF30 +ColorFileNameManger* gColorFileNameMangler = NULL; + +// 0x51DF34 +unsigned char _cmap[768] = { + 0x3F, 0x3F, 0x3F +}; + +// 0x673090 +unsigned char _systemCmap[256 * 3]; + +// 0x673390 +unsigned char _currentGammaTable[64]; + +// 0x6733D0 +unsigned char* _blendTable[256]; + +// 0x6737D0 +unsigned char _mappedColor[256]; + +// 0x6738D0 +unsigned char _colorMixAddTable[65536]; + +// 0x6838D0 +unsigned char _intensityColorTable[65536]; + +// 0x6938D0 +unsigned char _colorMixMulTable[65536]; + +// 0x6A38D0 +unsigned char _colorTable[32768]; + +// 0x6AB928 +ColorPaletteFileReadProc* gColorPaletteFileReadProc; + +// 0x6AB92C +ColorPaletteCloseProc* gColorPaletteFileCloseProc; + +// 0x6AB930 +ColorPaletteFileOpenProc* gColorPaletteFileOpenProc; + +// NOTE: Inlined. +// +// 0x4C7200 +int colorPaletteFileOpen(const char* filePath, int flags) +{ + if (gColorPaletteFileOpenProc != NULL) { + return gColorPaletteFileOpenProc(filePath, flags); + } + + return -1; +} + +// NOTE: Inlined. +// +// 0x4C7218 +int colorPaletteFileRead(int fd, void* buffer, size_t size) +{ + if (gColorPaletteFileReadProc != NULL) { + return gColorPaletteFileReadProc(fd, buffer, size); + } + + return -1; +} + +// NOTE: Inlined. +// +// 0x4C7230 +int colorPaletteFileClose(int fd) +{ + if (gColorPaletteFileCloseProc != NULL) { + return gColorPaletteFileCloseProc(fd); + } + + return -1; +} + +// 0x4C7248 +void colorPaletteSetFileIO(ColorPaletteFileOpenProc* openProc, ColorPaletteFileReadProc* readProc, ColorPaletteCloseProc* closeProc) +{ + gColorPaletteFileOpenProc = openProc; + gColorPaletteFileReadProc = readProc; + gColorPaletteFileCloseProc = closeProc; +} + +// 0x4C725C +void* colorPaletteMallocDefaultImpl(size_t size) +{ + return malloc(size); +} + +// 0x4C7264 +void* colorPaletteReallocDefaultImpl(void* ptr, size_t size) +{ + return realloc(ptr, size); +} + +// 0x4C726C +void colorPaletteFreeDefaultImpl(void* ptr) +{ + free(ptr); +} + +// 0x4C72B4 +int _calculateColor(int a1, int a2) +{ + int v1 = (a1 >> 9) + ((a2 & 0xFF) << 8); + return _intensityColorTable[v1]; +} + +// 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 colorPaletteFadeBetween(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 (gColorPaletteTransitionCallback != NULL) { + if (step % 128 == 0) { + gColorPaletteTransitionCallback(); + } + } + + _setSystemPalette(palette); + } + + _setSystemPalette(newPalette); +} + +// 0x4C73D4 +void colorPaletteSetTransitionCallback(ColorTransitionCallback* callback) +{ + gColorPaletteTransitionCallback = callback; +} + +// 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]; + } + + directDrawSetPalette(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]; + } + + directDrawSetPaletteInRange(newPalette, start, end - start + 1); +} + +// 0x4C7550 +void _setIntensityTableColor(int a1) +{ + int v1, v2, v3, v4, v5, v6, v7, v8, v9, v10; + + v5 = 0; + v10 = a1 << 8; + + 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[index + v10] = _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[0x7F + index + 1 + v10] = _colorTable[v9]; + + v5 += 0x200; + } +} + +// 0x4C7658 +void _setIntensityTables() +{ + for (int index = 0; index < 256; index++) { + if (_mappedColor[index] != 0) { + _setIntensityTableColor(index); + } else { + memset(_intensityColorTable + index * 256, 0, 256); + } + } +} + +// 0x4C769C +void _setMixTableColor(int a1) +{ + int i; + int v1, 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; + + v1 = a1 << 8; + + 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[v1 + 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[v1 + i] = _colorTable[v29]; + } else { + if (_mappedColor[i]) { + _colorMixAddTable[v1 + i] = i; + _colorMixMulTable[v1 + i] = i; + } else { + _colorMixAddTable[v1 + i] = a1; + _colorMixMulTable[v1 + i] = a1; + } + } + } +} + +// 0x4C78E4 +bool colorPaletteLoad(char* path) +{ + if (gColorFileNameMangler != NULL) { + path = gColorFileNameMangler(path); + } + + // NOTE: Uninline. + int fd = colorPaletteFileOpen(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. + colorPaletteFileRead(fd, &r, sizeof(r)); + + // NOTE: Uninline. + colorPaletteFileRead(fd, &g, sizeof(g)); + + // NOTE: Uninline. + colorPaletteFileRead(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. + colorPaletteFileRead(fd, _colorTable, 0x8000); + + unsigned int type; + // NOTE: Uninline. + colorPaletteFileRead(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. + colorPaletteFileRead(fd, _intensityColorTable, 0x10000); + + // NOTE: Uninline. + colorPaletteFileRead(fd, _colorMixAddTable, 0x10000); + + // NOTE: Uninline. + colorPaletteFileRead(fd, _colorMixMulTable, 0x10000); + } else { + _setIntensityTables(); + + for (int index = 0; index < 256; index++) { + _setMixTableColor(index); + } + } + + _rebuildColorBlendTables(); + + // NOTE: Uninline. + colorPaletteFileClose(fd); + + return true; +} + +// 0x4C7AB4 +char* _colorError() +{ + return _errorStr; +} + +// 0x4C7B44 +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 +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*)gColorPaletteMallocProc(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) { + gColorPaletteFreeProc(count); + _blendTable[a1] = NULL; + } + } +} + +// 0x4C7E58 +void colorPaletteSetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) +{ + gColorPaletteMallocProc = mallocProc; + gColorPaletteReallocProc = reallocProc; + gColorPaletteFreeProc = freeProc; +} + +// 0x4C7E6C +void colorSetBrightness(double value) +{ + gBrightness = value; + + for (int i = 0; i < 64; i++) { + double value = pow(i, gBrightness); + _currentGammaTable[i] = (unsigned char)min(max(value, 0.0), 63.0); + } + + _setSystemPalette(_systemCmap); +} + +// 0x4C89CC +bool _initColors() +{ + if (_colorsInited) { + return true; + } + + _colorsInited = true; + + colorSetBrightness(1.0); + + if (!colorPaletteLoad("color.pal")) { + return false; + } + + _setSystemPalette(_cmap); + + return true; +} + +// 0x4C8A18 +void _colorsClose() +{ + for (int index = 0; index < 256; index++) { + _freeColorBlendTable(index); + } + + // TODO: Incomplete. +} diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..fd6b7cb --- /dev/null +++ b/src/color.h @@ -0,0 +1,69 @@ +#ifndef COLOR_H +#define COLOR_H + +#include "memory_defs.h" + +#include +#include + +typedef char*(ColorFileNameManger)(char*); +typedef void(ColorTransitionCallback)(); + +typedef int(ColorPaletteFileOpenProc)(const char* path, int mode); +typedef int(ColorPaletteFileReadProc)(int fd, void* buffer, size_t size); +typedef int(ColorPaletteCloseProc)(int fd); + +extern char _aColor_cNoError[]; +extern char _aColor_cColorTa[]; + +extern char* _errorStr; +extern bool _colorsInited; +extern double gBrightness; +extern ColorTransitionCallback* gColorPaletteTransitionCallback; +extern MallocProc* gColorPaletteMallocProc; +extern ReallocProc* gColorPaletteReallocProc; +extern FreeProc* gColorPaletteFreeProc; +extern ColorFileNameManger* gColorFileNameMangler; +extern unsigned char _cmap[768]; + +extern unsigned char _systemCmap[256 * 3]; +extern unsigned char _currentGammaTable[64]; +extern unsigned char* _blendTable[256]; +extern unsigned char _mappedColor[256]; +extern unsigned char _colorMixAddTable[65536]; +extern unsigned char _intensityColorTable[65536]; +extern unsigned char _colorMixMulTable[65536]; +extern unsigned char _colorTable[32768]; +extern ColorPaletteFileReadProc* gColorPaletteFileReadProc; +extern ColorPaletteCloseProc* gColorPaletteFileCloseProc; +extern ColorPaletteFileOpenProc* gColorPaletteFileOpenProc; + +int colorPaletteFileOpen(const char* filePath, int flags); +int colorPaletteFileRead(int fd, void* buffer, size_t size); +int colorPaletteFileClose(int fd); +void colorPaletteSetFileIO(ColorPaletteFileOpenProc* openProc, ColorPaletteFileReadProc* readProc, ColorPaletteCloseProc* closeProc); +void* colorPaletteMallocDefaultImpl(size_t size); +void* colorPaletteReallocDefaultImpl(void* ptr, size_t size); +void colorPaletteFreeDefaultImpl(void* ptr); +int _calculateColor(int a1, int a2); +int _Color2RGB_(int a1); +void colorPaletteFadeBetween(unsigned char* oldPalette, unsigned char* newPalette, int steps); +void colorPaletteSetTransitionCallback(ColorTransitionCallback* callback); +void _setSystemPalette(unsigned char* palette); +unsigned char* _getSystemPalette(); +void _setSystemPaletteEntries(unsigned char* a1, int a2, int a3); +void _setIntensityTableColor(int a1); +void _setIntensityTables(); +void _setMixTableColor(int a1); +bool colorPaletteLoad(char* path); +char* _colorError(); +void _buildBlendTable(unsigned char* ptr, unsigned char ch); +void _rebuildColorBlendTables(); +unsigned char* _getColorBlendTable(int ch); +void _freeColorBlendTable(int a1); +void colorPaletteSetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); +void colorSetBrightness(double value); +bool _initColors(); +void _colorsClose(); + +#endif /* COLOR_H */ diff --git a/src/combat.c b/src/combat.c new file mode 100644 index 0000000..5c67a93 --- /dev/null +++ b/src/combat.c @@ -0,0 +1,5762 @@ +#include "combat.h" + +#include "actions.h" +#include "animation.h" +#include "art.h" +#include "color.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "db.h" +#include "debug.h" +#include "display_monitor.h" +#include "draw.h" +#include "elevator.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "loadsave.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "pipboy.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "tile.h" +#include "trait.h" +#include "window_manager.h" + +#include +#include + +// 0x500B50 +char _a_1[] = "."; + +// 0x51093C +int _combat_turn_running = 0; + +// 0x510940 +int _combatNumTurns = 0; + +// 0x510944 +unsigned int gCombatState = COMBAT_STATE_0x02; + +// 0x510948 +STRUCT_510948* _aiInfoList = NULL; + +// 0x51094C +STRUCT_664980* _gcsd = NULL; + +// 0x510950 +bool _combat_call_display = false; + +// Accuracy modifiers for hit locations. +// +// 0x510954 +const int _hit_location_penalty[HIT_LOCATION_COUNT] = { + -40, + -30, + -30, + 0, + -20, + -20, + -60, + -30, + 0, +}; + +// Critical hit tables for every kill type. +// +// 0x510978 +CriticalHitDescription gCriticalHitTables[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 +CriticalHitDescription gPlayerCriticalHitTable[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 +int _combat_end_due_to_load = 0; + +// 0x517F9C +bool _combat_cleanup_enabled = false; + +// Provides effects caused by failing weapons. +// +// 0x517FA0 +const 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 +const int _call_ty[4] = { + 122, + 188, + 251, + 316, +}; + +// 0x51803C +const int _hit_loc_left[4] = { + HIT_LOCATION_HEAD, + HIT_LOCATION_EYES, + HIT_LOCATION_RIGHT_ARM, + HIT_LOCATION_RIGHT_LEG, +}; + +// 0x51804C +const int _hit_loc_right[4] = { + HIT_LOCATION_TORSO, + HIT_LOCATION_GROIN, + HIT_LOCATION_LEFT_ARM, + HIT_LOCATION_LEFT_LEG, +}; + +// 0x56D2B0 +Attack _main_ctd; + +// combat.msg +// +// 0x56D368 +MessageList gCombatMessageList; + +// 0x56D370 +Object* gCalledShotCritter; + +// 0x56D374 +int gCalledShotWindow; + +// 0x56D378 +int _combat_elev; + +// 0x56D37C +int _list_total; + +// Probably last who_hit_me of obj_dude +// +// 0x56D380 +Object* _combat_ending_guy; + +// 0x56D384 +int _list_noncom; + +// 0x56D388 +Object* _combat_turn_obj; + +// target_highlight +// +// 0x56D38C +int _combat_highlight; + +// 0x56D390 +Object** _combat_list; + +// 0x56D394 +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; + +// 0x56D3A0 +Attack _shoot_ctd; + +// 0x56D458 +Attack _explosion_ctd; + +// combat_init +// 0x420CC0 +int combatInit() +{ + 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; + gCombatState = COMBAT_STATE_0x02; + + max_action_points = critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS); + + _combat_free_move = 0; + _combat_ending_guy = NULL; + _combat_end_due_to_load = 0; + + gDude->data.critter.combat.ap = max_action_points; + + _combat_cleanup_enabled = 0; + + if (!messageListInit(&gCombatMessageList)) { + return -1; + } + + sprintf(path, "%s%s", asc_5186C8, "combat.msg"); + + if (!messageListLoad(&gCombatMessageList, path)) { + return -1; + } + + return 0; +} + +// 0x420DA0 +void combatReset() +{ + 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; + gCombatState = COMBAT_STATE_0x02; + + max_action_points = critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS); + + _combat_free_move = 0; + _combat_ending_guy = NULL; + + gDude->data.critter.combat.ap = max_action_points; +} + +// 0x420E14 +void combatExit() +{ + messageListFree(&gCombatMessageList); +} + +// 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 combatLoad(File* stream) +{ + int v14; + STRUCT_510948* ptr; + int a2; + Object* obj; + int v24; + int i; + int j; + + if (fileReadInt32(stream, &gCombatState) == -1) return -1; + + if (!isInCombat()) { + obj = objectFindFirst(); + while (obj != NULL) { + if (obj->pid >> 24 == OBJ_TYPE_CRITTER) { + if (obj->data.critter.combat.whoHitMeCid == -1) { + obj->data.critter.combat.whoHitMe = NULL; + } + } + obj = objectFindNext(); + } + return 0; + } + + if (fileReadInt32(stream, &_combat_turn_running) == -1) return -1; + if (fileReadInt32(stream, &_combat_free_move) == -1) return -1; + if (fileReadInt32(stream, &_combat_exps) == -1) return -1; + if (fileReadInt32(stream, &_list_com) == -1) return -1; + if (fileReadInt32(stream, &_list_noncom) == -1) return -1; + if (fileReadInt32(stream, &_list_total) == -1) return -1; + + if (objectListCreate(-1, gElevation, 1, &_combat_list) != _list_total) { + objectListFree(_combat_list); + return -1; + } + + if (fileReadInt32(stream, &v24) == -1) return -1; + + gDude->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 (fileReadInt32(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) { + internal_free(_aiInfoList); + } + + _aiInfoList = internal_malloc(sizeof(*_aiInfoList) * _list_total); + if (_aiInfoList == NULL) { + return -1; + } + + for (v14 = 0; v14 < _list_total; v14++) { + ptr = &(_aiInfoList[v14]); + + if (fileReadInt32(stream, &a2) == -1) return -1; + + if (a2 == -1) { + ptr->field_0 = 0; + } else { + ptr->field_0 = objectFindById(a2); + if (ptr->field_0 == NULL) return -1; + } + + if (fileReadInt32(stream, &a2) == -1) return -1; + + if (a2 == -1) { + ptr->field_4 = 0; + } else { + ptr->field_4 = objectFindById(a2); + if (ptr->field_4 == NULL) return -1; + } + + if (fileReadInt32(stream, &a2) == -1) return -1; + + if (a2 == -1) { + ptr->field_8 = 0; + } else { + ptr->field_8 = objectFindById(a2); + if (ptr->field_8 == NULL) return -1; + } + + if (fileReadInt32(stream, &(ptr->field_C)) == -1) return -1; + } + + _combat_begin_extra(gDude); + + return 0; +} + +// 0x421244 +int combatSave(File* stream) +{ + if (fileWriteInt32(stream, gCombatState) == -1) return -1; + + if (!isInCombat()) return 0; + + if (fileWriteInt32(stream, _combat_turn_running) == -1) return -1; + if (fileWriteInt32(stream, _combat_free_move) == -1) return -1; + if (fileWriteInt32(stream, _combat_exps) == -1) return -1; + if (fileWriteInt32(stream, _list_com) == -1) return -1; + if (fileWriteInt32(stream, _list_noncom) == -1) return -1; + if (fileWriteInt32(stream, _list_total) == -1) return -1; + if (fileWriteInt32(stream, gDude->cid) == -1) return -1; + + for (int index = 0; index < _list_total; index++) { + if (fileWriteInt32(stream, _combat_list[index]->cid) == -1) return -1; + } + + if (_aiInfoList == NULL) { + return -1; + } + + for (int index = 0; index < _list_total; index++) { + STRUCT_510948* ptr = &(_aiInfoList[index]); + + if (fileWriteInt32(stream, ptr->field_0 != NULL ? ptr->field_0->id : -1) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_4 != NULL ? ptr->field_4->id : -1) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_8 != NULL ? ptr->field_8->id : -1) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_C) == -1) return -1; + } + + return 0; +} + +// 0x4213E8 +bool _combat_safety_invalidate_weapon(Object* a1, Object* a2, int hitMode, Object* a4, int* a5) +{ + return _combat_safety_invalidate_weapon_func(a1, a2, hitMode, a4, a5, NULL); +} + +// 0x4213FC +bool _combat_safety_invalidate_weapon_func(Object* critter, Object* weapon, int hitMode, Object* a4, int* a5, Object* a6) +{ + if (a5 != NULL) { + *a5 = 0; + } + + if (critter->pid == PROTO_ID_0x10001E0) { + return false; + } + + int intelligence = critterGetStat(critter, STAT_INTELLIGENCE); + int team = critter->data.critter.combat.team; + int v41 = _item_w_area_damage_radius(weapon, hitMode); + int maxDamage; + weaponGetDamageMinMax(weapon, NULL, &maxDamage); + int damageType = weaponGetDamageType(critter, weapon); + + if (v41 > 0) { + if (intelligence < 5) { + v41 -= 5 - intelligence; + if (v41 < 0) { + v41 = 0; + } + } + + if (a6 != NULL) { + if (objectGetDistanceBetween(a4, a6) < v41) { + debugPrint("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 != critter + && candidate != a4 + && !critterIsDead(candidate)) { + int v14 = objectGetDistanceBetween(a4, candidate); + if (v14 < v41 && 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; + } + } + } + } + + int v17 = objectGetDistanceBetween(a4, critter); + if (v17 <= v41) { + if (a5 != NULL) { + int v18 = objectGetDistanceBetween(a4, critter); + *a5 = v41 - v18 + 1; + return false; + } + + return true; + } + + return false; + } + + int v19 = weaponGetAnimationForHitMode(weapon, hitMode); + if (v19 != ANIM_FIRE_BURST && v19 != ANIM_FIRE_CONTINUOUS) { + return false; + } + + Attack attack; + attackInit(&attack, critter, a4, hitMode, HIT_LOCATION_TORSO); + + int accuracy = attackDetermineToHit(critter, critter->tile, a4, HIT_LOCATION_TORSO, hitMode, 1); + int v33; + int a4a; + _compute_spray(&attack, accuracy, &v33, &a4a, v19); + + if (a6 != NULL) { + for (int index = 0; index < attack.extrasLength; index++) { + if (attack.extras[index] == a6) { + debugPrint("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 != critter + && candidate != a4 + && !critterIsDead(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* a1, Object* a2, Object* a3, Object* a4) +{ + return _combat_safety_invalidate_weapon_func(a1, a4, HIT_MODE_RIGHT_WEAPON_PRIMARY, a2, NULL, a3); +} + +// 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; +} + +// 0x421850 +int _combatCopyAIInfo(int a1, int a2) +{ + STRUCT_510948* v3; + STRUCT_510948* v4; + + v3 = &_aiInfoList[a1]; + v4 = &_aiInfoList[a2]; + + v4->field_0 = v3->field_0; + v4->field_4 = v3->field_4; + v4->field_8 = v3->field_8; + v4->field_C = v3->field_C; + + 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].field_0; +} + +// 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].field_0 = 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].field_4; +} + +// 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 (critterIsDead(a2)) { + a2 = NULL; + } + + _aiInfoList[a1->cid].field_4 = 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].field_8; +} + +// 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].field_8 = NULL; + + return 0; +} + +// 0x421A34 +void _combat_begin(Object* a1) +{ + _combat_turn_running = 0; + _anim_stop(); + tickersRemove(_dude_fidget); + _combat_elev = gElevation; + + if (!isInCombat()) { + _combatNumTurns = 0; + _combat_exps = 0; + _combat_list = NULL; + _list_total = objectListCreate(-1, _combat_elev, OBJ_TYPE_CRITTER, &_combat_list); + _list_noncom = _list_total; + _list_com = 0; + _aiInfoList = internal_malloc(sizeof(*_aiInfoList) * _list_total); + if (_aiInfoList == NULL) { + return; + } + + for (int index = 0; index < _list_total; index++) { + STRUCT_510948* ptr = &(_aiInfoList[index]); + ptr->field_0 = NULL; + ptr->field_4 = NULL; + ptr->field_8 = NULL; + ptr->field_C = 0; + } + + Object* v1 = NULL; + for (int index = 0; index < _list_total; index++) { + Object* critter = _combat_list[index]; + CritterCombatData* combatData = &(critter->data.critter.combat); + combatData->maneuver &= 0x01; + combatData->damageLastTurn = 0; + combatData->whoHitMe = NULL; + combatData->ap = 0; + critter->cid = index; + + // NOTE: Not sure about this code, field_C is already reset. + if (isInCombat() && critter != NULL && index != -1) { + _aiInfoList[index].field_C = 0; + } + + scriptSetObjects(critter->sid, NULL, NULL); + scriptSetFixedParam(critter->sid, 0); + if (critter->pid == 0x1000098) { + if (!critterIsDead(critter)) { + v1 = critter; + } + } + } + + gCombatState |= COMBAT_STATE_0x01; + + tileWindowRefresh(); + gameUiDisable(0); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_WATCH); + _combat_ending_guy = NULL; + _combat_begin_extra(a1); + _caiTeamCombatInit(_combat_list, _list_total); + interfaceBarEndButtonsShow(true); + _gmouse_enable_scrolling(); + + if (v1 != NULL && !_isLoadingGame()) { + int fid = buildFid((v1->fid & 0xF000000) >> 24, + 100, + (v1->fid & 0xFF0000) >> 16, + (v1->fid & 0xF000) >> 12, + (v1->fid & 0x70000000) >> 28); + + reg_anim_clear(v1); + reg_anim_begin(2); + reg_anim_animate(v1, 6, -1); + reg_anim_17(v1, fid, -1); + reg_anim_end(); + + while (animationIsBusy(v1)) { + _process_bk(); + } + } + } +} + +// 0x421C8C +void _combat_begin_extra(Object* a1) +{ + for (int index = 0; index < _list_total; index++) { + _combat_update_critter_outline_for_los(_combat_list[index], 0); + } + + attackInit(&_main_ctd, a1, NULL, 4, 3); + + _combat_turn_obj = a1; + + _combat_ai_begin(_list_total, _combat_list); + + _combat_highlight = 2; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &_combat_highlight); +} + +// Something with outlining. +// +// 0x421D50 +void _combat_update_critter_outline_for_los(Object* critter, bool a2) +{ + if (critter->pid >> 24 != OBJ_TYPE_CRITTER) { + return; + } + + if (critter == gDude) { + return; + } + + if (critterIsDead(critter)) { + return; + } + + bool v5 = false; + if (!_combat_is_shot_blocked(gDude, gDude->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 = gDude->data.critter.combat.team == critter->data.critter.combat.team + ? OUTLINE_TYPE_FRIENDLY + : OUTLINE_TYPE_HOSTILE; + objectDisableOutline(critter, NULL); + objectClearOutline(critter, NULL); + objectSetOutline(critter, newOutlineType, NULL); + if (a2) { + objectEnableOutline(critter, NULL); + } else { + objectDisableOutline(critter, NULL); + } + } else { + if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) { + if (!a2) { + objectDisableOutline(critter, NULL); + } + } else { + if (a2) { + objectEnableOutline(critter, NULL); + } + } + } + } else { + int v7 = objectGetDistanceBetween(gDude, critter); + int v8 = critterGetStat(gDude, STAT_PERCEPTION) * 5; + if ((critter->flags & OBJECT_FLAG_0x20000) != 0) { + v8 /= 2; + } + + if (v7 <= v8) { + v5 = true; + } + + int outlineType = critter->outline & OUTLINE_TYPE_MASK; + if (outlineType != OUTLINE_TYPE_32) { + objectDisableOutline(critter, NULL); + objectClearOutline(critter, NULL); + + if (v5) { + objectSetOutline(critter, OUTLINE_TYPE_32, NULL); + + if (a2) { + objectEnableOutline(critter, NULL); + } else { + objectDisableOutline(critter, NULL); + } + } + } else { + if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) { + if (!a2) { + objectDisableOutline(critter, NULL); + } + } else { + if (a2) { + objectEnableOutline(critter, NULL); + } + } + } + } +} + +// Probably complete combat sequence. +// +// 0x421EFC +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 != gDude) { + _cai_attempt_w_reload(critter, 0); + } + } + } + + tickersAdd(_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 = 0; + } + + for (int index = 0; index < _list_total; index++) { + Object* critter = _combat_list[index]; + critter->data.critter.combat.ap = 0; + objectClearOutline(critter, NULL); + critter->data.critter.combat.whoHitMe = NULL; + + scriptSetObjects(critter->sid, NULL, NULL); + scriptSetFixedParam(critter->sid, 0); + + if (critter->pid == 0x1000098 && !critterIsDead(critter) && !_isLoadingGame()) { + int fid = buildFid((critter->fid & 0xF000000) >> 24, + 99, + (critter->fid & 0xFF0000) >> 16, + (critter->fid & 0xF000) >> 12, + (critter->fid & 0x70000000) >> 28); + reg_anim_clear(critter); + reg_anim_begin(2); + reg_anim_animate(critter, 6, -1); + reg_anim_17(critter, fid, -1); + reg_anim_end(); + + while (animationIsBusy(critter)) { + _process_bk(); + } + } + } + + tileWindowRefresh(); + + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + interfaceUpdateItems(true, leftItemAction, rightItemAction); + + gDude->data.critter.combat.ap = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + + interfaceRenderActionPoints(0, 0); + + if (_game_user_wants_to_quit == 0) { + _combat_give_exps(_combat_exps); + } + + _combat_exps = 0; + + gCombatState &= ~(COMBAT_STATE_0x01 | COMBAT_STATE_0x02); + gCombatState |= COMBAT_STATE_0x02; + + if (_list_total != 0) { + objectListFree(_combat_list); + + if (_aiInfoList != NULL) { + internal_free(_aiInfoList); + } + _aiInfoList = NULL; + } + + _list_total = 0; + + _combat_ai_over(); + gameUiEnable(); + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + interfaceRenderArmorClass(true); + + if (_critter_is_prone(gDude) && !critterIsDead(gDude) && _combat_ending_guy == NULL) { + queueRemoveEventsByType(gDude, EVENT_TYPE_KNOCKOUT); + knockoutEventProcess(gDude, NULL); + } +} + +// 0x422194 +void _combat_over_from_load() +{ + _combat_over(); + gCombatState = 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 (critterIsDead(gDude)) { + return; + } + + pcAddExperience(exp_points); + + v7.num = 621; // %s you earn %d exp. points. + if (!messageListGetItem(&gProtoMessageList, &v7)) { + return; + } + + v9.num = randomBetween(0, 3) + 622; // generate prefix for message + + current_hp = critterGetStat(gDude, STAT_CURRENT_HIT_POINTS); + max_hp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + if (current_hp == max_hp && randomBetween(0, 100) > 65) { + v9.num = 626; // Best possible prefix: For destroying your enemies without taking a scratch, + } + + if (!messageListGetItem(&gProtoMessageList, &v9)) { + return; + } + + sprintf(text, v7.text, v9.text, exp_points); + displayMonitorAddMessage(text); +} + +// 0x4222A8 +void _combat_add_noncoms() +{ + _combatai_notify_friends(gDude); + + 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 = 0; + + 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 != gDude) { + actionPoints = critterGetStat(obj, STAT_MAXIMUM_ACTION_POINTS); + } + + if (_gcsd != NULL) { + actionPoints += _gcsd->actionPointsBonus; + } + + obj->data.critter.combat.ap = actionPoints; + + _combat_turn(obj, false); + } + } +} + +// Compares critters by sequence. +// +// 0x4223C8 +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 +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 != gDude && a2 != gDude) { + for (int index = 0; index < _list_total; index++) { + Object* obj = _combat_list[index]; + if (obj == gDude) { + 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 +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 != gDude) { + 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; + + gameTimeAddSeconds(5); +} + +// 0x422694 +void combatAttemptEnd() +{ + if (_combat_elev == gDude->elevation) { + MessageListItem messageListItem; + int dudeTeam = gDude->data.critter.combat.team; + + for (int index = 0; index < _list_com; index++) { + Object* critter = _combat_list[index]; + if (critter != gDude) { + 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + } + } + } + } + + for (int index = _list_com; index < _list_com + _list_noncom; index++) { + Object* critter = _combat_list[index]; + if (critter != gDude) { + 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + } + } + } + } + } + + gCombatState |= COMBAT_STATE_0x08; + _caiTeamCombatExit(); +} + +// 0x4227DC +void _combat_turn_run() +{ + while (_combat_turn_running > 0) { + _process_bk(); + } +} + +// 0x4227F4 +int _combat_input() +{ + while ((gCombatState & COMBAT_STATE_0x02) != 0) { + if ((gCombatState & COMBAT_STATE_0x08) != 0) { + break; + } + + if ((gDude->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 (gDude->data.critter.combat.ap <= 0 && _combat_free_move <= 0) { + break; + } + + if (keyCode == KEY_SPACE) { + break; + } + + if (keyCode == KEY_RETURN) { + combatAttemptEnd(); + } else { + _scripts_check_state_in_combat(); + gameHandleKey(keyCode, true); + } + } + + int v4 = _game_user_wants_to_quit; + if (_game_user_wants_to_quit == 1) { + _game_user_wants_to_quit = 0; + } + + if ((gCombatState & COMBAT_STATE_0x08) != 0) { + gCombatState &= ~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; +} + +// 0x422914 +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; + + if (isInCombat()) { + if (object->cid != -1) { + _aiInfoList[object->cid].field_C = 0; + } + } + } +} + +// 0x42299C +int _combat_turn(Object* a1, bool a2) +{ + _combat_turn_obj = a1; + + attackInit(&_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 == gDude) { + keyboardReset(); + interfaceRenderArmorClass(true); + _combat_free_move = 2 * perkGetRank(gDude, PERK_BONUS_MOVE); + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } else { + soundContinueAll(); + } + + bool scriptOverrides = false; + if (a1->sid != -1) { + scriptSetObjects(a1->sid, NULL, NULL); + scriptSetFixedParam(a1->sid, 4); + scriptExecProc(a1->sid, SCRIPT_PROC_COMBAT); + + Script* scr; + if (scriptGetScript(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 == gDude) { + gameUiEnable(); + _gmouse_3d_refresh(); + + if (_gcsd != NULL) { + _combat_attack_this(_gcsd->defender); + } + + if (!a2) { + gCombatState |= 0x02; + } + + interfaceBarEndButtonsRenderGreenLights(); + + for (int index = 0; index < _list_total; index++) { + _combat_update_critter_outline_for_los(_combat_list[index], false); + } + + if (_combat_highlight != 0) { + _combat_outline_on(); + } + + if (_combat_input() == -1) { + gameUiDisable(1); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_WATCH); + a1->data.critter.combat.damageLastTurn = 0; + interfaceBarEndButtonsRenderRedLights(); + _combat_outline_off(); + interfaceRenderActionPoints(-1, -1); + interfaceRenderArmorClass(true); + _combat_free_move = 0; + return -1; + } + } else { + Rect rect; + if (objectEnableOutline(a1, &rect) == 0) { + tileWindowRefreshRect(&rect, a1->elevation); + } + + _combat_ai(a1, _gcsd != NULL ? _gcsd->defender : NULL); + } + } + + while (_combat_turn_running > 0) { + _process_bk(); + } + + if (a1 == gDude) { + gameUiDisable(1); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_WATCH); + interfaceBarEndButtonsRenderRedLights(); + _combat_outline_off(); + interfaceRenderActionPoints(-1, -1); + _combat_turn_obj = NULL; + interfaceRenderArmorClass(true); + _combat_turn_obj = gDude; + } else { + Rect rect; + if (objectDisableOutline(a1, &rect) == 0) { + tileWindowRefreshRect(&rect, a1->elevation); + } + } + } + + if ((gDude->data.critter.combat.results & DAM_DEAD) != 0) { + return -1; + } + + if (a1 != gDude || _combat_elev == gDude->elevation) { + _combat_free_move = 0; + return 0; + } + + return -1; +} + +// 0x422C60 +bool _combat_should_end() +{ + if (_list_com <= 1) { + return true; + } + + int index; + for (index = 0; index < _list_com; index++) { + if (_combat_list[index] == gDude) { + break; + } + } + + if (index == _list_com) { + return true; + } + + int team = gDude->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 == gElevation) + || (attack->defender == NULL || attack->defender->elevation == gElevation)) { + int v3 = gCombatState & 0x01; + + _combat_begin(NULL); + + int v6; + + // TODO: Not sure. + if (v3 != 0) { + if (_combat_turn(gDude, true) == -1) { + v6 = -1; + } else { + int index; + for (index = 0; index < _list_com; index++) { + if (_combat_list[index] == gDude) { + 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) { + gameUiEnable(); + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + } else { + _gmouse_disable_scrolling(); + interfaceBarEndButtonsHide(true); + _gmouse_enable_scrolling(); + _combat_over(); + scriptsExecMapUpdateProc(); + } + + _combat_end_due_to_load = 0; + + if (_game_user_wants_to_quit == 1) { + _game_user_wants_to_quit = 0; + } + } +} + +// 0x422EC4 +void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation) +{ + attack->attacker = attacker; + attack->hitMode = hitMode; + attack->weapon = critterGetWeaponForHitMode(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 != gDude && hitMode == HIT_MODE_PUNCH && randomBetween(1, 4) == 1) { + int fid = buildFid(1, a1->fid & 0xFFF, ANIM_KICK_LEG, (a1->fid & 0xF000) >> 12, (a1->fid & 0x70000000) >> 28); + if (artExists(fid)) { + hitMode = HIT_MODE_KICK; + } + } + + attackInit(&_main_ctd, a1, a2, hitMode, hitLocation); + debugPrint("computing attack...\n"); + + if (attackCompute(&_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 == gDude) { + interfaceGetCurrentHitMode(&hitMode, &aiming); + } else { + aiming = false; + } + } else { + aiming = true; + } + + int actionPoints = _item_w_mp_cost(a1, _main_ctd.hitMode, aiming); + debugPrint("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 == gDude) { + interfaceRenderActionPoints(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); + debugPrint("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 = tileGetRotationTo(a1->tile, a2->tile); + return tileGetTileInDirection(a1->tile, rotation, 1); +} + +// 0x423128 +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_FLAG_0x80000000) == 0) { + if ((critter->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + roll = ROLL_SUCCESS; + break; + } + + if (critter != attack->defender) { + int v6 = attackDetermineToHit(attack->attacker, attack->attacker->tile, critter, attack->defenderHitLocation, attack->hitMode, 1) / 3; + if (critterIsDead(critter)) { + v6 = 5; + } + + if (randomBetween(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_FLAG_0x80000000) == 0) { + return false; + } + + attack->defender = critter; + attack->tile = critter->tile; + attack->attackerFlags |= DAM_HIT; + attack->defenderHitLocation = HIT_LOCATION_TORSO; + attackComputeDamage(attack, 1, 2); + return true; +} + +// 0x423284 +int _shoot_along_path(Attack* attack, int a2, int a3, int anim) +{ + int v5 = a3; + int v17 = 0; + int v7 = attack->attacker->tile; + + Object* critter = attack->attacker; + while (critter != NULL) { + if (v5 <= 0 && anim != ANIM_FIRE_CONTINUOUS || v7 == a2 || attack->extrasLength >= 6) { + break; + } + + _make_straight_path_func(attack->attacker, v7, a2, NULL, &critter, 32, _obj_shoot_blocking_at); + + if (critter != NULL) { + if (((critter->fid & 0xF000000) >> 24) != OBJ_TYPE_CRITTER) { + break; + } + + int v8 = attackDetermineToHit(attack->attacker, attack->attacker->tile, critter, HIT_LOCATION_TORSO, attack->hitMode, 1); + if (anim == ANIM_FIRE_CONTINUOUS) { + v5 = 1; + } + + int a2a = 0; + while (randomBetween(1, 100) <= v8 && v5 > 0) { + v5 -= 1; + a2a += 1; + } + + if (a2a != 0) { + if (critter == attack->defender) { + v17 += a2a; + } 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; + attackInit(&_shoot_ctd, attack->attacker, critter, attack->hitMode, HIT_LOCATION_TORSO); + _shoot_ctd.attackerFlags |= DAM_HIT; + attackComputeDamage(&_shoot_ctd, a2a, 2); + + if (index == attack->extrasLength) { + attack->extrasDamage[index] = _shoot_ctd.defenderDamage; + attack->extrasFlags[index] = _shoot_ctd.defenderFlags; + attack->extrasKnockback[index] = _shoot_ctd.defenderKnockback; + attack->extrasLength++; + } else { + if (anim == ANIM_FIRE_BURST) { + attack->extrasDamage[index] += _shoot_ctd.defenderDamage; + attack->extrasFlags[index] |= _shoot_ctd.defenderFlags; + attack->extrasKnockback[index] += _shoot_ctd.defenderKnockback; + } + } + } + } + + v7 = critter->tile; + } + } + + if (anim == ANIM_FIRE_CONTINUOUS) { + v17 = 0; + } + + return v17; +} + +// 0x423488 +int _compute_spray(Attack* attack, int accuracy, int* a3, int* a4, int anim) +{ + *a3 = 0; + + int ammoQuantity = ammoGetQuantity(attack->weapon); + int burstRounds = weaponGetBurstRounds(attack->weapon); + if (burstRounds < ammoQuantity) { + ammoQuantity = burstRounds; + } + + *a4 = ammoQuantity; + + int criticalChance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE); + int roll = randomRoll(accuracy, criticalChance, NULL); + + if (roll == ROLL_CRITICAL_FAILURE) { + return roll; + } + + if (roll == ROLL_CRITICAL_SUCCESS) { + accuracy += 20; + } + + int v31; + int v14; + int v33; + int v30; + if (anim == ANIM_FIRE_BURST) { + v33 = ammoQuantity / 3; + if (v33 == 0) { + v33 = 1; + } + + v31 = ammoQuantity / 3; + v30 = ammoQuantity - v33 - v31; + v14 = v33 / 2; + if (v14 == 0) { + v14 = 1; + v33 -= 1; + } + } else { + v31 = 1; + v14 = 1; + v33 = 1; + v30 = 1; + } + + for (int index = 0; index < v14; index += 1) { + if (randomRoll(accuracy, 0, NULL) >= ROLL_SUCCESS) { + *a3 += 1; + } + } + + if (*a3 == 0 && _check_ranged_miss(attack)) { + *a3 = 1; + } + + int range = _item_w_range(attack->attacker, attack->hitMode); + int v19 = _tile_num_beyond(attack->attacker->tile, attack->defender->tile, range); + + *a3 += _shoot_along_path(attack, v19, v33 - *a3, anim); + + int v20; + if (objectGetDistanceBetween(attack->attacker, attack->defender) <= 3) { + v20 = _tile_num_beyond(attack->attacker->tile, attack->defender->tile, 3); + } else { + v20 = attack->defender->tile; + } + + int rotation = tileGetRotationTo(v20, attack->attacker->tile); + int v23 = tileGetTileInDirection(v20, (rotation + 1) % ROTATION_COUNT, 1); + + int v25 = _tile_num_beyond(attack->attacker->tile, v23, range); + + *a3 += _shoot_along_path(attack, v25, v31, anim); + + int v26 = tileGetTileInDirection(v20, (rotation + 5) % ROTATION_COUNT, 1); + + int v28 = _tile_num_beyond(attack->attacker->tile, v26, range); + *a3 += _shoot_along_path(attack, v28, v30, anim); + + if (roll != ROLL_FAILURE || *a3 <= 0 && attack->extrasLength <= 0) { + if (roll >= ROLL_SUCCESS && *a3 == 0 && attack->extrasLength == 0) { + roll = ROLL_FAILURE; + } + } else { + roll = ROLL_SUCCESS; + } + + return roll; +} + +// 0x423714 +int attackComputeEnhancedKnockout(Attack* attack) +{ + if (weaponGetPerk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) { + int difficulty = critterGetStat(attack->attacker, STAT_STRENGTH) - 8; + int chance = randomBetween(1, 100); + if (chance <= difficulty) { + Object* weapon = NULL; + if (attack->defender != gDude) { + weapon = critterGetWeaponForHitMode(attack->defender, HIT_MODE_RIGHT_WEAPON_PRIMARY); + } + + if (!(_attackFindInvalidFlags(attack->defender, weapon) & 1)) { + attack->defenderFlags |= DAM_KNOCKED_OUT; + } + } + } + + return 0; +} + +// 0x42378C +int attackCompute(Attack* attack) +{ + int range = _item_w_range(attack->attacker, attack->hitMode); + int distance = objectGetDistanceBetween(attack->attacker, attack->defender); + + if (range < distance) { + return -1; + } + + int anim = critterGetAnimationForHitMode(attack->attacker, attack->hitMode); + int accuracy = attackDetermineToHit(attack->attacker, attack->attacker->tile, attack->defender, attack->defenderHitLocation, attack->hitMode, 1); + + bool isGrenade = false; + int damageType = weaponGetDamageType(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 = weaponGetAttackTypeForHitMode(attack->weapon, attack->hitMode); + int ammoQuantity = 1; + int damageMultiplier = 2; + int v26 = 1; + + int roll; + + if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) { + roll = _compute_spray(attack, accuracy, &ammoQuantity, &v26, anim); + } else { + int chance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE); + roll = randomRoll(accuracy, chance - _hit_location_penalty[attack->defenderHitLocation], NULL); + } + + if (roll == ROLL_FAILURE) { + if (traitIsSelected(TRAIT_JINXED) || perkHasRank(gDude, PERK_JINXED)) { + if (randomBetween(0, 1) == 1) { + roll = ROLL_CRITICAL_FAILURE; + } + } + } + + if (roll == ROLL_SUCCESS) { + if ((attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) && attack->attacker == gDude) { + if (perkHasRank(attack->attacker, PERK_SLAYER)) { + roll = ROLL_CRITICAL_SUCCESS; + } + + if (perkHasRank(gDude, PERK_SILENT_DEATH) + && !_is_hit_from_front(gDude, attack->defender) + && dudeHasState(DUDE_STATE_SNEAKING) + && gDude != attack->defender->data.critter.combat.whoHitMe) { + damageMultiplier = 4; + } + + if (((attack->hitMode == HIT_MODE_HAMMER_PUNCH || attack->hitMode == HIT_MODE_POWER_KICK) && randomBetween(1, 100) <= 5) + || ((attack->hitMode == HIT_MODE_JAB || attack->hitMode == HIT_MODE_HOOK_KICK) && randomBetween(1, 100) <= 10) + || (attack->hitMode == HIT_MODE_HAYMAKER && randomBetween(1, 100) <= 15) + || (attack->hitMode == HIT_MODE_PALM_STRIKE && randomBetween(1, 100) <= 20) + || (attack->hitMode == HIT_MODE_PIERCING_STRIKE && randomBetween(1, 100) <= 40) + || (attack->hitMode == HIT_MODE_PIERCING_KICK && randomBetween(1, 100) <= 50)) { + roll = ROLL_CRITICAL_SUCCESS; + } + } + } + + if (attackType == ATTACK_TYPE_RANGED) { + attack->ammoQuantity = v26; + + if (roll == ROLL_SUCCESS && attack->attacker == gDude) { + if (perkGetRank(gDude, PERK_SNIPER) != 0) { + int d10 = randomBetween(1, 10); + int luck = critterGetStat(gDude, STAT_LUCK); + if (d10 <= luck) { + roll = ROLL_CRITICAL_SUCCESS; + } + } + } + } else { + if (ammoGetCapacity(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 = attackComputeCriticalHit(attack); + // FALLTHROUGH + case ROLL_SUCCESS: + attack->attackerFlags |= DAM_HIT; + attackComputeEnhancedKnockout(attack); + attackComputeDamage(attack, ammoQuantity, damageMultiplier); + break; + case ROLL_FAILURE: + if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { + _check_ranged_miss(attack); + } + break; + case ROLL_CRITICAL_FAILURE: + attackComputeCriticalFailure(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 = randomBetween(1, distance / 2); + if (throwDistance == 0) { + throwDistance = 1; + } + + int rotation = randomBetween(0, 5); + tile = tileGetTileInDirection(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_FLAG_0x80000000) == 0) { + attack->attackerFlags |= DAM_HIT; + attack->defender = v25; + attackComputeDamage(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); + } + } + + attackComputeDeathFlags(attack); + + return 0; +} + +// compute_explosion_on_extras +// 0x423C10 +void _compute_explosion_on_extras(Attack* attack, int a2, int a3, int a4) +{ + 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) { + debugPrint("\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 = tileGetTileInDirection(v5, rotation, 1)) != v19)) { + v20++; + if (v20 % v22 == 0) { + rotation += 1; + if (rotation == ROTATION_COUNT) { + rotation = ROTATION_NE; + } + } + } else { + v22++; + if (a3 && _item_w_grenade_dmg_radius(attack->weapon) < v22) { + v5 = -1; + } else if (a3 || _item_w_rocket_dmg_radius(attack->weapon) >= v22) { + v5 = tileGetTileInDirection(v19, ROTATION_NE, 1); + } else { + v5 = -1; + } + + v19 = v5; + rotation = ROTATION_SE; + v20 = 0; + } + + if (v5 == -1) { + break; + } + + Object* v11 = _obj_blocking_at(attacker, v5, attack->attacker->elevation); + if (v11 != NULL + && (v11->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER + && (v11->data.critter.combat.results & DAM_DEAD) == 0 + && (v11->flags & OBJECT_FLAG_0x80000000) == 0 + && !_combat_is_shot_blocked(v11, v11->tile, tile, NULL, NULL)) { + if (v11 == attack->attacker) { + attack->attackerFlags &= ~DAM_HIT; + attackComputeDamage(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] == v11) { + break; + } + } + + if (index == attack->extrasLength) { + attack->extrasHitLocation[index] = HIT_LOCATION_TORSO; + attack->extras[index] = v11; + attackInit(&_explosion_ctd, attack->attacker, v11, attack->hitMode, HIT_LOCATION_TORSO); + if (!a4) { + _explosion_ctd.attackerFlags |= DAM_HIT; + attackComputeDamage(&_explosion_ctd, 1, 2); + } + + attack->extrasDamage[index] = _explosion_ctd.defenderDamage; + attack->extrasFlags[index] = _explosion_ctd.defenderFlags; + attack->extrasKnockback[index] = _explosion_ctd.defenderKnockback; + attack->extrasLength += 1; + } + } + } + } +} + +// 0x423EB4 +int attackComputeCriticalHit(Attack* attack) +{ + Object* defender = attack->defender; + if (defender != NULL && _critter_flag_check(defender->pid, 1024)) { + return 2; + } + + if (defender != NULL && (defender->pid >> 24) != OBJ_TYPE_CRITTER) { + return 2; + } + + attack->attackerFlags |= DAM_CRITICAL; + + int chance = randomBetween(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 == gDude) { + criticalHitDescription = &(gPlayerCriticalHitTable[attack->defenderHitLocation][effect]); + } else { + int killType = critterGetKillType(defender); + criticalHitDescription = &(gCriticalHitTables[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 (statRoll(defender, criticalHitDescription->massiveCriticalStat, criticalHitDescription->massiveCriticalStatModifier, NULL) <= ROLL_FAILURE) { + attack->defenderFlags |= criticalHitDescription->massiveCriticalFlags; + attack->criticalMessageId = criticalHitDescription->massiveCriticalMessageId; + } + } + + if ((attack->defenderFlags & DAM_CRIP_RANDOM) != 0) { + attack->defenderFlags &= ~DAM_CRIP_RANDOM; + + switch (randomBetween(0, 3)) { + case 0: + attack->defenderFlags |= DAM_CRIP_LEG_LEFT; + break; + case 1: + attack->defenderFlags |= DAM_CRIP_LEG_RIGHT; + break; + case 2: + attack->defenderFlags |= DAM_CRIP_ARM_LEFT; + break; + case 3: + attack->defenderFlags |= DAM_CRIP_ARM_RIGHT; + break; + } + } + + if (weaponGetPerk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) { + attack->defenderFlags |= DAM_KNOCKED_OUT; + } + + Object* weapon = NULL; + if (defender != gDude) { + weapon = critterGetWeaponForHitMode(defender, HIT_MODE_RIGHT_WEAPON_PRIMARY); + } + + int flags = _attackFindInvalidFlags(defender, weapon); + attack->defenderFlags &= ~flags; + + return criticalHitDescription->damageMultiplier; +} + +// 0x424088 +int _attackFindInvalidFlags(Object* critter, Object* item) +{ + int flags = 0; + + if (critter != NULL && (critter->pid >> 24) == OBJ_TYPE_CRITTER && _critter_flag_check(critter->pid, 64)) { + flags |= DAM_DROP; + } + + if (item != NULL && weaponIsNatural(item)) { + flags |= DAM_DROP; + } + + return flags; +} + +// 0x4240DC +int attackComputeCriticalFailure(Attack* attack) +{ + attack->attackerFlags |= DAM_HIT; + + if (attack->attacker != NULL && _critter_flag_check(attack->attacker->pid, 1024)) { + return 0; + } + + if (attack->attacker == gDude) { + unsigned int gameTime = gameTimeGetTime(); + if (gameTime / GAME_TIME_TICKS_PER_DAY < 6) { + return 0; + } + } + + int attackType = weaponGetAttackTypeForHitMode(attack->weapon, attack->hitMode); + int criticalFailureTableIndex = weaponGetCriticalFailureType(attack->weapon); + if (criticalFailureTableIndex == -1) { + criticalFailureTableIndex = 0; + } + + int chance = randomBetween(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; + attackComputeDamage(attack, ammoQuantity, 2); + } else if ((attack->attackerFlags & DAM_EXPLODE) != 0) { + attackComputeDamage(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 = ammoGetQuantity(attack->weapon); + } else { + attack->attackerFlags &= ~DAM_LOSE_AMMO; + } + } + + if ((attack->attackerFlags & DAM_CRIP_RANDOM) != 0) { + attack->attackerFlags &= ~DAM_CRIP_RANDOM; + + switch (randomBetween(0, 3)) { + case 0: + attack->attackerFlags |= DAM_CRIP_LEG_LEFT; + break; + case 1: + attack->attackerFlags |= DAM_CRIP_LEG_RIGHT; + break; + case 2: + attack->attackerFlags |= DAM_CRIP_ARM_LEFT; + break; + case 3: + attack->attackerFlags |= DAM_CRIP_ARM_RIGHT; + break; + } + } + + 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; + attackComputeDamage(attack, ammoQuantity, 2); + } else { + attack->defender = attack->oops; + } + + if (attack->defender != NULL) { + attack->tile = attack->defender->tile; + } + } + + return 0; +} + +// 0x42436C +int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode) +{ + return attackDetermineToHit(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 attackDetermineToHit(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 attackDetermineToHit(a1, tile, a3, hitLocation, hitMode, 1); +} + +// determine_to_hit +// 0x4243A8 +int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6) +{ + Object* weapon = critterGetWeaponForHitMode(attacker, hitMode); + + bool targetIsCritter = defender != NULL + ? ((defender->fid & 0xF000000) >> 24) == 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 = skillGetValue(attacker, SKILL_UNARMED); + } else { + accuracy = _item_w_skill_level(attacker, hitMode); + + int modifier = 0; + + int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); + if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { + isRangedWeapon = true; + + int v29 = 0; + int v25 = 0; + + int weaponPerk = weaponGetPerk(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 = objectGetDistanceBetweenTiles(attacker, tile, defender, defender->tile); + } else { + modifier = 0; + } + + if (modifier >= v25) { + int penalty = attacker == gDude + ? v29 * (perception - 2) + : v29 * perception; + + modifier -= penalty; + } else { + modifier += v25; + } + + if (-2 * perception > modifier) { + modifier = -2 * perception; + } + + if (attacker == gDude) { + modifier -= 2 * perkGetRank(gDude, 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 == gDude && traitIsSelected(TRAIT_ONE_HANDER)) { + if (weaponIsTwoHanded(weapon)) { + accuracy -= 40; + } else { + accuracy += 20; + } + } + + int minStrength = weaponGetMinStrengthRequired(weapon); + modifier = minStrength - critterGetStat(attacker, STAT_STRENGTH); + if (attacker == gDude && perkGetRank(gDude, PERK_WEAPON_HANDLING) != 0) { + modifier -= 3; + } + + if (modifier > 0) { + accuracy -= 20 * modifier; + } + + if (weaponGetPerk(weapon) == PERK_WEAPON_ACCURATE) { + accuracy += 20; + } + } + + if (targetIsCritter && defender != NULL) { + int armorClass = critterGetStat(defender, STAT_ARMOR_CLASS); + armorClass += weaponGetAmmoArmorClassModifier(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_FLAG_0x800) != 0) { + accuracy += 15; + } + + if (attacker == gDude) { + int lightIntensity; + if (defender != NULL) { + lightIntensity = objectGetLightIntensity(defender); + if (weaponGetPerk(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 != gDude->data.critter.combat.team) { + int combatDifficuly = 1; + configGetInt(&gGameConfig, 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) { + debugPrint("Whoa! Bad skill value in determine_to_hit!\n"); + } + + return accuracy; +} + +// 0x4247B8 +void attackComputeDamage(Attack* attack, int ammoQuantity, int a3) +{ + 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 ((critter->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return; + } + + int damageType = weaponGetDamageType(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 (weaponGetPerk(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 == gDude && traitIsSelected(TRAIT_FINESSE)) { + damageResistance += 30; + } + } + + int damageBonus; + if (attack->attacker == gDude && weaponGetAttackTypeForHitMode(attack->weapon, attack->hitMode) == ATTACK_TYPE_RANGED) { + damageBonus = 2 * perkGetRank(gDude, PERK_BONUS_RANGED_DAMAGE); + } else { + damageBonus = 0; + } + + int combatDifficultyDamageModifier = 100; + if (attack->attacker->data.critter.combat.team != gDude->data.critter.combat.team) { + int combatDifficulty = COMBAT_DIFFICULTY_NORMAL; + configGetInt(&gGameConfig, 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 += weaponGetAmmoDamageResistanceModifier(attack->weapon); + if (damageResistance > 100) { + damageResistance = 100; + } else if (damageResistance < 0) { + damageResistance = 0; + } + + int damageMultiplier = a3 * weaponGetAmmoDamageMultiplier(attack->weapon); + int damageDivisor = weaponGetAmmoDamageDivisor(attack->weapon); + + for (int index = 0; index < ammoQuantity; index++) { + int damage = weaponGetMeleeDamage(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 == gDude) { + if (perkGetRank(attack->attacker, PERK_LIVING_ANATOMY) != 0) { + int kt = critterGetKillType(attack->defender); + if (kt != KILL_TYPE_ROBOT && kt != KILL_TYPE_ALIEN) { + *damagePtr += 5; + } + } + + if (perkGetRank(attack->attacker, PERK_PYROMANIAC) != 0) { + if (weaponGetDamageType(attack->attacker, attack->weapon) == DAMAGE_TYPE_FIRE) { + *damagePtr += 5; + } + } + } + + if (knockbackDistancePtr != NULL + && (critter->flags & OBJECT_FLAG_0x800) == 0 + && (damageType == DAMAGE_TYPE_EXPLOSION || attack->weapon == NULL || weaponGetAttackTypeForHitMode(attack->weapon, attack->hitMode) == ATTACK_TYPE_MELEE) + && (critter->pid >> 24) == OBJ_TYPE_CRITTER + && _critter_flag_check(critter->pid, 0x4000) == 0) { + bool shouldKnockback = true; + bool hasStonewall = false; + if (critter == gDude) { + if (perkGetRank(critter, PERK_STONEWALL) != 0) { + int chance = randomBetween(0, 100); + hasStonewall = true; + if (chance < 50) { + shouldKnockback = false; + } + } + } + + if (shouldKnockback) { + int knockbackDistanceDivisor = weaponGetPerk(attack->weapon) == PERK_WEAPON_KNOCKBACK ? 5 : 10; + + *knockbackDistancePtr = *damagePtr / knockbackDistanceDivisor; + + if (hasStonewall) { + *knockbackDistancePtr /= 2; + } + } + } +} + +// 0x424BAC +void attackComputeDeathFlags(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 && (attacker->fid & 0xF000000) >> 24 == 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 && (defender->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER; + + if (!defenderIsCritter && !v5) { + bool v9 = objectIsPartyMember(attack->defender) && objectIsPartyMember(attack->attacker) ? false : true; + if (v9) { + if (defender != NULL) { + if (defender->sid != -1) { + scriptSetFixedParam(defender->sid, attack->attackerDamage); + scriptSetObjects(defender->sid, attack->attacker, attack->weapon); + scriptExecProc(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 != gDude) { + _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); + } + } + } + + scriptSetObjects(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) { + scriptSetObjects(attack->attacker->sid, NULL, attack->defender); + scriptSetFixedParam(attack->attacker->sid, 2); + scriptExecProc(attack->attacker->sid, SCRIPT_PROC_COMBAT); + } + } + + for (int index = 0; index < attack->extrasLength; index++) { + Object* obj = attack->extras[index]; + if ((obj->fid & 0xF000000) >> 24 == 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); + } + } + + scriptSetObjects(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) { + scriptSetObjects(attack->attacker->sid, NULL, obj); + scriptSetFixedParam(attack->attacker->sid, 2); + scriptExecProc(attack->attacker->sid, SCRIPT_PROC_COMBAT); + } + } + } + } +} + +// 0x424EE8 +void _check_for_death(Object* object, int damage, int* flags) +{ + if (object == NULL || !_critter_flag_check(object->pid, 0x0400)) { + if (object == NULL || (object->pid >> 24) == OBJ_TYPE_CRITTER) { + if (damage > 0) { + if (critterGetHitPoints(object) - damage <= 0) { + *flags |= DAM_DEAD; + } + } + } + } +} + +// 0x424F2C +void _set_new_results(Object* critter, int flags) +{ + if (critter == NULL) { + return; + } + + if (((critter->fid & 0xF000000) >> 24) != OBJ_TYPE_CRITTER) { + return; + } + + if (_critter_flag_check(critter->pid, 0x0400)) { + return; + } + + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return; + } + + if ((flags & DAM_DEAD) != 0) { + queueRemoveEvents(critter); + } else if ((flags & DAM_KNOCKED_OUT) != 0) { + int endurance = critterGetStat(critter, STAT_ENDURANCE); + queueAddEvent(10 * (35 - 3 * endurance), critter, NULL, EVENT_TYPE_KNOCKOUT); + } + + if (critter == gDude && (flags & (DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT)) != 0) { + critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN); + + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + interfaceUpdateItems(true, leftItemAction, rightItemAction); + } else { + critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN); + } +} + +// 0x425020 +void _damage_object(Object* a1, int damage, bool animated, int a4, Object* a5) +{ + if (a1 == NULL) { + return; + } + + if ((a1->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER) { + return; + } + + if (_critter_flag_check(a1->pid, 1024)) { + return; + } + + if (damage <= 0) { + return; + } + + critterAdjustHitPoints(a1, -damage); + + if (a1 == gDude) { + interfaceRenderHitPoints(animated); + } + + a1->data.critter.combat.damageLastTurn += damage; + + if (!a4) { + // TODO: Not sure about this one. + if (!objectIsPartyMember(a1) || !objectIsPartyMember(a5)) { + scriptSetFixedParam(a1->sid, damage); + scriptExecProc(a1->sid, SCRIPT_PROC_DAMAGE); + } + } + + if ((a1->data.critter.combat.results & DAM_DEAD) != 0) { + scriptSetObjects(a1->sid, a1->data.critter.combat.whoHitMe, NULL); + scriptExecProc(a1->sid, SCRIPT_PROC_DESTROY); + _item_destroy_all_hidden(a1); + + if (a1 != gDude) { + Object* whoHitMe = a1->data.critter.combat.whoHitMe; + if (whoHitMe == gDude || whoHitMe != NULL && whoHitMe->data.critter.combat.team == gDude->data.critter.combat.team) { + bool scriptOverrides = false; + Script* scr; + if (scriptGetScript(a1->sid, &scr) != -1) { + scriptOverrides = scr->scriptOverrides; + } + + if (!scriptOverrides) { + _combat_exps += critterGetExp(a1); + killsIncByType(critterGetKillType(a1)); + } + } + } + + if (a1->sid != -1) { + scriptRemove(a1->sid); + a1->sid = -1; + } + + partyMemberRemove(a1); + } +} + +// Print attack description to monitor. +// +// 0x425170 +void _combat_display(Attack* attack) +{ + MessageListItem messageListItem; + + if (attack->attacker == gDude) { + Object* weapon = critterGetWeaponForHitMode(attack->attacker, attack->hitMode); + int strengthRequired = weaponGetMinStrengthRequired(weapon); + + if (perkGetRank(attack->attacker, PERK_WEAPON_HANDLING) != 0) { + strengthRequired -= 3; + } + + if (weapon != NULL) { + if (strengthRequired > critterGetStat(gDude, STAT_STRENGTH)) { + // You are not strong enough to use this weapon properly. + messageListItem.num = 107; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(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(gDude, STAT_GENDER) == GENDER_MALE) { + // You (male) + messageListItem.num = 506; + } else { + // You (female) + messageListItem.num = 556; + } + + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcpy(you, messageListItem.text); + } + + int baseMessageId; + if (mainCritter == gDude) { + mainCritterName = you; + if (critterGetStat(gDude, STAT_GENDER) == GENDER_MALE) { + baseMessageId = 500; + } else { + baseMessageId = 550; + } + } else if (mainCritter != NULL) { + mainCritterName = objectGetName(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 ((attack->defender->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + if (attack->oops == gDude) { + // 608 (male) - Oops! %s was hit instead of you! + // 708 (female) - Oops! %s was hit instead of you! + messageListItem.num = baseMessageId + 8; + if (messageListGetItem(&gCombatMessageList, &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 = objectGetName(attack->oops); + messageListItem.num = baseMessageId + 9; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + sprintf(text, messageListItem.text, mainCritterName, name); + } + } + } else { + if (attack->attacker == gDude) { + if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) { + // (male) %s missed + messageListItem.num = 515; + } else { + // (female) %s missed + messageListItem.num = 565; + } + + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + sprintf(text, messageListItem.text, you); + } + } else { + const char* name = objectGetName(attack->attacker); + if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) { + // (male) %s missed + messageListItem.num = 615; + } else { + // (female) %s missed + messageListItem.num = 715; + } + + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + sprintf(text, messageListItem.text, name); + } + } + } + + strcat(text, "."); + + displayMonitorAddMessage(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 ((v21->fid & 0xF000000) >> 24 == 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + if (attack->defenderDamage <= 1) { + sprintf(text, messageListItem.text, mainCritterName); + } else { + sprintf(text, messageListItem.text, mainCritterName, attack->defenderDamage); + } + } + } else { + combatCopyDamageAmountDescription(text, v21, attack->defenderDamage); + } + } else { + const char* hitLocationName = hitLocationGetName(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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + if (attack->defenderDamage <= 1) { + sprintf(text, messageListItem.text, mainCritterName, hitLocationName); + } else { + sprintf(text, messageListItem.text, mainCritterName, hitLocationName, attack->defenderDamage); + } + } + } + } + + int combatMessages = 1; + configGetInt(&gGameConfig, 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcat(text, messageListItem.text); + } + + if ((attack->defenderFlags & DAM_DEAD) != 0) { + strcat(text, "."); + displayMonitorAddMessage(text); + + if (attack->defender == gDude) { + 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + sprintf(text, "%s %s", mainCritterName, messageListItem.text); + } + } + } else { + combatAddDamageFlagsDescription(text, attack->defenderFlags, attack->defender); + } + + strcat(text, "."); + + displayMonitorAddMessage(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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + if (attack->attackerDamage <= 1) { + sprintf(text, messageListItem.text, mainCritterName); + } else { + sprintf(text, messageListItem.text, mainCritterName, attack->attackerDamage); + } + } + + combatAddDamageFlagsDescription(text, attack->attackerFlags, attack->attacker); + + strcat(text, "."); + + displayMonitorAddMessage(text); + } + + if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) { + if (attack->attackerDamage > 0) { + combatCopyDamageAmountDescription(text, attack->attacker, attack->attackerDamage); + combatAddDamageFlagsDescription(text, attack->attackerFlags, attack->attacker); + strcat(text, "."); + displayMonitorAddMessage(text); + } + } + } + + for (int index = 0; index < attack->extrasLength; index++) { + Object* critter = attack->extras[index]; + if ((critter->data.critter.combat.results & DAM_DEAD) == 0) { + combatCopyDamageAmountDescription(text, critter, attack->extrasDamage[index]); + combatAddDamageFlagsDescription(text, attack->extrasFlags[index], critter); + strcat(text, "."); + + displayMonitorAddMessage(text); + } + } +} + +// 0x425A9C +void combatCopyDamageAmountDescription(char* dest, Object* critter, int damage) +{ + MessageListItem messageListItem; + char text[40]; + char* name; + + int messageId; + if (critter == gDude) { + text[0] = '\0'; + + if (critterGetStat(gDude, STAT_GENDER) == GENDER_MALE) { + messageId = 500; + } else { + messageId = 550; + } + + // 506 - You + messageListItem.num = messageId + 6; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcpy(text, messageListItem.text); + } + + name = text; + } else { + name = objectGetName(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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + if (damage <= 1) { + sprintf(dest, messageListItem.text, name); + } else { + sprintf(dest, messageListItem.text, name, damage); + } + } +} + +// 0x425BA4 +void combatAddDamageFlagsDescription(char* dest, int flags, Object* critter) +{ + MessageListItem messageListItem; + + int num; + if (critter == gDude) { + 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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcat(dest, messageListItem.text); + } + + // were killed + messageListItem.num = num + 7; + if (messageListGetItem(&gCombatMessageList, &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 (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcat(dest, messageListItem.text); + } + } + + // " and " + messageListItem.num = 108; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcat(dest, messageListItem.text); + } + + messageListItem.num = flagsList[flagsListLength - 1]; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + strcat(dest, messageListItem.text); + } + } +} + +// 0x425E3C +void _combat_anim_begin() +{ + if (++_combat_turn_running == 1 && gDude == _main_ctd.attacker) { + gameUiDisable(1); + gameMouseSetCursor(26); + if (_combat_highlight == 2) { + _combat_outline_off(); + } + } +} + +// 0x425E80 +void _combat_anim_finished() +{ + _combat_turn_running -= 1; + if (_combat_turn_running != 0) { + return; + } + + if (gDude == _main_ctd.attacker) { + gameUiEnable(); + } + + if (_combat_cleanup_enabled) { + _combat_cleanup_enabled = false; + + Object* weapon = critterGetWeaponForHitMode(_main_ctd.attacker, _main_ctd.hitMode); + if (weapon != NULL) { + if (ammoGetCapacity(weapon) > 0) { + int ammoQuantity = ammoGetQuantity(weapon); + ammoSetQuantity(weapon, ammoQuantity - _main_ctd.ammoQuantity); + + if (_main_ctd.attacker == gDude) { + _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 == gDude && _combat_highlight == 2) { + _combat_outline_on(); + } + + if (_scr_end_combat()) { + if ((gDude->data.critter.combat.results & DAM_KNOCKED_OUT) != 0) { + if (attacker->data.critter.combat.team == gDude->data.critter.combat.team) { + _combat_ending_guy = gDude->data.critter.combat.whoHitMe; + } else { + _combat_ending_guy = attacker; + } + } + } + + attackInit(&_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 +void _combat_standup(Object* a1) +{ + int v2; + + v2 = 3; + if (a1 == gDude && perkGetRank(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 == gDude) { + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + + _dude_standup(a1); + + // NOTE: Uninline. + _combat_turn_run(); +} + +// Render two digits. +// +// 0x42603C +void _print_tohit(unsigned char* dest, int destPitch, int accuracy) +{ + CacheEntry* numbersFrmHandle; + int numbersFrmFid = buildFid(6, 82, 0, 0, 0); + unsigned char* numbersFrmData = artLockFrameData(numbersFrmFid, 0, 0, &numbersFrmHandle); + if (numbersFrmData == NULL) { + return; + } + + if (accuracy >= 0) { + blitBufferToBuffer(numbersFrmData + 9 * (accuracy % 10), 9, 17, 360, dest + 9, destPitch); + blitBufferToBuffer(numbersFrmData + 9 * (accuracy / 10), 9, 17, 360, dest, destPitch); + } else { + blitBufferToBuffer(numbersFrmData + 108, 6, 17, 360, dest + 9, destPitch); + blitBufferToBuffer(numbersFrmData + 108, 6, 17, 360, dest, destPitch); + } + + artUnlock(numbersFrmHandle); +} + +// 0x42612C +char* hitLocationGetName(Object* critter, int hitLocation) +{ + MessageListItem messageListItem; + messageListItem.num = 1000 + 10 * _art_alias_num(critter->fid & 0xFFF) + hitLocation; + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + return messageListItem.text; + } + + return NULL; +} + +// 0x4261B4 +void _draw_loc_off(int a1, int a2) +{ + _draw_loc_(a2, _colorTable[992]); +} + +// 0x4261C0 +void _draw_loc_on_(int a1, int a2) +{ + _draw_loc_(a2, _colorTable[31744]); +} + +// 0x4261CC +void _draw_loc_(int eventCode, int color) +{ + color |= 0x3000000; + + if (eventCode >= 4) { + char* name = hitLocationGetName(gCalledShotCritter, _hit_loc_right[eventCode - 4]); + int width = fontGetStringWidth(name); + windowDrawText(gCalledShotWindow, name, 0, 431 - width, _call_ty[eventCode - 4] - 86, color); + } else { + char* name = hitLocationGetName(gCalledShotCritter, _hit_loc_left[eventCode]); + windowDrawText(gCalledShotWindow, name, 0, 74, _call_ty[eventCode] - 86, color); + } +} + +// 0x426218 +int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hitMode) +{ + if (critter == NULL) { + return 0; + } + + if (critter->pid >> 24 != OBJ_TYPE_CRITTER) { + return 0; + } + + gCalledShotCritter = critter; + gCalledShotWindow = windowCreate(CALLED_SHOW_WINDOW_X, CALLED_SHOW_WINDOW_Y, CALLED_SHOW_WINDOW_WIDTH, CALLED_SHOW_WINDOW_HEIGHT, _colorTable[0], WINDOW_FLAG_0x10); + if (gCalledShotWindow == -1) { + return -1; + } + + int fid; + CacheEntry* handle; + unsigned char* data; + + unsigned char* windowBuffer = windowGetBuffer(gCalledShotWindow); + + fid = buildFid(6, 118, 0, 0, 0); + data = artLockFrameData(fid, 0, 0, &handle); + if (data == NULL) { + windowDestroy(gCalledShotWindow); + return -1; + } + + blitBufferToBuffer(data, CALLED_SHOW_WINDOW_WIDTH, CALLED_SHOW_WINDOW_HEIGHT, CALLED_SHOW_WINDOW_WIDTH, windowBuffer, CALLED_SHOW_WINDOW_WIDTH); + artUnlock(handle); + + fid = buildFid(1, critter->fid & 0xFFF, ANIM_CALLED_SHOT_PIC, 0, 0); + data = artLockFrameData(fid, 0, 0, &handle); + if (data != NULL) { + blitBufferToBuffer(data, 170, 225, 170, windowBuffer + CALLED_SHOW_WINDOW_WIDTH * 31 + 168, CALLED_SHOW_WINDOW_WIDTH); + artUnlock(handle); + } + + fid = buildFid(6, 8, 0, 0, 0); + + CacheEntry* upHandle; + unsigned char* up = artLockFrameData(fid, 0, 0, &upHandle); + if (up == NULL) { + windowDestroy(gCalledShotWindow); + return -1; + } + + fid = buildFid(6, 9, 0, 0, 0); + + CacheEntry* downHandle; + unsigned char* down = artLockFrameData(fid, 0, 0, &downHandle); + if (down == NULL) { + artUnlock(upHandle); + windowDestroy(gCalledShotWindow); + return -1; + } + + // Cancel button + int btn = buttonCreate(gCalledShotWindow, 210, 268, 15, 16, -1, -1, -1, KEY_ESCAPE, up, down, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + for (int index = 0; index < 4; index++) { + int probability; + int btn; + + probability = _determine_to_hit(gDude, critter, _hit_loc_left[index], hitMode); + _print_tohit(windowBuffer + CALLED_SHOW_WINDOW_WIDTH * (_call_ty[index] - 86) + 33, CALLED_SHOW_WINDOW_WIDTH, probability); + + btn = buttonCreate(gCalledShotWindow, 33, _call_ty[index] - 90, 128, 20, index, index, -1, index, NULL, NULL, NULL, 0); + buttonSetMouseCallbacks(btn, _draw_loc_on_, _draw_loc_off, NULL, NULL); + _draw_loc_(index, _colorTable[992]); + + probability = _determine_to_hit(gDude, critter, _hit_loc_right[index], hitMode); + _print_tohit(windowBuffer + CALLED_SHOW_WINDOW_WIDTH * (_call_ty[index] - 86) + 453, CALLED_SHOW_WINDOW_WIDTH, probability); + + btn = buttonCreate(gCalledShotWindow, 341, _call_ty[index] - 90, 128, 20, index + 4, index + 4, -1, index + 4, NULL, NULL, NULL, 0); + buttonSetMouseCallbacks(btn, _draw_loc_on_, _draw_loc_off, NULL, NULL); + _draw_loc_(index + 4, _colorTable[992]); + } + + windowRefresh(gCalledShotWindow); + + bool gameUiWasDisabled = gameUiIsDisabled(); + if (gameUiWasDisabled) { + gameUiEnable(); + } + + _gmouse_disable(0); + gameMouseSetCursor(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) { + gameUiDisable(0); + } + + fontSetCurrent(oldFont); + + artUnlock(downHandle); + artUnlock(upHandle); + windowDestroy(gCalledShotWindow); + + if (eventCode == KEY_ESCAPE) { + return -1; + } + + *hitLocation = eventCode < 4 ? _hit_loc_left[eventCode] : _hit_loc_right[eventCode - 4]; + + soundPlayFile("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 = objectGetDistanceBetween(attacker, defender); + if ((defender->data.critter.combat.results & DAM_DEAD) != 0) { + return 4; // defender is dead + } + } + + Object* weapon = critterGetWeaponForHitMode(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 7; // both hands crippled + } + + if ((attacker->data.critter.combat.results & (DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT)) != 0) { + if (weaponIsTwoHanded(weapon)) { + return 6; // crippled one arm for two-handed weapon + } + } + } + + if (_item_w_mp_cost(attacker, hitMode, aiming) > attacker->data.critter.combat.ap) { + return 3; // not enough action points + } + + if (_item_w_range(attacker, hitMode) < range) { + return 2; // target out of range + } + + int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); + + if (ammoGetCapacity(weapon) > 0) { + if (ammoGetQuantity(weapon) == 0) { + return 1; // out of 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 5; // Your aim is blocked + } + } + + return 0; // success +} + +// 0x426744 +bool _combat_to_hit(Object* target, int* accuracy) +{ + int hitMode; + bool aiming; + if (interfaceGetCurrentHitMode(&hitMode, &aiming) == -1) { + return false; + } + + if (_combat_check_bad_shot(gDude, target, hitMode, aiming) != 0) { + return false; + } + + *accuracy = attackDetermineToHit(gDude, gDude->tile, target, HIT_LOCATION_UNCALLED, hitMode, 1); + + return true; +} + +// 0x4267CC +void _combat_attack_this(Object* a1) +{ + if (a1 == NULL) { + return; + } + + if ((gCombatState & 0x02) == 0) { + return; + } + + int hitMode; + bool aiming; + if (interfaceGetCurrentHitMode(&hitMode, &aiming) == -1) { + return; + } + + MessageListItem messageListItem; + Object* item; + char formattedText[80]; + const char* sfx; + + int rc = _combat_check_bad_shot(gDude, a1, hitMode, aiming); + switch (rc) { + case 1: + item = critterGetWeaponForHitMode(gDude, hitMode); + messageListItem.num = 101; // Out of ammo. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, item, hitMode, NULL); + soundPlayFile(sfx); + return; + case 2: + messageListItem.num = 102; // Target out of range. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + case 3: + item = critterGetWeaponForHitMode(gDude, hitMode); + messageListItem.num = 100; // You need %d action points. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + int actionPointsRequired = _item_w_mp_cost(gDude, hitMode, aiming); + sprintf(formattedText, messageListItem.text, actionPointsRequired); + displayMonitorAddMessage(formattedText); + } + return; + case 4: + return; + case 5: + messageListItem.num = 104; // Your aim is blocked. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + case 6: + messageListItem.num = 106; // You cannot use two-handed weapons with a crippled arm. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + case 7: + messageListItem.num = 105; // You cannot use weapons with both arms crippled. + if (messageListGetItem(&gCombatMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return; + } + + if (!isInCombat()) { + STRUCT_664980 stru; + stru.attacker = gDude; + 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(gDude, a1, hitMode, HIT_LOCATION_UNCALLED); + return; + } + + if (aiming != 1) { + debugPrint("Bad called shot value %d\n", aiming); + } + + int hitLocation; + if (calledShotSelectHitLocation(a1, &hitLocation, hitMode) != -1) { + _combat_attack(gDude, a1, hitMode, hitLocation); + } +} + +// Highlights critters. +// +// 0x426AA8 +void _combat_outline_on() +{ + int targetHighlight = TARGET_HIGHLIGHT_TARGETING_ONLY; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &targetHighlight); + if (targetHighlight == TARGET_HIGHLIGHT_OFF) { + return; + } + + if (gameMouseGetMode() != 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 = objectListCreate(-1, gElevation, OBJ_TYPE_CRITTER, &critterList); + for (int index = 0; index < critterListLength; index++) { + Object* critter = critterList[index]; + if (critter != gDude && (critter->data.critter.combat.results & DAM_DEAD) == 0) { + _combat_update_critter_outline_for_los(critter, 1); + } + } + + if (critterListLength != 0) { + objectListFree(critterList); + } + } + + for (int index = 0; index < _list_total; index++) { + _combat_update_critter_outline_for_los(_combat_list[index], 1); + } + + tileWindowRefresh(); +} + +// 0x426BC0 +void _combat_outline_off() +{ + int i; + int v5; + Object** v9; + + if (gCombatState & 1) { + for (i = 0; i < _list_total; i++) { + objectDisableOutline(_combat_list[i], NULL); + } + } else { + v5 = objectListCreate(-1, gElevation, 1, &v9); + for (i = 0; i < v5; i++) { + objectDisableOutline(v9[i], NULL); + objectClearOutline(v9[i], NULL); + } + if (v5) { + objectListFree(v9); + } + } + + tileWindowRefresh(); +} + +// 0x426C64 +void _combat_highlight_change() +{ + int targetHighlight = 2; + configGetInt(&gGameConfig, 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* v9 = a1; + int current = from; + while (v9 != NULL && current != to) { + _make_straight_path_func(a1, current, to, 0, &v9, 32, _obj_shoot_blocking_at); + if (v9 != NULL) { + if ((v9->fid & 0xF000000) >> 24 != OBJ_TYPE_CRITTER && v9 != a4) { + return true; + } + + if (a5 != NULL) { + if (v9 != a4) { + if (a4 != NULL) { + if ((a4->data.critter.combat.results & DAM_DEAD) == 0) { + *a5 += 1; + + if ((a4->flags & OBJECT_FLAG_0x800) != 0) { + *a5 += 1; + } + } + } + } + } + + if ((v9->flags & OBJECT_FLAG_0x800) != 0) { + int rotation = tileGetRotationTo(current, to); + current = tileGetTileInDirection(current, rotation, 1); + } else { + current = v9->tile; + } + } + } + + return false; +} + +// 0x426D94 +int _combat_player_knocked_out_by() +{ + if ((gDude->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; + objectClearOutline(obj, NULL); + + obj->data.critter.combat.whoHitMe = NULL; + _combatai_delete_critter(obj); +} + +// 0x426EC4 +void _combatKillCritterOutsideCombat(Object* critter_obj, char* msg) +{ + if (critter_obj != gDude) { + displayMonitorAddMessage(msg); + scriptExecProc(critter_obj->sid, SCRIPT_PROC_DESTROY); + critterKill(critter_obj, -1, 1); + } +} diff --git a/src/combat.h b/src/combat.h new file mode 100644 index 0000000..9c8060c --- /dev/null +++ b/src/combat.h @@ -0,0 +1,138 @@ +#ifndef COMBAT_H +#define COMBAT_H + +#include "animation.h" +#include "db.h" +#include "combat_defs.h" +#include "message.h" +#include "obj_types.h" +#include "party_member.h" +#include "proto_types.h" + +#define CALLED_SHOW_WINDOW_X (68) +#define CALLED_SHOW_WINDOW_Y (20) +#define CALLED_SHOW_WINDOW_WIDTH (504) +#define CALLED_SHOW_WINDOW_HEIGHT (309) + +extern char _a_1[]; + +extern int _combat_turn_running; +extern int _combatNumTurns; +extern unsigned int gCombatState; +extern STRUCT_510948* _aiInfoList; +extern STRUCT_664980* _gcsd; +extern bool _combat_call_display; +extern const int _hit_location_penalty[HIT_LOCATION_COUNT]; +extern CriticalHitDescription gCriticalHitTables[KILL_TYPE_COUNT][HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT]; +extern CriticalHitDescription gPlayerCriticalHitTable[HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT]; +extern int _combat_end_due_to_load; +extern bool _combat_cleanup_enabled; +extern const int _cf_table[WEAPON_CRITICAL_FAILURE_TYPE_COUNT][WEAPON_CRITICAL_FAILURE_EFFECT_COUNT]; +extern const int _call_ty[4]; +extern const int _hit_loc_left[4]; +extern const int _hit_loc_right[4]; + +extern Attack _main_ctd; +extern MessageList gCombatMessageList; +extern Object* gCalledShotCritter; +extern int gCalledShotWindow; +extern int _combat_elev; +extern int _list_total; +extern Object* _combat_ending_guy; +extern int _list_noncom; +extern Object* _combat_turn_obj; +extern int _combat_highlight; +extern Object** _combat_list; +extern int _list_com; +extern int _combat_exps; +extern int _combat_free_move; +extern Attack _shoot_ctd; +extern Attack _explosion_ctd; + +int combatInit(); +void combatReset(); +void combatExit(); +int _find_cid(int a1, int a2, Object** a3, int a4); +int combatLoad(File* stream); +int combatSave(File* stream); +bool _combat_safety_invalidate_weapon(Object* a1, Object* a2, int hitMode, Object* a4, int* a5); +bool _combat_safety_invalidate_weapon_func(Object* critter, Object* weapon, int hitMode, Object* a4, int* a5, Object* a6); +bool _combatTestIncidentalHit(Object* a1, Object* a2, Object* a3, Object* a4); +Object* _combat_whose_turn(); +void _combat_data_init(Object* obj); +int _combatCopyAIInfo(int a1, int a2); +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); +void _combat_begin(Object* a1); +void _combat_begin_extra(Object* a1); +void _combat_update_critter_outline_for_los(Object* critter, bool a2); +void _combat_over(); +void _combat_over_from_load(); +void _combat_give_exps(int exp_points); +void _combat_add_noncoms(); +int _compare_faster(const void* a1, const void* a2); +void _combat_sequence_init(Object* a1, Object* a2); +void _combat_sequence(); +void combatAttemptEnd(); +void _combat_turn_run(); +int _combat_input(); +void _combat_set_move_all(); +int _combat_turn(Object* a1, bool a2); +bool _combat_should_end(); +void _combat(STRUCT_664980* attack); +void attackInit(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); +bool _check_ranged_miss(Attack* attack); +int _shoot_along_path(Attack* attack, int a2, int a3, int anim); +int _compute_spray(Attack* attack, int accuracy, int* a3, int* a4, int anim); +int attackComputeEnhancedKnockout(Attack* attack); +int attackCompute(Attack* attack); +void _compute_explosion_on_extras(Attack* attack, int a2, int a3, int a4); +int attackComputeCriticalHit(Attack* a1); +int _attackFindInvalidFlags(Object* a1, Object* a2); +int attackComputeCriticalFailure(Attack* attack); +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); +int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6); +void attackComputeDamage(Attack* attack, int ammoQuantity, int a3); +void attackComputeDeathFlags(Attack* attack); +void _apply_damage(Attack* attack, bool animated); +void _check_for_death(Object* a1, int a2, int* a3); +void _set_new_results(Object* a1, int a2); +void _damage_object(Object* a1, int damage, bool animated, int a4, Object* a5); +void _combat_display(Attack* attack); +void combatCopyDamageAmountDescription(char* dest, Object* critter_obj, int damage); +void combatAddDamageFlagsDescription(char* a1, int flags, Object* a3); +void _combat_anim_begin(); +void _combat_anim_finished(); +void _combat_standup(Object* a1); +void _print_tohit(unsigned char* dest, int dest_pitch, int a3); +char* hitLocationGetName(Object* critter, int hitLocation); +void _draw_loc_off(int a1, int a2); +void _draw_loc_on_(int a1, int a2); +void _draw_loc_(int eventCode, int color); +int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hitMode); +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 (gCombatState & COMBAT_STATE_0x01) != 0; +} + +#endif /* COMBAT_H */ diff --git a/src/combat_ai.c b/src/combat_ai.c new file mode 100644 index 0000000..fe7403f --- /dev/null +++ b/src/combat_ai.c @@ -0,0 +1,3349 @@ +#include "combat_ai.h" + +#include "actions.h" +#include "animation.h" +#include "combat.h" +#include "config.h" +#include "core.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_config.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "light.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "proto.h" +#include "proto_instance.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_object.h" +#include "tile.h" + +#include +#include + +// 0x51805C +Object* _combat_obj = NULL; + +// 0x518060 +int gAiPacketsLength = 0; + +// 0x518064 +AiPacket* gAiPackets = NULL; + +// 0x518068 +bool gAiInitialized = false; + +// 0x51806C +const char* gAreaAttackModeKeys[AREA_ATTACK_MODE_COUNT] = { + "always", + "sometimes", + "be_sure", + "be_careful", + "be_absolutely_sure", +}; + +// 0x5180D0 +const char* gAttackWhoKeys[ATTACK_WHO_COUNT] = { + "whomever_attacking_me", + "strongest", + "weakest", + "whomever", + "closest", +}; + +// 0x51809C +const char* gBestWeaponKeys[BEST_WEAPON_COUNT] = { + "no_pref", + "melee", + "melee_over_ranged", + "ranged_over_melee", + "ranged", + "unarmed", + "unarmed_over_thrown", + "random", +}; + +// 0x5180E4 +const char* gChemUseKeys[CHEM_USE_COUNT] = { + "clean", + "stims_when_hurt_little", + "stims_when_hurt_lots", + "sometimes", + "anytime", + "always", +}; + +// 0x5180BC +const char* gDistanceModeKeys[DISTANCE_COUNT] = { + "stay_close", + "charge", + "snipe", + "on_your_own", + "stay", +}; + +// 0x518080 +const char* gRunAwayModeKeys[RUN_AWAY_MODE_COUNT] = { + "none", + "coward", + "finger_hurts", + "bleeding", + "not_feeling_good", + "tourniquet", + "never", +}; + +// 0x5180FC +const char* gDispositionKeys[DISPOSITION_COUNT] = { + "none", + "custom", + "coward", + "defensive", + "aggressive", + "berserk", +}; + +// 0x518114 +const char* gHurtTooMuchKeys[HURT_COUNT] = { + "blind", + "crippled", + "crippled_legs", + "crippled_arms", +}; + +// hurt_too_much +// +// 0x518124 +const 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 +const int _hp_run_away_value[6] = { + 0, + 25, + 40, + 60, + 75, + 100, +}; + +// 0x518150 +Object* _attackerTeamObj = NULL; + +// 0x518154 +Object* _targetTeamObj = NULL; + +// 0x518158 +const 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 +}; + +// 0x51820C +const int _aiPartyMemberDistances[DISTANCE_COUNT] = { + 5, + 7, + 7, + 7, + 50000, +}; + +// 0x518220 +int gLanguageFilter = -1; + +// ai.msg +// +// 0x56D510 +MessageList gCombatAiMessageList; + +// 0x56D518 +char _target_str[260]; + +// 0x56D61C +int _curr_crit_num; + +// 0x56D620 +Object** _curr_crit_list; + +// 0x56D624 +char _attack_str[268]; + +// parse hurt_too_much +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, gHurtTooMuchKeys[i]) == 0) { + *valuePtr |= _rmatchHurtVals[i]; + break; + } + } + + if (i == 4) { + debugPrint("Unrecognized flag: %s\n", str); + } + + str[v10] = tmp; + + if (tmp == '\0') { + break; + } + + str += v10 + 1; + } +} + +// parse behaviour entry +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 +void aiPacketInit(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 aiInit() +{ + int index; + + if (aiMessageListInit() == -1) { + return -1; + } + + gAiPacketsLength = 0; + + Config config; + if (!configInit(&config)) { + return -1; + } + + if (!configRead(&config, "data\\ai.txt", true)) { + return -1; + } + + gAiPackets = internal_malloc(sizeof(*gAiPackets) * config.entriesLength); + if (gAiPackets == NULL) { + goto err; + } + + for (index = 0; index < config.entriesLength; index++) { + aiPacketInit(&(gAiPackets[index])); + } + + for (index = 0; index < config.entriesLength; index++) { + DictionaryEntry* sectionEntry = &(config.entries[index]); + AiPacket* ai = &(gAiPackets[index]); + char* stringValue; + + ai->name = internal_strdup(sectionEntry->key); + + if (!configGetInt(&config, sectionEntry->key, "packet_num", &(ai->packet_num))) goto err; + if (!configGetInt(&config, sectionEntry->key, "max_dist", &(ai->max_dist))) goto err; + if (!configGetInt(&config, sectionEntry->key, "min_to_hit", &(ai->min_to_hit))) goto err; + if (!configGetInt(&config, sectionEntry->key, "min_hp", &(ai->min_hp))) goto err; + if (!configGetInt(&config, sectionEntry->key, "aggression", &(ai->aggression))) goto err; + + if (configGetString(&config, sectionEntry->key, "hurt_too_much", &stringValue)) { + _parse_hurt_str(stringValue, &(ai->hurt_too_much)); + } + + if (!configGetInt(&config, sectionEntry->key, "secondary_freq", &(ai->secondary_freq))) goto err; + if (!configGetInt(&config, sectionEntry->key, "called_freq", &(ai->called_freq))) goto err; + if (!configGetInt(&config, sectionEntry->key, "font", &(ai->font))) goto err; + if (!configGetInt(&config, sectionEntry->key, "color", &(ai->color))) goto err; + if (!configGetInt(&config, sectionEntry->key, "outline_color", &(ai->outline_color))) goto err; + if (!configGetInt(&config, sectionEntry->key, "chance", &(ai->chance))) goto err; + if (!configGetInt(&config, sectionEntry->key, "run_start", &(ai->run.start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "run_end", &(ai->run.end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "move_start", &(ai->move.start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "move_end", &(ai->move.end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "attack_start", &(ai->attack.start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "attack_end", &(ai->attack.end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "miss_start", &(ai->miss.start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "miss_end", &(ai->miss.end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err; + if (!configGetInt(&config, sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err; + + ai->hit[HIT_LOCATION_GROIN].end++; + + if (configGetString(&config, sectionEntry->key, "area_attack_mode", &stringValue)) { + _cai_match_str_to_list(stringValue, gAreaAttackModeKeys, AREA_ATTACK_MODE_COUNT, &(ai->area_attack_mode)); + } else { + ai->run_away_mode = -1; + } + + if (configGetString(&config, sectionEntry->key, "run_away_mode", &stringValue)) { + _cai_match_str_to_list(stringValue, gRunAwayModeKeys, RUN_AWAY_MODE_COUNT, &(ai->run_away_mode)); + + if (ai->run_away_mode >= 0) { + ai->run_away_mode--; + } + } + + if (configGetString(&config, sectionEntry->key, "best_weapon", &stringValue)) { + _cai_match_str_to_list(stringValue, gBestWeaponKeys, BEST_WEAPON_COUNT, &(ai->best_weapon)); + } + + if (configGetString(&config, sectionEntry->key, "distance", &stringValue)) { + _cai_match_str_to_list(stringValue, gDistanceModeKeys, DISTANCE_COUNT, &(ai->distance)); + } + + if (configGetString(&config, sectionEntry->key, "attack_who", &stringValue)) { + _cai_match_str_to_list(stringValue, gAttackWhoKeys, ATTACK_WHO_COUNT, &(ai->attack_who)); + } + + if (configGetString(&config, sectionEntry->key, "chem_use", &stringValue)) { + _cai_match_str_to_list(stringValue, gChemUseKeys, CHEM_USE_COUNT, &(ai->chem_use)); + } + + configGetIntList(&config, sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT); + + if (configGetString(&config, sectionEntry->key, "disposition", &stringValue)) { + _cai_match_str_to_list(stringValue, gDispositionKeys, DISPOSITION_COUNT, &(ai->disposition)); + ai->disposition--; + } + + if (configGetString(&config, sectionEntry->key, "body_type", &stringValue)) { + ai->body_type = internal_strdup(stringValue); + } else { + ai->body_type = NULL; + } + + if (configGetString(&config, sectionEntry->key, "general_type", &stringValue)) { + ai->general_type = internal_strdup(stringValue); + } else { + ai->general_type = NULL; + } + } + + if (index < config.entriesLength) { + goto err; + } + + gAiPacketsLength = config.entriesLength; + + configFree(&config); + + gAiInitialized = true; + + return 0; + +err: + + if (gAiPackets != NULL) { + for (index = 0; index < config.entriesLength; index++) { + AiPacket* ai = &(gAiPackets[index]); + if (ai->name != NULL) { + internal_free(ai->name); + } + + // FIXME: leaking ai->body_type and ai->general_type, does not matter + // because it halts further processing + } + internal_free(gAiPackets); + } + + debugPrint("Error processing ai.txt"); + + configFree(&config); + + return -1; +} + +// 0x4279F8 +void aiReset() +{ +} + +// 0x4279FC +int aiExit() +{ + for (int index = 0; index < gAiPacketsLength; index++) { + AiPacket* ai = &(gAiPackets[index]); + + if (ai->name != NULL) { + internal_free(ai->name); + ai->name = NULL; + } + + if (ai->general_type != NULL) { + internal_free(ai->general_type); + ai->general_type = NULL; + } + + if (ai->body_type != NULL) { + internal_free(ai->body_type); + ai->body_type = NULL; + } + } + + internal_free(gAiPackets); + gAiPacketsLength = 0; + + gAiInitialized = false; + + // NOTE: Uninline. + if (aiMessageListFree() != 0) { + return -1; + } + + return 0; +} + +// 0x427AD8 +int aiLoad(File* stream) +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + int pid = gPartyMemberPids[index]; + if (pid != -1 && (pid >> 24) == OBJ_TYPE_CRITTER) { + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + AiPacket* ai = aiGetPacketByNum(proto->critter.aiPacket); + if (ai->disposition == 0) { + aiPacketRead(stream, ai); + } + } + } + + return 0; +} + +// 0x427B50 +int aiSave(File* stream) +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + int pid = gPartyMemberPids[index]; + if (pid != -1 && (pid >> 24) == OBJ_TYPE_CRITTER) { + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + AiPacket* ai = aiGetPacketByNum(proto->critter.aiPacket); + if (ai->disposition == 0) { + aiPacketWrite(stream, ai); + } + } + } + + return 0; +} + +// 0x427BC8 +int aiPacketRead(File* stream, AiPacket* ai) +{ + if (fileReadInt32(stream, &(ai->packet_num)) == -1) return -1; + if (fileReadInt32(stream, &(ai->max_dist)) == -1) return -1; + if (fileReadInt32(stream, &(ai->min_to_hit)) == -1) return -1; + if (fileReadInt32(stream, &(ai->min_hp)) == -1) return -1; + if (fileReadInt32(stream, &(ai->aggression)) == -1) return -1; + if (fileReadInt32(stream, &(ai->hurt_too_much)) == -1) return -1; + if (fileReadInt32(stream, &(ai->secondary_freq)) == -1) return -1; + if (fileReadInt32(stream, &(ai->called_freq)) == -1) return -1; + if (fileReadInt32(stream, &(ai->font)) == -1) return -1; + if (fileReadInt32(stream, &(ai->color)) == -1) return -1; + if (fileReadInt32(stream, &(ai->outline_color)) == -1) return -1; + if (fileReadInt32(stream, &(ai->chance)) == -1) return -1; + if (fileReadInt32(stream, &(ai->run.start)) == -1) return -1; + if (fileReadInt32(stream, &(ai->run.end)) == -1) return -1; + if (fileReadInt32(stream, &(ai->move.start)) == -1) return -1; + if (fileReadInt32(stream, &(ai->move.end)) == -1) return -1; + if (fileReadInt32(stream, &(ai->attack.start)) == -1) return -1; + if (fileReadInt32(stream, &(ai->attack.end)) == -1) return -1; + if (fileReadInt32(stream, &(ai->miss.start)) == -1) return -1; + if (fileReadInt32(stream, &(ai->miss.end)) == -1) return -1; + + for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) { + AiMessageRange* range = &(ai->hit[index]); + if (fileReadInt32(stream, &(range->start)) == -1) return -1; + if (fileReadInt32(stream, &(range->end)) == -1) return -1; + } + + if (fileReadInt32(stream, &(ai->area_attack_mode)) == -1) return -1; + if (fileReadInt32(stream, &(ai->best_weapon)) == -1) return -1; + if (fileReadInt32(stream, &(ai->distance)) == -1) return -1; + if (fileReadInt32(stream, &(ai->attack_who)) == -1) return -1; + if (fileReadInt32(stream, &(ai->chem_use)) == -1) return -1; + if (fileReadInt32(stream, &(ai->run_away_mode)) == -1) return -1; + + for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { + if (fileReadInt32(stream, &(ai->chem_primary_desire[index])) == -1) return -1; + } + + return 0; +} + +// 0x427E1C +int aiPacketWrite(File* stream, AiPacket* ai) +{ + if (fileWriteInt32(stream, ai->packet_num) == -1) return -1; + if (fileWriteInt32(stream, ai->max_dist) == -1) return -1; + if (fileWriteInt32(stream, ai->min_to_hit) == -1) return -1; + if (fileWriteInt32(stream, ai->min_hp) == -1) return -1; + if (fileWriteInt32(stream, ai->aggression) == -1) return -1; + if (fileWriteInt32(stream, ai->hurt_too_much) == -1) return -1; + if (fileWriteInt32(stream, ai->secondary_freq) == -1) return -1; + if (fileWriteInt32(stream, ai->called_freq) == -1) return -1; + if (fileWriteInt32(stream, ai->font) == -1) return -1; + if (fileWriteInt32(stream, ai->color) == -1) return -1; + if (fileWriteInt32(stream, ai->outline_color) == -1) return -1; + if (fileWriteInt32(stream, ai->chance) == -1) return -1; + if (fileWriteInt32(stream, ai->run.start) == -1) return -1; + if (fileWriteInt32(stream, ai->run.end) == -1) return -1; + if (fileWriteInt32(stream, ai->move.start) == -1) return -1; + if (fileWriteInt32(stream, ai->move.end) == -1) return -1; + if (fileWriteInt32(stream, ai->attack.start) == -1) return -1; + if (fileWriteInt32(stream, ai->attack.end) == -1) return -1; + if (fileWriteInt32(stream, ai->miss.start) == -1) return -1; + if (fileWriteInt32(stream, ai->miss.end) == -1) return -1; + + for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) { + AiMessageRange* range = &(ai->hit[index]); + if (fileWriteInt32(stream, range->start) == -1) return -1; + if (fileWriteInt32(stream, range->end) == -1) return -1; + } + + if (fileWriteInt32(stream, ai->area_attack_mode) == -1) return -1; + if (fileWriteInt32(stream, ai->best_weapon) == -1) return -1; + if (fileWriteInt32(stream, ai->distance) == -1) return -1; + if (fileWriteInt32(stream, ai->attack_who) == -1) return -1; + if (fileWriteInt32(stream, ai->chem_use) == -1) return -1; + if (fileWriteInt32(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 (fileWriteInt32(stream, ai->chem_primary_desire[index]) == -1) return -1; + } + + return 0; +} + +// Get ai from object +// +// 0x4280B4 +AiPacket* aiGetPacket(Object* obj) +{ + // NOTE: Uninline. + AiPacket* ai = aiGetPacketByNum(obj->data.critter.combat.aiPacket); + return ai; +} + +// get ai packet by num +// +// 0x42811C +AiPacket* aiGetPacketByNum(int aiPacketId) +{ + for (int index = 0; index < gAiPacketsLength; index++) { + AiPacket* ai = &(gAiPackets[index]); + if (aiPacketId == ai->packet_num) { + return ai; + } + } + + debugPrint("Missing AI Packet\n"); + + return gAiPackets; +} + +// 0x428184 +int aiGetAreaAttackMode(Object* obj) +{ + AiPacket* ai = aiGetPacket(obj); + return ai->area_attack_mode; +} + +// 0x428190 +int aiGetRunAwayMode(Object* obj) +{ + AiPacket* ai; + int v3; + int v5; + int v6; + int i; + + ai = aiGetPacket(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 >= _hp_run_away_value[i]) { + v3 = i; + } + } + + return v3; +} + +// 0x4281FC +int aiGetBestWeapon(Object* obj) +{ + AiPacket* ai = aiGetPacket(obj); + return ai->best_weapon; +} + +// 0x428208 +int aiGetDistance(Object* obj) +{ + AiPacket* ai = aiGetPacket(obj); + return ai->distance; +} + +// 0x428214 +int aiGetAttackWho(Object* obj) +{ + AiPacket* ai = aiGetPacket(obj); + return ai->attack_who; +} + +// 0x428220 +int aiGetChemUse(Object* obj) +{ + AiPacket* ai = aiGetPacket(obj); + return ai->chem_use; +} + +// 0x42822C +int aiSetAreaAttackMode(Object* critter, int areaAttackMode) +{ + if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) { + return -1; + } + + AiPacket* ai = aiGetPacket(critter); + ai->area_attack_mode = areaAttackMode; + return 0; +} + +// 0x428248 +int aiSetRunAwayMode(Object* obj, int runAwayMode) +{ + if (runAwayMode >= 6) { + return -1; + } + + AiPacket* ai = aiGetPacket(obj); + ai->run_away_mode = runAwayMode; + + int maximumHp = critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS); + ai->min_hp = maximumHp - maximumHp * _hp_run_away_value[runAwayMode] / 100; + + int currentHp = critterGetStat(obj, STAT_CURRENT_HIT_POINTS); + const char* name = critterGetName(obj); + + debugPrint("\n%s minHp = %d; curHp = %d", name, ai->min_hp, currentHp); + + return 0; +} + +// 0x4282D0 +int aiSetBestWeapon(Object* critter, int bestWeapon) +{ + if (bestWeapon >= BEST_WEAPON_COUNT) { + return -1; + } + + AiPacket* ai = aiGetPacket(critter); + ai->best_weapon = bestWeapon; + return 0; +} + +// 0x4282EC +int aiSetDistance(Object* critter, int distance) +{ + if (distance >= DISTANCE_COUNT) { + return -1; + } + + AiPacket* ai = aiGetPacket(critter); + ai->distance = distance; + return 0; +} + +// 0x428308 +int aiSetAttackWho(Object* critter, int attackWho) +{ + if (attackWho >= ATTACK_WHO_COUNT) { + return -1; + } + + AiPacket* ai = aiGetPacket(critter); + ai->attack_who = attackWho; + return 0; +} + +// 0x428324 +int aiSetChemUse(Object* critter, int chemUse) +{ + if (chemUse >= CHEM_USE_COUNT) { + return -1; + } + + AiPacket* ai = aiGetPacket(critter); + ai->chem_use = chemUse; + return 0; +} + +// 0x428340 +int aiGetDisposition(Object* obj) +{ + if (obj == NULL) { + return 0; + } + + AiPacket* ai = aiGetPacket(obj); + return ai->disposition; +} + +// 0x428354 +int aiSetDisposition(Object* obj, int disposition) +{ + if (obj == NULL) { + return -1; + } + + if (disposition == -1 || disposition >= 5) { + return -1; + } + + AiPacket* ai = aiGetPacket(obj); + obj->data.critter.combat.aiPacket = ai->packet_num - (disposition - ai->disposition); + + return 0; +} + +// 0x428398 +int _ai_magic_hands(Object* critter, Object* item, int num) +{ + reg_anim_begin(2); + + reg_anim_animate(critter, ANIM_MAGIC_HANDS_MIDDLE, 0); + + if (reg_anim_end() == 0) { + if (isInCombat()) { + _combat_turn_run(); + } + } + + if (num != -1) { + MessageListItem messageListItem; + messageListItem.num = num; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + const char* critterName = objectGetName(critter); + + char text[200]; + if (item != NULL) { + const char* itemName = objectGetName(item); + sprintf(text, "%s %s %s.", critterName, messageListItem.text, itemName); + } else { + sprintf(text, "%s %s.", critterName, messageListItem.text); + } + + displayMonitorAddMessage(text); + } + } + + return 0; +} + +// ai using drugs +// 0x428480 +int _ai_check_drugs(Object* critter) +{ + if (critterGetBodyType(critter) != BODY_TYPE_BIPED) { + return 0; + } + + int v25 = 0; + int v28 = 0; + int v29 = 0; + Object* v3 = _combatAIInfoGetLastItem(critter); + if (v3 == NULL) { + AiPacket* ai = aiGetPacket(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) + && itemRemove(critter, drug, 1) == 0) { + if (_item_d_take_drug(critter, drug) == -1) { + itemAdd(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 && randomBetween(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 + && itemRemove(critter, drug, 1) == 0) { + if (_item_d_take_drug(critter, drug) == -1) { + itemAdd(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 && itemRemove(critter, v3, 1) == 0) { + if (_item_d_take_drug(critter, v3) == -1) { + itemAdd(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 +void _ai_run_away(Object* a1, Object* a2) +{ + if (a2 == NULL) { + a2 = gDude; + } + + CritterCombatData* combatData = &(a1->data.critter.combat); + + AiPacket* ai = aiGetPacket(a1); + int distance = objectGetDistanceBetween(a1, a2); + if (distance < ai->max_dist) { + combatData->maneuver |= CRITTER_MANUEVER_FLEEING; + + int rotation = tileGetRotationTo(a2->tile, a1->tile); + + int destination; + int actionPoints = combatData->ap; + for (; actionPoints > 0; actionPoints -= 1) { + destination = tileGetTileInDirection(a1->tile, rotation, actionPoints); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + + destination = tileGetTileInDirection(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPoints); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + + destination = tileGetTileInDirection(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPoints); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + } + + if (actionPoints > 0) { + reg_anim_begin(2); + _combatai_msg(a1, NULL, AI_MESSAGE_TYPE_RUN, 0); + reg_anim_obj_run_to_tile(a1, destination, a1->elevation, combatData->ap, 0); + if (reg_anim_end() == 0) { + _combat_turn_run(); + } + } + } else { + combatData->maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; + } +} + +// 0x42899C +int _ai_move_away(Object* a1, Object* a2, int a3) +{ + if (aiGetPacket(a1)->distance == DISTANCE_STAY) { + return -1; + } + + if (objectGetDistanceBetween(a1, a2) <= a3) { + int actionPoints = a1->data.critter.combat.ap; + if (a3 < actionPoints) { + actionPoints = a3; + } + + int rotation = tileGetRotationTo(a2->tile, a1->tile); + + int destination; + int actionPointsLeft = actionPoints; + for (; actionPointsLeft > 0; actionPointsLeft -= 1) { + destination = tileGetTileInDirection(a1->tile, rotation, actionPointsLeft); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + + destination = tileGetTileInDirection(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPointsLeft); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + + destination = tileGetTileInDirection(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPointsLeft); + if (_make_path(a1, a1->tile, destination, NULL, 1) > 0) { + break; + } + } + + if (actionPoints > 0) { + reg_anim_begin(2); + reg_anim_obj_move_to_tile(a1, destination, a1->elevation, actionPoints, 0); + if (reg_anim_end() == 0) { + _combat_turn_run(); + } + } + } + + return 0; +} + +// 0x428AC4 +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 = objectGetDistanceBetween(a1, v1); + if (distance > a2) { + return false; + } + + if (a3 > distance) { + int v2 = objectGetDistanceBetween(a1, v1) - a3; + _ai_move_steps_closer(a1, v1, v2, 0); + } + + return true; +} + +// Compare objects by distance to origin. +// +// 0x428B1C +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 = objectGetDistanceBetween(v1, _combat_obj); + int distance2 = objectGetDistanceBetween(v2, _combat_obj); + + if (distance1 < distance2) { + return -1; + } else if (distance1 > distance2) { + return 1; + } else { + return 0; + } +} + +// 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; +} + +// 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; +} + +// 0x428C3C +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; + } + + _combat_obj = a1; + qsort(_curr_crit_list, _curr_crit_num, sizeof(*_curr_crit_list), _compare_nearer); + + 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 +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; + + _combat_obj = a1; + qsort(_curr_crit_list, _curr_crit_num, sizeof(*_curr_crit_list), _compare_nearer); + + 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 +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; + } + + _combat_obj = a1; + qsort(_curr_crit_list, _curr_crit_num, sizeof(*_curr_crit_list), _compare_nearer); + + 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 (objectIsPartyMember(a1)) { + int disposition = a1 != NULL ? aiGetPacket(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 && aiGetPacket(a1)->distance == 1) { + v2 = false; + } + + attackWho = aiGetPacket(a1)->attack_who; + switch (attackWho) { + case ATTACK_WHO_WHOMEVER_ATTACKING_ME: { + Object* candidate = _combatAIInfoGetLastTarget(gDude); + if (candidate == NULL || a1->data.critter.combat.team == candidate->data.critter.combat.team) { + break; + } + + if (pathfinderFindPath(a1, a1->tile, gDude->data.critter.combat.whoHitMe->tile, NULL, 0, _obj_blocking_at) == 0 + && _combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) != 0) { + debugPrint("\nai_danger_source: %s couldn't attack at target! Picking alternate!", critterGetName(a1)); + break; + } + + if (v2 && critterIsFleeing(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 && critterIsFleeing(targets[index])) { + targets[index] = NULL; + } + } + } + + int (*compareProc)(const void*, const void*); + switch (attackWho) { + case ATTACK_WHO_STRONGEST: + compareProc = _compare_strength; + break; + case ATTACK_WHO_WEAKEST: + compareProc = _compare_weakness; + break; + default: + compareProc = _compare_nearer; + _combat_obj = a1; + break; + } + + qsort(targets, 4, sizeof(*targets), compareProc); + + for (int index = 0; index < 4; index++) { + Object* candidate = targets[index]; + if (candidate != NULL && objectCanHearObject(a1, candidate)) { + if (pathfinderFindPath(a1, a1->tile, candidate->tile, NULL, 0, _obj_blocking_at) != 0 + || _combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) == 0) { + return candidate; + } + debugPrint("\nai_danger_source: I couldn't get at my target! Picking alternate!"); + } + } + + return NULL; +} + +// 0x4291C4 +int _caiSetupTeamCombat(Object* a1, Object* a2) +{ + Object* obj; + + obj = objectFindFirstAtElevation(a1->elevation); + while (obj != NULL) { + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER && obj != gDude) { + obj->data.critter.combat.maneuver |= 0x01; + } + obj = objectFindNextAtElevation(); + } + + _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 +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 lightGetLightLevel() > 62259; + } + + v9 = -1; + + while (1) { + ammo_obj = _inven_find_type(critter_obj, 4, &v9); + if (ammo_obj == NULL) { + break; + } + + if (weaponCanBeReloadedWith(weapon_obj, ammo_obj)) { + if (out_ammo_obj) { + *out_ammo_obj = ammo_obj; + } + return 1; + } + + if (weaponGetAnimationCode(weapon_obj)) { + if (_item_w_range(critter_obj, 2) < 3) { + _inven_unwield(critter_obj, 1); + } + } else { + _inven_unwield(critter_obj, 1); + } + } + + return 0; +} + +// 0x42938C +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 +Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon2, Object* defender) +{ + if (attacker == NULL) { + return NULL; + } + + AiPacket* ai = aiGetPacket(attacker); + if (ai->best_weapon == BEST_WEAPON_RANDOM) { + return randomBetween(1, 100) <= 50 ? weapon1 : weapon2; + } + int minDamage; + int maxDamage; + + int v24 = 0; + int v25 = 999; + int v26 = 999; + int avgDamage1 = 0; + + Attack attack; + attackInit(&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 = weaponGetAttackTypeForHitMode(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY); + if (weaponGetDamageMinMax(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 (weaponGetPerk(weapon2) != -1) { + avgDamage1 *= 5; + } + + if (defender != NULL) { + if (_combat_safety_invalidate_weapon(attacker, weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) { + v24 = 1; + } + } + + if (weaponIsNatural(weapon1)) { + return weapon1; + } + } else { + distance = objectGetDistanceBetween(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 = weaponGetAttackTypeForHitMode(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY); + if (weaponGetDamageMinMax(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 (weaponGetPerk(weapon2) != -1) { + avgDamage2 *= 5; + } + + if (defender != NULL) { + if (_combat_safety_invalidate_weapon(attacker, weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) { + v23 = 1; + } + } + + if (weaponIsNatural(weapon2)) { + return weapon2; + } + } else { + if (distance == 0) { + distance = objectGetDistanceBetween(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 itemGetCost(weapon2) > itemGetCost(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 +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_LEFT | DAM_CRIP_ARM_RIGHT)) != 0 && weaponIsTwoHanded(weapon)) { + return false; + } + + int rotation = critter->rotation + 1; + int animationCode = weaponGetAnimationCode(weapon); + int v9 = weaponGetAnimationForHitMode(weapon, hitMode); + int fid = buildFid(1, critter->fid & 0xFFF, v9, animationCode, rotation); + if (!artExists(fid)) { + return false; + } + + int skill = weaponGetSkillForHitMode(weapon, hitMode); + AiPacket* ai = aiGetPacket(critter); + if (skillGetValue(critter, skill) < ai->min_to_hit) { + return false; + } + + int attackType = weaponGetAttackTypeForHitMode(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 = critterGetBodyType(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 = critterGetItem2(critter); + while (true) { + Object* weapon = _inven_find_type(critter, ITEM_TYPE_WEAPON, &token); + if (weapon == NULL) { + break; + } + + if (weapon == rightHandWeapon) { + continue; + } + + if (a2) { + if (weaponGetActionPointCost1(weapon) > critter->data.critter.combat.ap) { + continue; + } + } + + if (!_ai_can_use_weapon(critter, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY)) { + continue; + } + + if (weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY) == ATTACK_TYPE_RANGED) { + if (ammoGetQuantity(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 (!objectIsPartyMember(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 = critterGetArmor(critter); + if (armor != NULL) { + armorScore = armorGetArmorClass(armor); + + for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) { + armorScore += armorGetDamageResistance(armor, damageType); + armorScore += armorGetDamageThreshold(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 = armorGetArmorClass(candidate); + for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) { + candidateScore += armorGetDamageResistance(candidate, damageType); + candidateScore += armorGetDamageThreshold(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 +bool aiCanUseItem(Object* critter, Object* item) +{ + if (critter == NULL) { + return false; + } + + if (item == NULL) { + return false; + } + + AiPacket* ai = aiGetPacketByNum(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 (critterGetBodyType(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 +Object* _ai_search_environ(Object* critter, int itemType) +{ + if (critterGetBodyType(critter) != BODY_TYPE_BIPED) { + return NULL; + } + + Object** objects; + int count = objectListCreate(-1, gElevation, OBJ_TYPE_ITEM, &objects); + if (count == 0) { + return NULL; + } + + _combat_obj = critter; + qsort(objects, count, sizeof(*objects), _compare_nearer); + + int perception = critterGetStat(critter, STAT_PERCEPTION) + 5; + Object* item2 = critterGetItem2(critter); + + Object* foundItem = NULL; + + for (int index = 0; index < count; index++) { + Object* item = objects[index]; + int distance = objectGetDistanceBetween(critter, item); + if (distance > perception) { + break; + } + + if (itemGetType(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 (weaponCanBeReloadedWith(item2, item)) { + foundItem = item; + } + break; + case ITEM_TYPE_DRUG: + case ITEM_TYPE_MISC: + if (aiCanUseItem(critter, item)) { + foundItem = item; + } + break; + } + + if (foundItem != NULL) { + break; + } + } + } + + objectListFree(objects); + + return foundItem; +} + +// 0x429D60 +Object* _ai_retrieve_object(Object* a1, Object* a2) +{ + if (actionPickUp(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 +int _ai_pick_hit_mode(Object* a1, Object* a2, Object* a3) +{ + if (a2 == NULL) { + return HIT_MODE_PUNCH; + } + + if (itemGetType(a2) != ITEM_TYPE_WEAPON) { + return HIT_MODE_PUNCH; + } + + int attackType = weaponGetAttackTypeForHitMode(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 = aiGetPacket(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 (randomBetween(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 || objectGetDistanceBetween(a1, a3) < 10) { + if (randomBetween(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 + || statRoll(a1, STAT_INTELLIGENCE, 0, NULL) <= 1) { + return HIT_MODE_RIGHT_WEAPON_SECONDARY; + } + } + + return HIT_MODE_RIGHT_WEAPON_PRIMARY; +} + +// 0x429FC8 +int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4) +{ + if (actionPoints <= 0) { + return -1; + } + + int distance = aiGetPacket(a1)->distance; + if (distance == DISTANCE_STAY) { + return -1; + } + + if (distance == DISTANCE_STAY_CLOSE) { + if (a2 != gDude) { + int v10 = objectGetDistanceBetween(a1, gDude); + if (v10 > 5 && objectGetDistanceBetween(a2, gDude) > 5 && v10 + actionPoints > 5) { + return -1; + } + } + } + + if (objectGetDistanceBetween(a1, a2) <= 1) { + return -1; + } + + reg_anim_begin(2); + + 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 (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_blocking_at) == 0) { + _moveBlockObj = NULL; + if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_ai_blocking_at) == 0 + && _moveBlockObj != NULL + && (_moveBlockObj->pid >> 24) == 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_FLAG_0x800) != 0) { + reg_anim_obj_run_to_obj(a1, a2, actionPoints, 0); + } else { + reg_anim_obj_run_to_tile(a1, tile, a1->elevation, actionPoints, 0); + } + } else { + if ((a2->flags & OBJECT_FLAG_0x800) != 0) { + reg_anim_obj_move_to_obj(a1, a2, actionPoints, 0); + } else { + reg_anim_obj_move_to_tile(a1, tile, a1->elevation, actionPoints, 0); + } + } + + if (reg_anim_end() != 0) { + return -1; + } + + _combat_turn_run(); + + return 0; +} + +// 0x42A1D4 +int _cai_retargetTileFromFriendlyFire(Object* a1, Object* a2, int* a3) +{ + if (a1 == NULL) { + return -1; + } + + if (a2 == NULL) { + return -1; + } + + if (a3 == NULL) { + return -1; + } + + if (*a3 == -1) { + return -1; + } + + if (_curr_crit_num == 0) { + return -1; + } + + int tiles[32]; + + STRUCT_832 v1; + v1.field_0 = a1; + v1.field_4 = a2; + v1.field_32C = a1->data.critter.combat.team; + v1.field_330 = _combatai_rating(a1); + v1.field_328 = 0; + v1.field_338 = tiles; + v1.field_334 = *a3 != a1->tile; + v1.field_33C = 0; + v1.field_340 = critterGetStat(a1, 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 == v1.field_32C + && _combatAIInfoGetLastTarget(obj) == v1.field_4 + && obj != v1.field_0) { + int v10 = _combatai_rating(obj); + if (v10 >= v1.field_330) { + v1.field_8[v1.field_328] = obj; + v1.field_198[v1.field_328] = v10; + v1.field_328 += 1; + } + } + } + + _combat_obj = a1; + + qsort(v1.field_8, v1.field_328, sizeof(*v1.field_8), _compare_nearer); + + if (_cai_retargetTileFromFriendlyFireSubFunc(&v1, *a3) == 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, a1->elevation) == 0) { + int distance = tileDistanceBetween(*a3, tile); + if (distance < minDistance) { + minDistance = distance; + minDistanceIndex = index; + } + } + } + + if (minDistanceIndex != -1) { + *a3 = tiles[minDistanceIndex]; + } + } + + return 0; +} + +// 0x42A410 +int _cai_retargetTileFromFriendlyFireSubFunc(STRUCT_832* a1, int tile) +{ + if (a1->field_340 <= 0) { + return 0; + } + + int distance = 1; + + for (int index = 0; index < a1->field_328; index++) { + Object* obj = a1->field_8[index]; + if (_cai_attackWouldIntersect(obj, a1->field_4, a1->field_0, tile, &distance)) { + debugPrint("In the way!"); + + a1->field_338[a1->field_33C] = tileGetTileInDirection(tile, (obj->rotation + 1) % ROTATION_COUNT, distance); + a1->field_338[a1->field_33C + 1] = tileGetTileInDirection(tile, (obj->rotation + 5) % ROTATION_COUNT, distance); + + a1->field_340 -= 2; + a1->field_33C += 2; + break; + } + } + + return 0; +} + +// 0x42A518 +bool _cai_attackWouldIntersect(Object* a1, Object* a2, Object* a3, int tile, int* distance) +{ + int hitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY; + bool aiming = false; + if (a1 == gDude) { + interfaceGetCurrentHitMode(&hitMode, &aiming); + } + + Object* v8 = critterGetWeaponForHitMode(a1, hitMode); + if (v8 == NULL) { + return false; + } + + if (_item_w_range(a1, hitMode) < 1) { + return false; + } + + Object* object = NULL; + _make_straight_path_func(a1, a1->tile, a2->tile, NULL, &object, 32, _obj_shoot_blocking_at); + if (object != a3) { + if (!_combatTestIncidentalHit(a1, a2, a3, v8)) { + return false; + } + } + + return true; +} + +// 0x42A5B8 +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 +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 = aiGetPacket(a1); + if (randomBetween(1, ai->called_freq) == 1) { + combat_difficulty = 1; + configGetInt(&gGameConfig, 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 = randomBetween(0, 8); + v7 = _determine_to_hit(a1, a2, a3, v5); + if (v7 < ai->min_to_hit) { + v5 = 3; + } + } + } + } + } + + return v5; +} + +// 0x42A748 +int _ai_attack(Object* a1, Object* a2, int a3) +{ + int v6; + + if (a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) { + return -1; + } + + reg_anim_begin(2); + reg_anim_set_rotation_to_tile(a1, a2->tile); + reg_anim_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 +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 = critterGetItem2(a1); + if (weapon != NULL && itemGetType(weapon) != ITEM_TYPE_WEAPON) { + weapon = NULL; + } + + int hitMode = _ai_pick_hit_mode(a1, weapon, a2); + int minToHit = aiGetPacket(a1)->min_to_hit; + + int actionPoints = a1->data.critter.combat.ap; + int v31 = 0; + int v42 = 0; + if (weapon == NULL) { + if (critterGetBodyType(a2) != BODY_TYPE_BIPED + || ((a2->fid & 0xF000) >> 12 != 0) + || !artExists(buildFid(1, a1->fid & 0xFFF, ANIM_THROW_PUNCH, 0, a1->rotation + 1)) + || _combat_safety_invalidate_weapon(a1, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a2, &v31)) { + _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 == 1) { + // 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 = sfxBuildWeaponName(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 = sfxBuildWeaponName(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 = sfxBuildWeaponName(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 == 3 || reason == 6 || reason == 7) { + // 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 == 2) { + // target out of range + int accuracy = _determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, v30); + if (accuracy < minToHit) { + const char* name = critterGetName(a1); + debugPrint("%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) { + if (_ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, v38) == -1) { + return -1; + } + } + v38 = 0; + } + } else if (reason == 5) { + // aim is blocked + if (_ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, v38) == -1) { + return -1; + } + v38 = 0; + } else if (reason == 0) { + 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 = critterGetName(a1); + debugPrint("%s: FLEEING: Can't possibly Hit Target!", name); + _ai_run_away(a1, a2); + return 0; + } + + if (actionPoints > 0) { + int v24 = pathfinderFindPath(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 = tileGetTileInDirection(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 = critterGetName(a1); + debugPrint("%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 && lightGetLightLevel() < 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 = critterGetItem2(critter_obj); + if (weapon_obj == NULL) { + return; + } + + v5 = ammoGetQuantity(weapon_obj); + if (v5 < ammoGetCapacity(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 && objectIsPartyMember(critter_obj)) { + v10 = _gsound_compute_relative_volume(critter_obj); + sfx = sfxBuildWeaponName(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 = internal_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) { + internal_free(_curr_crit_list); + } + + _curr_crit_num = 0; +} + +// 0x42AFDC +int _cai_perform_distance_prefs(Object* a1, Object* a2) +{ + if (a1->data.critter.combat.ap <= 0) { + return -1; + } + + int distance = aiGetPacket(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 != gDude) { + int distance = objectGetDistanceBetween(a1, gDude); + if (distance > 5) { + _ai_move_steps_closer(a1, gDude, distance - 5, 0); + } + } + break; + case DISTANCE_CHARGE: + if (a2 != NULL) { + _ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, 1); + } + break; + case DISTANCE_SNIPE: + if (a2 != NULL) { + if (objectGetDistanceBetween(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) { + reg_anim_begin(2); + reg_anim_obj_move_to_tile(a1, tile, a1->elevation, a1->data.critter.combat.ap, 0); + if (reg_anim_end() != 0) { + return -1; + } + _combat_turn_run(); + } + + return 0; +} + +// 0x42B100 +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 _hp_run_away_value[run_away_mode]; + } else if (run_away_mode == -1) { + return ai->min_hp; + } + + return 0; +} + +// 0x42B130 +void _combat_ai(Object* a1, Object* a2) +{ + AiPacket* ai = aiGetPacket(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 = critterGetName(a1); + debugPrint("\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 = critterGetName(a1); + debugPrint("%s: FLEEING: I'm Hurt!", name); + _ai_run_away(a1, a2); + return; + } + + if (_ai_check_drugs(a1)) { + const char* name = critterGetName(a1); + debugPrint("%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 + && objectGetDistanceBetween(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 && !objectIsPartyMember(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 = critterGetName(a1); + debugPrint("%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 (objectGetDistanceBetween(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 = gDude; + if (objectIsPartyMember(a1)) { + // NOTE: Uninline + int distance = aiGetDistance(a1); + if (distance != -1) { + v21 = _aiPartyMemberDistances[distance]; + } + } + } + + if (a2 == NULL && v20 != NULL && objectGetDistanceBetween(a1, v20) > v21) { + int v23 = objectGetDistanceBetween(a1, v20); + _ai_move_steps_closer(a1, v20, v23 - v21, 0); + } else { + if (a1->data.critter.combat.ap > 0) { + debugPrint("\n>>>NOTE: %s had extra AP's to use!<<<", critterGetName(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 != gDude->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) { + scriptSetObjects(a1->sid, NULL, NULL); + scriptSetFixedParam(a1->sid, 5); + scriptExecProc(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 || !objectCanHearObject(a1, v4); +} + +// 0x42B504 +int critterSetTeam(Object* obj, int team) +{ + if ((obj->pid >> 24) != 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); + debugPrint("\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; + + objectClearOutline(obj, NULL); + + int outlineType; + if (obj->data.critter.combat.team == gDude->data.critter.combat.team) { + outlineType = OUTLINE_TYPE_2; + } else { + outlineType = OUTLINE_TYPE_HOSTILE; + } + + objectSetOutline(obj, outlineType, NULL); + + if (outlineWasEnabled) { + Rect rect; + objectEnableOutline(obj, &rect); + tileWindowRefreshRect(&rect, obj->elevation); + } + } + + return 0; +} + +// 0x42B5D4 +int critterSetAiPacket(Object* object, int aiPacket) +{ + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + object->data.critter.combat.aiPacket = aiPacket; + + if (_isPotentialPartyMember(object)) { + Proto* proto; + if (protoGetProto(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 ((a1->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + bool combatTaunts = true; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &combatTaunts); + if (!combatTaunts) { + return -1; + } + + if (a1 == gDude) { + return -1; + } + + if (a1->data.critter.combat.results & 0x81) { + return -1; + } + + AiPacket* ai = aiGetPacket(a1); + + debugPrint("%s is using %s packet with a %d%% chance to taunt\n", objectGetName(a1), ai->name, ai->chance); + + if (randomBetween(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 = randomBetween(start, end); + if (!messageListGetItem(&gCombatAiMessageList, &messageListItem)) { + debugPrint("\nERROR: combatai_msg: Couldn't find message # %d for %s", messageListItem.num, critterGetName(a1)); + return -1; + } + + debugPrint("%s said message %d\n", objectGetName(a1), messageListItem.num); + strncpy(string, messageListItem.text, 259); + + // TODO: Get rid of casts. + return reg_anim_11_0(a1, (Object*)type, (AnimationProc*)_ai_print_msg, delay); +} + +// 0x42B80C +int _ai_print_msg(Object* critter, int type) +{ + if (textObjectsGetCount() > 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 = aiGetPacket(critter); + + Rect rect; + if (textObjectAdd(critter, string, ai->font, ai->color, ai->outline_color, &rect) == 0) { + tileWindowRefreshRect(&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 = randomBetween(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)) { + critter = obj; + break; + } + + index += 1; + if (index == _curr_crit_num) { + index = 0; + } + + if (index == start) { + break; + } + } + } + + return critter; +} + +// 0x42B90C +int _combatai_rating(Object* obj) +{ + int melee_damage; + Object* item; + int weapon_damage_min; + int weapon_damage_max; + + if (obj == NULL) { + return 0; + } + + if ((obj->fid & 0xF000000) >> 24 != 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 = critterGetItem2(obj); + if (item != NULL && itemGetType(item) == ITEM_TYPE_WEAPON && weaponGetDamageMinMax(item, &weapon_damage_min, &weapon_damage_max) != -1 && melee_damage < weapon_damage_max) { + melee_damage = weapon_damage_max; + } + + item = critterGetItem1(obj); + if (item != NULL && itemGetType(item) == ITEM_TYPE_WEAPON && weaponGetDamageMinMax(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 objectCanHearObject(Object* a1, Object* a2) +{ + if (a2 == NULL) { + return false; + } + + int distance = objectGetDistanceBetween(a2, a1); + int perception = critterGetStat(a1, STAT_PERCEPTION); + int sneak = skillGetValue(a2, SKILL_SNEAK); + if (_can_see(a1, a2)) { + int v8 = perception * 5; + if ((a2->flags & OBJECT_FLAG_0x20000) != 0) { + v8 /= 2; + } + + if (a2 == gDude) { + if (dudeIsSneaking()) { + v8 /= 4; + if (sneak > 120) { + v8 -= 1; + } + } else if (dudeHasState(0)) { + v8 = v8 * 2 / 3; + } + } + + if (distance <= v8) { + return true; + } + } + + int v12; + if (isInCombat()) { + v12 = perception * 2; + } else { + v12 = perception; + } + + if (a2 == gDude) { + if (dudeIsSneaking()) { + v12 /= 4; + if (sneak > 120) { + v12 -= 1; + } + } else if (dudeHasState(0)) { + v12 = v12 * 2 / 3; + } + } + + if (distance <= v12) { + return true; + } + + return false; +} + +// Load combatai.msg and apply language filter. +// +// 0x42BB34 +int aiMessageListInit() +{ + if (!messageListInit(&gCombatAiMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "combatai.msg"); + + if (!messageListLoad(&gCombatAiMessageList, path)) { + return -1; + } + + bool languageFilter; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); + + if (languageFilter) { + messageListFilterBadwords(&gCombatAiMessageList); + } + + return 0; +} + +// NOTE: Inlined. +// +// 0x42BBD8 +int aiMessageListFree() +{ + if (!messageListFree(&gCombatAiMessageList)) { + return -1; + } + + return 0; +} + +// 0x42BBF0 +void aiMessageListReloadIfNeeded() +{ + bool languageFilter = false; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); + + if (languageFilter != gLanguageFilter) { + gLanguageFilter = languageFilter; + + if (languageFilter) { + messageListFilterBadwords(&gCombatAiMessageList); + } else { + // NOTE: Uninline. + aiMessageListFree(); + + aiMessageListInit(); + } + } +} + +// 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 (objectCanHearObject(obj, a1)) { + obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; + if ((a1->data.critter.combat.results & DAM_DEAD) != 0) { + if (!objectCanHearObject(obj, obj->data.critter.combat.whoHitMe)) { + debugPrint("\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 (objectCanHearObject(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; + } + } +} diff --git a/src/combat_ai.h b/src/combat_ai.h new file mode 100644 index 0000000..521e566 --- /dev/null +++ b/src/combat_ai.h @@ -0,0 +1,182 @@ +#ifndef COMBAT_AI_H +#define COMBAT_AI_H + +#include "combat_ai_defs.h" +#include "combat_defs.h" +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "party_member.h" + +#include + +#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 STRUCT_832 { + Object* field_0; + Object* field_4; + Object* field_8[100]; + int field_198[100]; + int field_328; + int field_32C; + int field_330; + int field_334; + int* field_338; + int field_33C; + int field_340; +} STRUCT_832; + +extern Object* _combat_obj; +extern int gAiPacketsLength; +extern AiPacket* gAiPackets; +extern bool gAiInitialized; +extern const char* gAreaAttackModeKeys[AREA_ATTACK_MODE_COUNT]; +extern const char* gAttackWhoKeys[ATTACK_WHO_COUNT]; +extern const char* gBestWeaponKeys[BEST_WEAPON_COUNT]; +extern const char* gChemUseKeys[CHEM_USE_COUNT]; +extern const char* gDistanceModeKeys[DISTANCE_COUNT]; +extern const char* gRunAwayModeKeys[RUN_AWAY_MODE_COUNT]; +extern const char* gDispositionKeys[DISPOSITION_COUNT]; +extern const char* gHurtTooMuchKeys[HURT_COUNT]; +extern const int _rmatchHurtVals[5]; +extern const int _hp_run_away_value[6]; +extern Object* _attackerTeamObj; +extern Object* _targetTeamObj; +extern const int _weapPrefOrderings[BEST_WEAPON_COUNT + 1][5]; +extern const int _aiPartyMemberDistances[DISTANCE_COUNT]; +extern int gLanguageFilter; + +extern MessageList gCombatAiMessageList; +extern char _target_str[260]; +extern int _curr_crit_num; +extern Object** _curr_crit_list; +extern char _attack_str[268]; + +void _parse_hurt_str(char* str, int* out_value); +int _cai_match_str_to_list(const char* str, const char** list, int count, int* out_value); +void aiPacketInit(AiPacket* ai); +int aiInit(); +void aiReset(); +int aiExit(); +int aiLoad(File* stream); +int aiSave(File* stream); +int aiPacketRead(File* stream, AiPacket* ai); +int aiPacketWrite(File* stream, AiPacket* ai); +AiPacket* aiGetPacket(Object* obj); +AiPacket* aiGetPacketByNum(int aiPacketNum); +int aiGetAreaAttackMode(Object* obj); +int aiGetRunAwayMode(Object* obj); +int aiGetBestWeapon(Object* obj); +int aiGetDistance(Object* obj); +int aiGetAttackWho(Object* obj); +int aiGetChemUse(Object* obj); +int aiSetAreaAttackMode(Object* critter, int areaAttackMode); +int aiSetRunAwayMode(Object* obj, int run_away_mode); +int aiSetBestWeapon(Object* critter, int bestWeapon); +int aiSetDistance(Object* critter, int distance); +int aiSetAttackWho(Object* critter, int attackWho); +int aiSetChemUse(Object* critter, int chemUse); +int aiGetDisposition(Object* obj); +int aiSetDisposition(Object* obj, int a2); +int _ai_magic_hands(Object* a1, Object* a2, int num); +int _ai_check_drugs(Object* critter); +void _ai_run_away(Object* a1, Object* a2); +int _ai_move_away(Object* a1, Object* a2, int a3); +bool _ai_find_friend(Object* a1, int a2, int a3); +int _compare_nearer(const void* a1, const void* a2); +int _compare_strength(const void* p1, const void* p2); +int _compare_weakness(const void* p1, const void* p2); +Object* _ai_find_nearest_team(Object* a1, Object* a2, int a3); +Object* _ai_find_nearest_team_in_combat(Object* a1, Object* a2, int a3); +int _ai_find_attackers(Object* a1, Object** a2, Object** a3, Object** a4); +Object* _ai_danger_source(Object* a1); +int _caiSetupTeamCombat(Object* a1, Object* a2); +int _caiTeamCombatInit(Object** a1, int a2); +void _caiTeamCombatExit(); +int _ai_have_ammo(Object* critter_obj, Object* weapon_obj, Object** out_ammo_obj); +bool _caiHasWeapPrefType(AiPacket* ai, int attackType); +Object* _ai_best_weapon(Object* a1, Object* a2, Object* a3, Object* a4); +bool _ai_can_use_weapon(Object* critter, Object* weapon, int hitMode); +Object* _ai_search_inven_weap(Object* critter, int a2, Object* a3); +Object* _ai_search_inven_armor(Object* critter); +bool aiCanUseItem(Object* obj, Object* a2); +Object* _ai_search_environ(Object* critter, int itemType); +Object* _ai_retrieve_object(Object* a1, Object* a2); +int _ai_pick_hit_mode(Object* a1, Object* a2, Object* a3); +int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4); +int _cai_retargetTileFromFriendlyFire(Object* a1, Object* a2, int* a3); +int _cai_retargetTileFromFriendlyFireSubFunc(STRUCT_832* a1, int a2); +bool _cai_attackWouldIntersect(Object* a1, Object* a2, Object* a3, int tile, int* distance); +int _ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4); +int _ai_called_shot(Object* a1, Object* a2, int a3); +int _ai_attack(Object* a1, Object* a2, int a3); +int _ai_try_attack(Object* a1, Object* a2); +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(); +int _cai_perform_distance_prefs(Object* a1, Object* a2); +int _cai_get_min_hp(AiPacket* ai); +void _combat_ai(Object* a1, Object* a2); +bool _combatai_want_to_join(Object* a1); +bool _combatai_want_to_stop(Object* a1); +int critterSetTeam(Object* obj, int team); +int critterSetAiPacket(Object* object, int aiPacket); +int _combatai_msg(Object* a1, Attack* attack, int a3, int a4); +int _ai_print_msg(Object* critter, int type); +Object* _combat_ai_random_target(Attack* attack); +int _combatai_rating(Object* obj); +int _combatai_check_retaliation(Object* a1, Object* a2); +bool objectCanHearObject(Object* a1, Object* a2); +int aiMessageListInit(); +int aiMessageListFree(); +void aiMessageListReloadIfNeeded(); +void _combatai_notify_onlookers(Object* a1); +void _combatai_notify_friends(Object* a1); +void _combatai_delete_critter(Object* obj); + +#endif /* COMBAT_AI_H */ diff --git a/src/combat_ai_defs.h b/src/combat_ai_defs.h new file mode 100644 index 0000000..444c960 --- /dev/null +++ b/src/combat_ai_defs.h @@ -0,0 +1,82 @@ +#ifndef COMBAT_AI_DEFS_H +#define COMBAT_AI_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 /* COMBAT_AI_DEFS_H */ diff --git a/src/combat_defs.h b/src/combat_defs.h new file mode 100644 index 0000000..ba1b448 --- /dev/null +++ b/src/combat_defs.h @@ -0,0 +1,156 @@ +#ifndef COMBAT_DEFS_H +#define COMBAT_DEFS_H + +#include "obj_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 STRUCT_510948 { + Object* field_0; + Object* field_4; + Object* field_8; + int field_C; +} STRUCT_510948; + +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; + +#endif /* COMBAT_DEFS_H */ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..e01642a --- /dev/null +++ b/src/config.c @@ -0,0 +1,549 @@ +#include "config.h" + +#include "db.h" +#include "memory.h" + +#include +#include +#include +#include + +// Last section key read from .INI file. +// +// 0x518224 +char gConfigLastSectionKey[CONFIG_FILE_MAX_LINE_LENGTH] = "unknown"; + +// 0x42BD90 +bool configInit(Config* config) +{ + if (config == NULL) { + return false; + } + + if (dictionaryInit(config, CONFIG_INITIAL_CAPACITY, sizeof(ConfigSection), NULL) != 0) { + return false; + } + + return true; +} + +// 0x42BDBC +void configFree(Config* config) +{ + if (config == NULL) { + return; + } + + for (int sectionIndex = 0; sectionIndex < config->entriesLength; sectionIndex++) { + DictionaryEntry* sectionEntry = &(config->entries[sectionIndex]); + + ConfigSection* section = sectionEntry->value; + for (int keyValueIndex = 0; keyValueIndex < section->entriesLength; keyValueIndex++) { + DictionaryEntry* keyValueEntry = &(section->entries[keyValueIndex]); + + char** value = keyValueEntry->value; + internal_free(*value); + *value = NULL; + } + + dictionaryFree(section); + } + + dictionaryFree(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 configParseCommandLineArguments(Config* config, int argc, char** argv) +{ + if (config == NULL) { + return false; + } + + for (int arg = 0; arg < argc; arg++) { + char* pch = argv[arg]; + + // Find opening bracket. + while (*pch != '\0' && *pch != '[') { + pch++; + } + + if (*pch == '\0') { + continue; + } + + char* sectionKey = pch + 1; + + // Find closing bracket. + while (*pch != '\0' && *pch != ']') { + pch++; + } + + if (*pch == '\0') { + continue; + } + + *pch = '\0'; + + char key[260]; + char value[260]; + if (configParseKeyValue(pch + 1, key, value)) { + if (!configSetString(config, sectionKey, key, value)) { + *pch = ']'; + return false; + } + } + + *pch = ']'; + } + + return true; +} + +// 0x42BF48 +bool configGetString(Config* config, const char* sectionKey, const char* key, char** valuePtr) +{ + if (config == NULL || sectionKey == NULL || key == NULL || valuePtr == NULL) { + return false; + } + + int sectionIndex = dictionaryGetIndexByKey(config, sectionKey); + if (sectionIndex == -1) { + return false; + } + + DictionaryEntry* sectionEntry = &(config->entries[sectionIndex]); + ConfigSection* section = sectionEntry->value; + + int index = dictionaryGetIndexByKey(section, key); + if (index == -1) { + return false; + } + + DictionaryEntry* keyValueEntry = &(section->entries[index]); + *valuePtr = *(char**)keyValueEntry->value; + + return true; +} + +// 0x42BF90 +bool configSetString(Config* config, const char* sectionKey, const char* key, const char* value) +{ + if (config == NULL || sectionKey == NULL || key == NULL || value == NULL) { + return false; + } + + int sectionIndex = dictionaryGetIndexByKey(config, sectionKey); + if (sectionIndex == -1) { + // FIXME: Looks like a bug, this function never returns -1, which will + // eventually lead to crash. + if (configEnsureSectionExists(config, sectionKey) == -1) { + return false; + } + sectionIndex = dictionaryGetIndexByKey(config, sectionKey); + } + + DictionaryEntry* sectionEntry = &(config->entries[sectionIndex]); + ConfigSection* section = sectionEntry->value; + + int index = dictionaryGetIndexByKey(section, key); + if (index != -1) { + DictionaryEntry* keyValueEntry = &(section->entries[index]); + + char** existingValue = keyValueEntry->value; + internal_free(*existingValue); + *existingValue = NULL; + + dictionaryRemoveValue(section, key); + } + + char* valueCopy = internal_strdup(value); + if (valueCopy == NULL) { + return false; + } + + if (dictionaryAddValue(section, key, &valueCopy) == -1) { + internal_free(valueCopy); + return false; + } + + return true; +} + +// 0x42C05C +bool configGetInt(Config* config, const char* sectionKey, const char* key, int* valuePtr) +{ + if (valuePtr == NULL) { + return false; + } + + char* stringValue; + if (!configGetString(config, sectionKey, key, &stringValue)) { + return false; + } + + *valuePtr = atoi(stringValue); + + return true; +} + +// 0x42C090 +bool configGetIntList(Config* config, const char* sectionKey, const char* key, int* arr, int count) +{ + if (arr == NULL || count < 2) { + return false; + } + + char* string; + if (!configGetString(config, sectionKey, key, &string)) { + return false; + } + + char temp[CONFIG_FILE_MAX_LINE_LENGTH]; + strncpy(temp, string, CONFIG_FILE_MAX_LINE_LENGTH - 1); + + char* beginning = temp; + char* pch = beginning; + while (*pch != '\0') { + if (*pch == ',') { + *pch = '\0'; + + *arr++ = atoi(beginning); + + *pch = ','; + + pch++; + beginning = pch; + + count--; + + if (count < 0) { + break; + } + } + + pch++; + } + + if (count <= 1) { + *arr = atoi(beginning); + } + + return true; +} + +// 0x42C160 +bool configSetInt(Config* config, const char* sectionKey, const char* key, int value) +{ + char stringValue[20]; + itoa(value, stringValue, 10); + + return configSetString(config, sectionKey, key, stringValue); +} + +// Reads .INI file into config. +// +// 0x42C280 +bool configRead(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 = fileOpen(filePath, "rb"); + if (stream != NULL) { + while (fileReadString(string, sizeof(string), stream) != NULL) { + configParseLine(config, string); + } + fileClose(stream); + } + } else { + FILE* stream = fopen(filePath, "rt"); + if (stream != NULL) { + while (fgets(string, sizeof(string), stream) != NULL) { + configParseLine(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 configWrite(Config* config, const char* filePath, bool isDb) +{ + if (config == NULL || filePath == NULL) { + return false; + } + + if (isDb) { + File* stream = fileOpen(filePath, "wt"); + if (stream == NULL) { + return false; + } + + for (int sectionIndex = 0; sectionIndex < config->entriesLength; sectionIndex++) { + DictionaryEntry* sectionEntry = &(config->entries[sectionIndex]); + filePrintFormatted(stream, "[%s]\n", sectionEntry->key); + + ConfigSection* section = sectionEntry->value; + for (int index = 0; index < section->entriesLength; index++) { + DictionaryEntry* keyValueEntry = &(section->entries[index]); + filePrintFormatted(stream, "%s=%s\n", keyValueEntry->key, *(char**)keyValueEntry->value); + } + + filePrintFormatted(stream, "\n"); + } + + fileClose(stream); + } else { + FILE* stream = fopen(filePath, "wt"); + if (stream == NULL) { + return false; + } + + for (int sectionIndex = 0; sectionIndex < config->entriesLength; sectionIndex++) { + DictionaryEntry* sectionEntry = &(config->entries[sectionIndex]); + fprintf(stream, "[%s]\n", sectionEntry->key); + + ConfigSection* section = sectionEntry->value; + for (int index = 0; index < section->entriesLength; index++) { + DictionaryEntry* keyValueEntry = &(section->entries[index]); + fprintf(stream, "%s=%s\n", keyValueEntry->key, *(char**)keyValueEntry->value); + } + + 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 [gConfigLastSectionKey] 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 [gConfigLastSectionKey]. +// +// Returns `true` when a section was parsed or key-value pair was parsed and +// added to the config, or `false` otherwise. +// +// 0x42C4BC +bool configParseLine(Config* config, char* string) +{ + char* pch; + + // Find comment marker and truncate the string. + pch = string; + while (*pch != '\0' && *pch != ';') { + pch++; + } + + if (*pch != '\0') { + *pch = '\0'; + } + + // Find opening bracket. + pch = string; + while (*pch != '\0' && *pch != '[') { + pch++; + } + + if (*pch == '[') { + char* sectionKey = pch + 1; + + // Find closing bracket. + while (*pch != '\0' && *pch != ']') { + pch++; + } + + if (*pch == ']') { + *pch = '\0'; + strcpy(gConfigLastSectionKey, sectionKey); + return configTrimString(gConfigLastSectionKey); + } + } + + char key[260]; + char value[260]; + if (!configParseKeyValue(string, key, value)) { + return false; + } + + return configSetString(config, gConfigLastSectionKey, key, value); +} + +// Splits "key=value" pair from [string] and copy appropriate parts into [key] +// and [value] respectively. +// +// Both key and value are trimmed. +// +// 0x42C594 +bool configParseKeyValue(char* string, char* key, char* value) +{ + if (string == NULL || key == NULL || value == NULL) { + return false; + } + + // Find equals character. + char* pch = string; + while (*pch != '\0' && *pch != '=') { + pch++; + } + + if (*pch == '\0') { + return false; + } + + *pch = '\0'; + + strcpy(key, string); + strcpy(value, pch + 1); + + *pch = '='; + + configTrimString(key); + configTrimString(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 +bool configEnsureSectionExists(Config* config, const char* sectionKey) +{ + if (config == NULL || sectionKey == NULL) { + return false; + } + + if (dictionaryGetIndexByKey(config, sectionKey) != -1) { + // Section already exists, no need to do anything. + return true; + } + + ConfigSection section; + if (dictionaryInit(§ion, CONFIG_INITIAL_CAPACITY, sizeof(char**), NULL) == -1) { + return false; + } + + if (dictionaryAddValue(config, sectionKey, §ion) == -1) { + return false; + } + + return true; +} + +// Removes leading and trailing whitespace from the specified string. +// +// 0x42C698 +bool configTrimString(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 configGetDouble(Config* config, const char* sectionKey, const char* key, double* valuePtr) +{ + if (valuePtr == NULL) { + return false; + } + + char* stringValue; + if (!configGetString(config, sectionKey, key, &stringValue)) { + return false; + } + + *valuePtr = strtod(stringValue, NULL); + + return true; +} + +// 0x42C74C +bool configSetDouble(Config* config, const char* sectionKey, const char* key, double value) +{ + char stringValue[32]; + sprintf(stringValue, "%.6f", value); + + return configSetString(config, sectionKey, key, stringValue); +} + +// NOTE: Boolean-typed variant of [configGetInt]. +bool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr) +{ + if (valuePtr == NULL) { + return false; + } + + int integerValue; + if (!configGetInt(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 configSetInt(config, sectionKey, key, value ? 1 : 0); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..09dabf5 --- /dev/null +++ b/src/config.h @@ -0,0 +1,47 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "dictionary.h" + +#include + +#define CONFIG_FILE_MAX_LINE_LENGTH (256) + +// The initial number of sections (or key-value) pairs in the config. +#define CONFIG_INITIAL_CAPACITY (10) + +// A representation of .INI file. +// +// It's implemented as a [Dictionary] whos keys are section names of .INI file, +// and it's values are [ConfigSection] structs. +typedef Dictionary Config; + +// Representation of .INI section. +// +// It's implemented as a [Dictionary] whos keys are names of .INI file +// key-pair values, and it's values are pointers to strings (char**). +typedef Dictionary ConfigSection; + +extern char gConfigLastSectionKey[CONFIG_FILE_MAX_LINE_LENGTH]; + +bool configInit(Config* config); +void configFree(Config* config); +bool configParseCommandLineArguments(Config* config, int argc, char** argv); +bool configGetString(Config* config, const char* sectionKey, const char* key, char** valuePtr); +bool configSetString(Config* config, const char* sectionKey, const char* key, const char* value); +bool configGetInt(Config* config, const char* sectionKey, const char* key, int* valuePtr); +bool configGetIntList(Config* config, const char* section, const char* key, int* arr, int count); +bool configSetInt(Config* config, const char* sectionKey, const char* key, int value); +bool configRead(Config* config, const char* filePath, bool isDb); +bool configWrite(Config* config, const char* filePath, bool isDb); +bool configParseLine(Config* config, char* string); +bool configParseKeyValue(char* string, char* key, char* value); +bool configEnsureSectionExists(Config* config, const char* sectionKey); +bool configTrimString(char* string); +bool configGetDouble(Config* config, const char* sectionKey, const char* key, double* valuePtr); +bool configSetDouble(Config* config, const char* sectionKey, const char* key, double value); + +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 /* CONFIG_H */ diff --git a/src/core.c b/src/core.c new file mode 100644 index 0000000..d948201 --- /dev/null +++ b/src/core.c @@ -0,0 +1,4608 @@ +#include "core.h" + +#include "color.h" +#include "dinput.h" +#include "draw.h" +#include "memory.h" +#include "mmx.h" +#include "text_font.h" +#include "window_manager.h" +#include "window_manager_private.h" + +// NOT USED. +void (*_idle_func)() = NULL; + +// NOT USED. +void (*_focus_func)(int) = NULL; + +// 0x51E23C +int gKeyboardKeyRepeatRate = 80; + +// 0x51E240 +int gKeyboardKeyRepeatDelay = 500; + +// 0x51E244 +bool _keyboard_hooked = false; + +// 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 +unsigned char gMouseDefaultCursor[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 +int _mouse_idling = 0; + +// 0x51E294 +unsigned char* gMouseCursorData = NULL; + +// 0x51E298 +unsigned char* _mouse_shape = NULL; + +// 0x51E29C +unsigned char* _mouse_fptr = NULL; + +// 0x51E2A0 +double gMouseSensitivity = 1.0; + +// 0x51E2A8 +unsigned int _ticker_ = 0; + +// 0x51E2AC +int gMouseButtonsState = 0; + +// 0x51E2B0 +LPDIRECTDRAW gDirectDraw = NULL; + +// 0x51E2B4 +LPDIRECTDRAWSURFACE gDirectDrawSurface1 = NULL; + +// 0x51E2B8 +LPDIRECTDRAWSURFACE gDirectDrawSurface2 = NULL; + +// 0x51E2BC +LPDIRECTDRAWPALETTE gDirectDrawPalette = NULL; + +// NOTE: This value is never set, so it's impossible to understand it's +// meaning. +// +// 0x51E2C4 +void (*_update_palette_func)() = NULL; + +// 0x51E2C8 +bool gMmxEnabled = true; + +// 0x51E2CC +bool gMmxProbed = false; + +// 0x51E2D0 +unsigned char _kb_installed = 0; + +// 0x51E2D4 +bool gKeyboardDisabled = false; + +// 0x51E2D8 +bool gKeyboardNumpadDisabled = false; + +// 0x51E2DC +bool gKeyboardNumlockDisabled = false; + +// 0x51E2E0 +int gKeyboardEventQueueWriteIndex = 0; + +// 0x51E2E4 +int gKeyboardEventQueueReadIndex = 0; + +// 0x51E2E8 +short word_51E2E8 = 0; + +// 0x51E2EA +int gModifierKeysState = 0; + +// TODO: It's _kb_next_ascii_English_US (not implemented yet). +// +// 0x51E2EC +int (*_kb_scan_to_ascii)() = keyboardDequeueLogicalKeyCode; + +// 0x51E2F0 +STRUCT_51E2F0* _vcr_buffer = NULL; + +// number of entries in _vcr_buffer +// 0x51E2F4 +int _vcr_buffer_index = 0; + +// 0x51E2F8 +int _vcr_state = 2; + +// 0x51E2FC +int _vcr_time = 0; + +// 0x51E300 +int _vcr_counter = 0; + +// 0x51E304 +int _vcr_terminate_flags = 0; + +// 0x51E308 +int _vcr_terminated_condition = 0; + +// 0x51E30C +int _vcr_start_time = 0; + +// 0x51E310 +int _vcr_registered_atexit = 0; + +// 0x51E314 +File* _vcr_file = NULL; + +// 0x51E318 +int _vcr_buffer_end = 0; + +// A map of DIK_* constants normalized for QWERTY keyboard. +// +// 0x6ABC70 +unsigned char gNormalizedQwertyKeys[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 +InputEvent gInputEventQueue[40]; + +// 0x6ABF50 +STRUCT_6ABF50 _GNW95_key_time_stamps[256]; + +// 0x6AC750 +int _input_mx; + +// 0x6AC754 +int _input_my; + +// 0x6AC758 +HHOOK _GNW95_keyboardHandle; + +// 0x6AC75C +bool gPaused; + +// 0x6AC760 +int gScreenshotKeyCode; + +// 0x6AC764 +int _using_msec_timer; + +// 0x6AC768 +int gPauseKeyCode; + +// 0x6AC76C +ScreenshotHandler* gScreenshotHandler; + +// 0x6AC770 +int gInputEventQueueReadIndex; + +// 0x6AC774 +unsigned char* gScreenshotBuffer; + +// 0x6AC778 +PauseHandler* gPauseHandler; + +// 0x6AC77C +int gInputEventQueueWriteIndex; + +// 0x6AC780 +bool gRunLoopDisabled; + +// 0x6AC784 +TickerListNode* gTickerListHead; + +// 0x6AC788 +unsigned int gTickerLastTimestamp; + +// 0x6AC790 +bool gCursorIsHidden; + +// x (1) +// 0x6AC794 +int _raw_x; + +// 0x6AC798 +int gMouseCursorHeight; + +// y (1) +// 0x6AC79C +int _raw_y; + +// mouse event (1) +// 0x6AC7A0 +int _raw_buttons; + +// 0x6AC7A4 +int gMouseCursorY; + +// 0x6AC7A8 +int gMouseCursorX; + +// 0x6AC7AC +int _mouse_disabled; + +// 0x6AC7B0 +int gMouseEvent; + +// 0x6AC7B4 +unsigned int _mouse_speed; + +// 0x6AC7B8 +int _mouse_curr_frame; + +// 0x6AC7BC +bool gMouseInitialized; + +// 0x6AC7C0 +int gMouseCursorPitch; + +// 0x6AC7C4 +int gMouseCursorWidth; + +// 0x6AC7C8 +int _mouse_num_frames; + +// 0x6AC7CC +int _mouse_hoty; + +// 0x6AC7D0 +int _mouse_hotx; + +// 0x6AC7D4 +unsigned int _mouse_idle_start_time; + +// 0x6AC7D8 +WindowDrawingProc2* _mouse_blit_trans; + +// 0x6AC7DC +WINDOWDRAWINGPROC _mouse_blit; + +// 0x6AC7E0 +unsigned char _mouse_trans; + +// 0x6AC7E4 +int gMouseRightButtonDownTimestamp; + +// 0x6AC7E8 +int gMouseLeftButtonDownTimestamp; + +// 0x6AC7EC +int gMousePreviousEvent; + +// 0x6AC7F0 +unsigned short gSixteenBppPalette[256]; + +// screen rect +Rect _scr_size; + +// 0x6ACA00 +int gGreenMask; + +// 0x6ACA04 +int gRedMask; + +// 0x6ACA08 +int gBlueMask; + +// 0x6ACA0C +int gBlueShift; + +// 0x6ACA10 +int gRedShift; + +// 0x6ACA14 +int gGreenShift; + +// 0x6ACA18 +void (*_scr_blit)(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) = _GNW95_ShowRect; + +// 0x6ACA1C +void (*_zero_mem)() = NULL; + +// 0x6ACA20 +bool gMmxSupported; + +// 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 +unsigned char gLastVideoModePalette[268]; + +// Ring buffer of keyboard events. +// +// 0x6ACB30 +KeyboardEvent gKeyboardEventsQueue[64]; + +// A map of logical key configurations for physical scan codes [DIK_*]. +// +// 0x6ACC30 +LogicalKeyEntry gLogicalKeyEntries[256]; + +// A state of physical keys [DIK_*] currently pressed. +// +// 0 - key is not pressed. +// 1 - key pressed. +// +// 0x6AD830 +unsigned char gPressedPhysicalKeys[256]; + +// 0x6AD930 +unsigned int _kb_idle_start_time; + +// 0x6AD934 +KeyboardEvent gLastKeyboardEvent; + +// 0x6AD938 +int gKeyboardLayout; + +// The number of keys currently pressed. +// +// 0x6AD93C +unsigned char gPressedPhysicalKeysCount; + +// 0x4C8A70 +int coreInit(int a1) +{ + if (!directInputInit()) { + return -1; + } + + if (keyboardInit() == -1) { + return -1; + } + + if (mouseInit() == -1) { + return -1; + } + + if (_GNW95_input_init() == -1) { + return -1; + } + + _GNW95_hook_input(1); + buildNormalizedQwertyKeys(); + _GNW95_clear_time_stamps(); + + _using_msec_timer = a1; + gInputEventQueueWriteIndex = 0; + gInputEventQueueReadIndex = -1; + _input_mx = -1; + _input_my = -1; + gRunLoopDisabled = 0; + gPaused = false; + gPauseKeyCode = KEY_ALT_P; + gPauseHandler = pauseHandlerDefaultImpl; + gScreenshotHandler = screenshotHandlerDefaultImpl; + gTickerListHead = NULL; + gScreenshotKeyCode = KEY_ALT_C; + + return 0; +} + +// 0x4C8B40 +void coreExit() +{ + _GNW95_hook_keyboard(0); + _GNW95_input_init(); + mouseFree(); + keyboardFree(); + directInputFree(); + + TickerListNode* curr = gTickerListHead; + while (curr != NULL) { + TickerListNode* next = curr->next; + internal_free(curr); + curr = next; + } +} + +// 0x4C8B78 +int _get_input() +{ + int v3; + + _GNW95_process_message(); + + if (!gProgramIsActive) { + _GNW95_lost_focus(); + } + + _process_bk(); + + v3 = dequeueInputEvent(); + if (v3 == -1 && mouseGetEvent() & 0x33) { + mouseGetPosition(&_input_mx, &_input_my); + return -2; + } else { + return _GNW_check_menu_bars(v3); + } + + return -1; +} + +// 0x4C8BDC +void _process_bk() +{ + int v1; + + tickersExecute(); + + if (_vcr_update() != 3) { + _mouse_info(); + } + + v1 = _win_check_all_buttons(); + if (v1 != -1) { + enqueueInputEvent(v1); + return; + } + + v1 = _kb_getch(); + if (v1 != -1) { + enqueueInputEvent(v1); + return; + } +} + +// 0x4C8C04 +void enqueueInputEvent(int a1) +{ + if (a1 == -1) { + return; + } + + if (a1 == gPauseKeyCode) { + pauseGame(); + return; + } + + if (a1 == gScreenshotKeyCode) { + takeScreenshot(); + return; + } + + if (gInputEventQueueWriteIndex == gInputEventQueueReadIndex) { + return; + } + + InputEvent* inputEvent = &(gInputEventQueue[gInputEventQueueWriteIndex]); + inputEvent->logicalKey = a1; + + mouseGetPosition(&(inputEvent->mouseX), &(inputEvent->mouseY)); + + gInputEventQueueWriteIndex++; + + if (gInputEventQueueWriteIndex == 40) { + gInputEventQueueWriteIndex = 0; + return; + } + + if (gInputEventQueueReadIndex == -1) { + gInputEventQueueReadIndex = 0; + } +} + +// 0x4C8C9C +int dequeueInputEvent() +{ + if (gInputEventQueueReadIndex == -1) { + return -1; + } + + InputEvent* inputEvent = &(gInputEventQueue[gInputEventQueueReadIndex]); + int eventCode = inputEvent->logicalKey; + _input_mx = inputEvent->mouseX; + _input_my = inputEvent->mouseY; + + gInputEventQueueReadIndex++; + + if (gInputEventQueueReadIndex == 40) { + gInputEventQueueReadIndex = 0; + } + + if (gInputEventQueueReadIndex == gInputEventQueueWriteIndex) { + gInputEventQueueReadIndex = -1; + gInputEventQueueWriteIndex = 0; + } + + return eventCode; +} + +// 0x4C8D04 +void inputEventQueueReset() +{ + gInputEventQueueReadIndex = -1; + gInputEventQueueWriteIndex = 0; +} + +// 0x4C8D1C +void tickersExecute() +{ + if (gPaused) { + return; + } + + if (gRunLoopDisabled) { + return; + } + +#pragma warning(suppress : 28159) + gTickerLastTimestamp = GetTickCount(); + + TickerListNode* curr = gTickerListHead; + TickerListNode** currPtr = &(gTickerListHead); + + while (curr != NULL) { + TickerListNode* next = curr->next; + if (curr->flags & 1) { + *currPtr = next; + + internal_free(curr); + } else { + curr->proc(); + currPtr = &(curr->next); + } + curr = next; + } +} + +// 0x4C8D74 +void tickersAdd(TickerProc* proc) +{ + TickerListNode* curr = gTickerListHead; + while (curr != NULL) { + if (curr->proc == proc) { + if ((curr->flags & 0x01) != 0) { + curr->flags &= ~0x01; + return; + } + } + curr = curr->next; + } + + curr = internal_malloc(sizeof(*curr)); + curr->flags = 0; + curr->proc = proc; + curr->next = gTickerListHead; + gTickerListHead = curr; +} + +// 0x4C8DC4 +void tickersRemove(TickerProc* proc) +{ + TickerListNode* curr = gTickerListHead; + while (curr != NULL) { + if (curr->proc == proc) { + curr->flags |= 0x01; + return; + } + curr = curr->next; + } +} + +// 0x4C8DE4 +void tickersEnable() +{ + gRunLoopDisabled = false; +} + +// 0x4C8DF0 +void tickersDisable() +{ + gRunLoopDisabled = true; +} + +// 0x4C8DFC +void pauseGame() +{ + if (!gPaused) { + gPaused = true; + + int win = gPauseHandler(); + + while (_get_input() != KEY_ESCAPE) { + } + + gPaused = false; + windowDestroy(win); + } +} + +// 0x4C8E38 +int pauseHandlerDefaultImpl() +{ + int len; + int v1; + int v2; + int win; + unsigned char* buf; + int v6; + int v7; + + len = fontGetStringWidth("Paused") + 32; + v1 = fontGetLineHeight(); + v2 = 3 * v1 + 16; + + win = windowCreate((_scr_size.right - _scr_size.left + 1 - len) / 2, (_scr_size.bottom - _scr_size.top + 1 - v2) / 2, len, v2, 256, 20); + if (win == -1) { + return -1; + } + + windowDrawBorder(win); + buf = windowGetBuffer(win); + fontDrawText(buf + 8 * len + 16, "Paused", len, len, _colorTable[31744]); + + v6 = v2 - 8 - v1; + v7 = fontGetStringWidth("Done"); + // TODO: Incomplete. + // _win_register_text_button(win, (len - v7 - 16) / 2, v6 - 6, -1, -1, -1, 27, "Done", 0); + + windowRefresh(win); + + return win; +} + +// 0x4C8F34 +void pauseHandlerConfigure(int keyCode, PauseHandler* handler) +{ + gPauseKeyCode = keyCode; + + if (handler == NULL) { + handler = pauseHandlerDefaultImpl; + } + + gPauseHandler = handler; +} + +// 0x4C8F4C +void takeScreenshot() +{ + int width = _scr_size.right - _scr_size.left + 1; + int height = _scr_size.bottom - _scr_size.top + 1; + gScreenshotBuffer = internal_malloc(width * height); + if (gScreenshotBuffer == NULL) { + return; + } + + WINDOWDRAWINGPROC v0 = _scr_blit; + _scr_blit = screenshotBlitter; + + WINDOWDRAWINGPROC v2 = _mouse_blit; + _mouse_blit = screenshotBlitter; + + WindowDrawingProc2* v1 = _mouse_blit_trans; + _mouse_blit_trans = NULL; + + windowRefreshAll(&_scr_size); + + _mouse_blit_trans = v1; + _mouse_blit = v2; + _scr_blit = v0; + + unsigned char* palette = _getSystemPalette(); + gScreenshotHandler(width, height, gScreenshotBuffer, palette); + internal_free(gScreenshotBuffer); +} + +// 0x4C8FF0 +void screenshotBlitter(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int width, int height, int destX, int destY) +{ + int destWidth = _scr_size.right - _scr_size.left + 1; + blitBufferToBuffer(src + srcPitch * srcY + srcX, width, height, srcPitch, gScreenshotBuffer + destWidth * destY + destX, destWidth); +} + +// 0x4C9048 +int screenshotHandlerDefaultImpl(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 screenshotHandlerConfigure(int keyCode, ScreenshotHandler* handler) +{ + gScreenshotKeyCode = keyCode; + + if (handler == NULL) { + handler = screenshotHandlerDefaultImpl; + } + + gScreenshotHandler = handler; +} + +// 0x4C9370 +unsigned int _get_time() +{ +#pragma warning(suppress : 28159) + return GetTickCount(); +} + +// 0x4C937C +void coreDelayProcessingEvents(unsigned int delay) +{ + // NOTE: Uninline. + unsigned int start = _get_time(); + unsigned int end = _get_time(); + + // NOTE: Uninline. + unsigned int diff = getTicksBetween(end, start); + while (diff < delay) { + _process_bk(); + + end = _get_time(); + + // NOTE: Uninline. + diff = getTicksBetween(end, start); + } +} + +// 0x4C93B8 +void coreDelay(unsigned int ms) +{ +#pragma warning(suppress : 28159) + unsigned int start = GetTickCount(); + unsigned int diff; + do { + // NOTE: Uninline + diff = getTicksSince(start); + } while (diff < ms); +} + +// 0x4C93E0 +unsigned int getTicksSince(unsigned int start) +{ +#pragma warning(suppress : 28159) + unsigned int end = GetTickCount(); + + // NOTE: Uninline. + return getTicksBetween(end, start); +} + +// 0x4C9400 +unsigned int getTicksBetween(unsigned int end, unsigned int start) +{ + if (start > end) { + return INT_MAX; + } else { + return end - start; + } +} + +// 0x4C9410 +unsigned int _get_bk_time() +{ + return gTickerLastTimestamp; +} + +// 0x4C9490 +void buildNormalizedQwertyKeys() +{ + unsigned char* keys = gNormalizedQwertyKeys; + 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 (gKeyboardLayout) { + case 0: + k = DIK_MINUS; + break; + case 1: + k = DIK_6; + break; + default: + k = DIK_SLASH; + break; + } + keys[DIK_MINUS] = k; + + switch (gKeyboardLayout) { + 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 (gKeyboardLayout) { + case 1: + k = DIK_A; + break; + default: + k = DIK_Q; + break; + } + keys[DIK_Q] = k; + + switch (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + case 0: + k = DIK_SEMICOLON; + break; + default: + k = DIK_COMMA; + break; + } + keys[DIK_SEMICOLON] = k; + + switch (gKeyboardLayout) { + case 0: + k = DIK_APOSTROPHE; + break; + case 1: + k = DIK_4; + break; + default: + k = DIK_MINUS; + break; + } + keys[DIK_APOSTROPHE] = k; + + switch (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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 (gKeyboardLayout) { + case 1: + k = DIK_SEMICOLON; + break; + default: + k = DIK_M; + break; + } + keys[DIK_M] = k; + + switch (gKeyboardLayout) { + case 1: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + keys[DIK_COMMA] = k; + + switch (gKeyboardLayout) { + case 1: + k = DIK_COMMA; + break; + default: + k = DIK_PERIOD; + break; + } + keys[DIK_PERIOD] = k; + + switch (gKeyboardLayout) { + 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 a1) +{ + _GNW95_hook_keyboard(a1); + + if (a1) { + mouseDeviceAcquire(); + } else { + mouseDeviceUnacquire(); + } +} + +// 0x4C9C20 +int _GNW95_input_init() +{ + return 0; +} + +// 0x4C9C28 +int _GNW95_hook_keyboard(int a1) +{ + if (a1 == _keyboard_hooked) { + return 0; + } + + if (!a1) { + keyboardDeviceUnacquire(); + + UnhookWindowsHookEx(_GNW95_keyboardHandle); + + keyboardReset(); + + _keyboard_hooked = a1; + + return 0; + } + + if (keyboardDeviceAcquire()) { + _GNW95_keyboardHandle = SetWindowsHookExA(WH_KEYBOARD, _GNW95_keyboard_hook, 0, GetCurrentThreadId()); + keyboardReset(); + _keyboard_hooked = a1; + + 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 (gProgramIsActive && !keyboardIsDisabled()) { + KeyboardData data; + while (keyboardDeviceGetData(&data)) { + _GNW95_process_key(&data); + } + + // NOTE: Uninline + int tick = _get_time(); + + for (int key = 0; key < 256; key++) { + STRUCT_6ABF50* ptr = &(_GNW95_key_time_stamps[key]); + if (ptr->tick != -1) { + int elapsedTime = ptr->tick > tick ? INT_MAX : tick - ptr->tick; + int delay = ptr->repeatCount == 0 ? gKeyboardKeyRepeatDelay : gKeyboardKeyRepeatRate; + if (elapsedTime > delay) { + data.key = key; + data.down = 1; + _GNW95_process_key(&data); + + ptr->tick = tick; + ptr->repeatCount++; + } + } + } + } + + 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].tick = -1; + _GNW95_key_time_stamps[index].repeatCount = 0; + } +} + +// 0x4C9E14 +void _GNW95_process_key(KeyboardData* data) +{ + short key = data->key & 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 = gNormalizedQwertyKeys[data->key & 0xFF]; + + if (_vcr_state == 1) { + if (_vcr_terminate_flags & 1) { + _vcr_terminated_condition = 2; + _vcr_stop(); + } + } else { + if ((key & 0x0100) != 0) { + _kb_simulate_key(224); + qwertyKey -= 0x80; + } + + STRUCT_6ABF50* ptr = &(_GNW95_key_time_stamps[data->key & 0xFF]); + if (data->down == 1) { + ptr->tick = _get_time(); + ptr->repeatCount = 0; + } else { + qwertyKey |= 0x80; + ptr->tick = -1; + } + + _kb_simulate_key(qwertyKey); + } +} + +// 0x4C9EEC +void _GNW95_lost_focus() +{ + if (_focus_func != NULL) { + _focus_func(0); + } + + while (!gProgramIsActive) { + _GNW95_process_message(); + + if (_idle_func != NULL) { + _idle_func(); + } + } + + if (_focus_func != NULL) { + _focus_func(1); + } +} + +// 0x4C9F40 +int mouseInit() +{ + gMouseInitialized = false; + _mouse_disabled = 0; + + gCursorIsHidden = true; + + mousePrepareDefaultCursor(); + + if (mouseSetFrame(NULL, 0, 0, 0, 0, 0, 0) == -1) { + return -1; + } + + if (!mouseDeviceAcquire()) { + return -1; + } + + gMouseInitialized = true; + gMouseCursorX = _scr_size.right / 2; + gMouseCursorY = _scr_size.bottom / 2; + _raw_x = _scr_size.right / 2; + _raw_y = _scr_size.bottom / 2; + _mouse_idle_start_time = _get_time(); + + return 0; +} + +// 0x4C9FD8 +void mouseFree() +{ + mouseDeviceUnacquire(); + + if (gMouseCursorData != NULL) { + internal_free(gMouseCursorData); + gMouseCursorData = NULL; + } + + if (_mouse_fptr != NULL) { + tickersRemove(_mouse_anim); + _mouse_fptr = NULL; + } +} + +// 0x4CA01C +void mousePrepareDefaultCursor() +{ + for (int index = 0; index < 64; index++) { + switch (gMouseDefaultCursor[index]) { + case 0: + gMouseDefaultCursor[index] = _colorTable[0]; + break; + case 1: + gMouseDefaultCursor[index] = _colorTable[8456]; + break; + case 15: + gMouseDefaultCursor[index] = _colorTable[32767]; + break; + } + } +} + +// 0x4CA0AC +int mouseSetFrame(unsigned char* a1, int width, int height, int pitch, int a5, int a6, int a7) +{ + Rect rect; + unsigned char* v9; + int v11, v12; + int v7, v8; + + v7 = a5; + v8 = a6; + v9 = a1; + + if (a1 == NULL) { + // NOTE: Original code looks tail recursion optimization. + return mouseSetFrame(gMouseDefaultCursor, MOUSE_DEFAULT_CURSOR_WIDTH, MOUSE_DEFAULT_CURSOR_HEIGHT, MOUSE_DEFAULT_CURSOR_WIDTH, 1, 1, _colorTable[0]); + } + + bool cursorWasHidden = gCursorIsHidden; + if (!gCursorIsHidden && gMouseInitialized) { + gCursorIsHidden = true; + mouseGetRect(&rect); + windowRefreshAll(&rect); + } + + if (width != gMouseCursorWidth || height != gMouseCursorHeight) { + unsigned char* buf = internal_malloc(width * height); + if (buf == NULL) { + if (!cursorWasHidden) { + mouseShowCursor(); + } + return -1; + } + + if (gMouseCursorData != NULL) { + internal_free(gMouseCursorData); + } + + gMouseCursorData = buf; + } + + gMouseCursorWidth = width; + gMouseCursorHeight = height; + gMouseCursorPitch = pitch; + _mouse_shape = v9; + _mouse_trans = a7; + + if (_mouse_fptr) { + tickersRemove(_mouse_anim); + _mouse_fptr = NULL; + } + + v11 = _mouse_hotx - v7; + _mouse_hotx = v7; + + gMouseCursorX += v11; + + v12 = _mouse_hoty - v8; + _mouse_hoty = v8; + + gMouseCursorY += v12; + + _mouse_clip(); + + if (!cursorWasHidden) { + mouseShowCursor(); + } + + _raw_x = gMouseCursorX; + _raw_y = gMouseCursorY; + + return 0; +} + +// NOTE: Looks like this code is not reachable. +// +// 0x4CA2D0 +void _mouse_anim() +{ + if (getTicksSince(_ticker_) >= _mouse_speed) { + _ticker_ = _get_time(); + + if (++_mouse_curr_frame == _mouse_num_frames) { + _mouse_curr_frame = 0; + } + + _mouse_shape = gMouseCursorWidth * _mouse_curr_frame * gMouseCursorHeight + _mouse_fptr; + + if (!gCursorIsHidden) { + mouseShowCursor(); + } + } +} + +// 0x4CA34C +void mouseShowCursor() +{ + int i; + unsigned char* v2; + int v7, v8; + int v9, v10; + int v4; + unsigned char v6; + int v3; + + v2 = gMouseCursorData; + if (gMouseInitialized) { + if (!_mouse_blit_trans || !gCursorIsHidden) { + _win_get_mouse_buf(gMouseCursorData); + v2 = gMouseCursorData; + v3 = 0; + + for (i = 0; i < gMouseCursorHeight; i++) { + for (v4 = 0; v4 < gMouseCursorWidth; v4++) { + v6 = _mouse_shape[i * gMouseCursorPitch + v4]; + if (v6 != _mouse_trans) { + v2[v3] = v6; + } + v3++; + } + } + } + + if (gMouseCursorX >= _scr_size.left) { + if (gMouseCursorWidth + gMouseCursorX - 1 <= _scr_size.right) { + v8 = gMouseCursorWidth; + v7 = 0; + } else { + v7 = 0; + v8 = _scr_size.right - gMouseCursorX + 1; + } + } else { + v7 = _scr_size.left - gMouseCursorX; + v8 = gMouseCursorWidth - (_scr_size.left - gMouseCursorX); + } + + if (gMouseCursorY >= _scr_size.top) { + if (gMouseCursorHeight + gMouseCursorY - 1 <= _scr_size.bottom) { + v9 = 0; + v10 = gMouseCursorHeight; + } else { + v9 = 0; + v10 = _scr_size.bottom - gMouseCursorY + 1; + } + } else { + v9 = _scr_size.top - gMouseCursorY; + v10 = gMouseCursorHeight - (_scr_size.top - gMouseCursorY); + } + + gMouseCursorData = v2; + if (_mouse_blit_trans && gCursorIsHidden) { + _mouse_blit_trans(_mouse_shape, gMouseCursorPitch, gMouseCursorHeight, v7, v9, v8, v10, v7 + gMouseCursorX, v9 + gMouseCursorY, _mouse_trans); + } else { + _mouse_blit(gMouseCursorData, gMouseCursorWidth, gMouseCursorHeight, v7, v9, v8, v10, v7 + gMouseCursorX, v9 + gMouseCursorY); + } + + v2 = gMouseCursorData; + gCursorIsHidden = false; + } + gMouseCursorData = v2; +} + +// 0x4CA534 +void mouseHideCursor() +{ + Rect rect; + + if (gMouseInitialized) { + if (!gCursorIsHidden) { + rect.left = gMouseCursorX; + rect.top = gMouseCursorY; + rect.right = gMouseCursorX + gMouseCursorWidth - 1; + rect.bottom = gMouseCursorY + gMouseCursorHeight - 1; + + gCursorIsHidden = true; + windowRefreshAll(&rect); + } + } +} + +// 0x4CA59C +void _mouse_info() +{ + if (!gMouseInitialized) { + return; + } + + if (gCursorIsHidden) { + return; + } + + if (_mouse_disabled) { + return; + } + + int x; + int y; + int buttons = 0; + + MouseData mouseData; + if (mouseDeviceGetData(&mouseData)) { + x = mouseData.x; + y = mouseData.y; + + if (mouseData.buttons[0] == 1) { + buttons |= MOUSE_STATE_LEFT_BUTTON_DOWN; + } + + if (mouseData.buttons[1] == 1) { + buttons |= MOUSE_STATE_RIGHT_BUTTON_DOWN; + } + } else { + x = 0; + y = 0; + } + + // Adjust for mouse senstivity. + x = (int)(x * gMouseSensitivity); + y = (int)(y * gMouseSensitivity); + + if (_vcr_state == 1) { + if (((_vcr_terminate_flags & 4) && buttons) || ((_vcr_terminate_flags & 2) && (x || y))) { + _vcr_terminated_condition = 2; + _vcr_stop(); + return; + } + x = 0; + y = 0; + buttons = gMouseButtonsState; + } + + _mouse_simulate_input(x, y, buttons); +} + +// 0x4CA698 +void _mouse_simulate_input(int delta_x, int delta_y, int buttons) +{ + if (!gMouseInitialized || gCursorIsHidden) { + return; + } + + if (delta_x || delta_y || buttons != gMouseButtonsState) { + if (_vcr_state == 0) { + if (_vcr_buffer_index == 4095) { + _vcr_dump_buffer(); + } + + STRUCT_51E2F0* ptr = &(_vcr_buffer[_vcr_buffer_index]); + ptr->type = 3; + ptr->field_4 = _vcr_time; + ptr->field_8 = _vcr_counter; + ptr->dx = delta_x; + ptr->dy = delta_y; + ptr->buttons = buttons; + + _vcr_buffer_index++; + } + } else { + if (gMouseButtonsState == 0) { + if (!_mouse_idling) { + _mouse_idle_start_time = _get_time(); + _mouse_idling = 1; + } + + gMouseButtonsState = 0; + _raw_buttons = 0; + gMouseEvent = 0; + + return; + } + } + + _mouse_idling = 0; + gMouseButtonsState = buttons; + gMousePreviousEvent = gMouseEvent; + gMouseEvent = 0; + + if ((gMousePreviousEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != 0) { + if ((buttons & 0x01) != 0) { + gMouseEvent |= MOUSE_EVENT_LEFT_BUTTON_REPEAT; + + if (getTicksSince(gMouseLeftButtonDownTimestamp) > BUTTON_REPEAT_TIME) { + gMouseEvent |= MOUSE_EVENT_LEFT_BUTTON_DOWN; + gMouseLeftButtonDownTimestamp = _get_time(); + } + } else { + gMouseEvent |= MOUSE_EVENT_LEFT_BUTTON_UP; + } + } else { + if ((buttons & 0x01) != 0) { + gMouseEvent |= MOUSE_EVENT_LEFT_BUTTON_DOWN; + gMouseLeftButtonDownTimestamp = _get_time(); + } + } + + if ((gMousePreviousEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT) != 0) { + if ((buttons & 0x02) != 0) { + gMouseEvent |= MOUSE_EVENT_RIGHT_BUTTON_REPEAT; + if (getTicksSince(gMouseRightButtonDownTimestamp) > BUTTON_REPEAT_TIME) { + gMouseEvent |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; + gMouseRightButtonDownTimestamp = _get_time(); + } + } else { + gMouseEvent |= MOUSE_EVENT_RIGHT_BUTTON_UP; + } + } else { + if (buttons & 0x02) { + gMouseEvent |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; + gMouseRightButtonDownTimestamp = _get_time(); + } + } + + _raw_buttons = gMouseEvent; + + if (delta_x != 0 || delta_y != 0) { + Rect mouseRect; + mouseRect.left = gMouseCursorX; + mouseRect.top = gMouseCursorY; + mouseRect.right = gMouseCursorWidth + gMouseCursorX - 1; + mouseRect.bottom = gMouseCursorHeight + gMouseCursorY - 1; + + gMouseCursorX += delta_x; + gMouseCursorY += delta_y; + _mouse_clip(); + + windowRefreshAll(&mouseRect); + + mouseShowCursor(); + + _raw_x = gMouseCursorX; + _raw_y = gMouseCursorY; + } +} + +// 0x4CA8C8 +bool _mouse_in(int left, int top, int right, int bottom) +{ + if (!gMouseInitialized) { + return false; + } + + return gMouseCursorHeight + gMouseCursorY > top + && right >= gMouseCursorX + && gMouseCursorWidth + gMouseCursorX > left + && bottom >= gMouseCursorY; +} + +// 0x4CA934 +bool _mouse_click_in(int left, int top, int right, int bottom) +{ + if (!gMouseInitialized) { + return false; + } + + return _mouse_hoty + gMouseCursorY >= top + && _mouse_hotx + gMouseCursorX <= right + && _mouse_hotx + gMouseCursorX >= left + && _mouse_hoty + gMouseCursorY <= bottom; +} + +// 0x4CA9A0 +void mouseGetRect(Rect* rect) +{ + rect->left = gMouseCursorX; + rect->top = gMouseCursorY; + rect->right = gMouseCursorWidth + gMouseCursorX - 1; + rect->bottom = gMouseCursorHeight + gMouseCursorY - 1; +} + +// 0x4CA9DC +void mouseGetPosition(int* xPtr, int* yPtr) +{ + *xPtr = _mouse_hotx + gMouseCursorX; + *yPtr = _mouse_hoty + gMouseCursorY; +} + +// 0x4CAA04 +void _mouse_set_position(int a1, int a2) +{ + gMouseCursorX = a1 - _mouse_hotx; + gMouseCursorY = a2 - _mouse_hoty; + _raw_y = a2 - _mouse_hoty; + _raw_x = a1 - _mouse_hotx; + _mouse_clip(); +} + +// 0x4CAA38 +void _mouse_clip() +{ + if (_mouse_hotx + gMouseCursorX < _scr_size.left) { + gMouseCursorX = _scr_size.left - _mouse_hotx; + } else if (_mouse_hotx + gMouseCursorX > _scr_size.right) { + gMouseCursorX = _scr_size.right - _mouse_hotx; + } + + if (_mouse_hoty + gMouseCursorY < _scr_size.top) { + gMouseCursorY = _scr_size.top - _mouse_hoty; + } else if (_mouse_hoty + gMouseCursorY > _scr_size.bottom) { + gMouseCursorY = _scr_size.bottom - _mouse_hoty; + } +} + +// 0x4CAAA0 +int mouseGetEvent() +{ + return gMouseEvent; +} + +// 0x4CAAA8 +bool cursorIsHidden() +{ + return gCursorIsHidden; +} + +// 0x4CAB5C +void _mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons) +{ + MouseData mouseData; + if (!mouseDeviceGetData(&mouseData)) { + mouseData.x = 0; + mouseData.y = 0; + mouseData.buttons[0] = (gMouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0; + mouseData.buttons[1] = (gMouseEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0; + } + + _raw_buttons = 0; + _raw_x += mouseData.x; + _raw_y += mouseData.y; + + if (mouseData.buttons[0] != 0) { + _raw_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN; + } + + if (mouseData.buttons[1] != 0) { + _raw_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; + } + + *out_x = _raw_x; + *out_y = _raw_y; + *out_buttons = _raw_buttons; +} + +// 0x4CAC3C +void mouseSetSensitivity(double value) +{ + if (value > 0 && value < 2.0) { + gMouseSensitivity = value; + } +} + +// 0x4CACD0 +void mmxSetEnabled(bool a1) +{ + if (!gMmxProbed) { + gMmxSupported = mmxIsSupported(); + gMmxProbed = true; + } + + if (gMmxSupported) { + gMmxEnabled = a1; + } +} + +// 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 _init_vesa_mode(640, 480); +} + +// 0x4CAD94 +int _init_mode_640_400() +{ + return _init_vesa_mode(640, 400); +} + +// 0x4CADA8 +int _init_mode_800_600() +{ + return _init_vesa_mode(800, 600); +} + +// 0x4CADBC +int _init_mode_1024_768() +{ + return _init_vesa_mode(1024, 768); +} + +// 0x4CADD0 +int _init_mode_1280_1024() +{ + return _init_vesa_mode(1280, 1024); +} + +// 0x4CADF8 +void _get_start_mode_() +{ +} + +// 0x4CADFC +void _zero_vid_mem() +{ + if (_zero_mem) { + _zero_mem(); + } +} + +// 0x4CAE1C +int _GNW95_init_mode_ex(int width, int height, int bpp) +{ + if (_GNW95_init_window() == -1) { + return -1; + } + + if (directDrawInit(width, height, bpp) == -1) { + return -1; + } + + _scr_size.left = 0; + _scr_size.top = 0; + _scr_size.right = width - 1; + _scr_size.bottom = height - 1; + + mmxSetEnabled(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 +int _init_vesa_mode(int width, int height) +{ + return _GNW95_init_mode_ex(width, height, 8); +} + +// 0x4CAEDC +int _GNW95_init_window() +{ + if (gProgramWindow == NULL) { + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + gProgramWindow = CreateWindowExA(WS_EX_TOPMOST, "GNW95 Class", gProgramWindowTitle, WS_POPUP | WS_VISIBLE | WS_SYSMENU, 0, 0, width, height, NULL, NULL, gInstance, NULL); + if (gProgramWindow == NULL) { + return -1; + } + + UpdateWindow(gProgramWindow); + SetFocus(gProgramWindow); + } + + return 0; +} + +// calculate shift for mask +// 0x4CAF50 +int getShiftForBitMask(int mask) +{ + int shift = 0; + + if ((mask & 0xFFFF0000) != 0) { + shift |= 16; + mask &= 0xFFFF0000; + } + + if ((mask & 0xFF00FF00) != 0) { + shift |= 8; + mask &= 0xFF00FF00; + } + + if ((mask & 0xF0F0F0F0) != 0) { + shift |= 4; + mask &= 0xF0F0F0F0; + } + + if ((mask & 0xCCCCCCCC) != 0) { + shift |= 2; + mask &= 0xCCCCCCCC; + } + + if ((mask & 0xAAAAAAAA) != 0) { + shift |= 1; + } + + return shift; +} + +// 0x4CAF9C +int directDrawInit(int width, int height, int bpp) +{ + if (gDirectDraw != NULL) { + unsigned char* palette = directDrawGetPalette(); + directDrawFree(); + + if (directDrawInit(width, height, bpp) == -1) { + return -1; + } + + directDrawSetPalette(palette); + + return 0; + } + + if (gDirectDrawCreateProc(NULL, &gDirectDraw, NULL) != DD_OK) { + return -1; + } + + if (IDirectDraw_SetCooperativeLevel(gDirectDraw, gProgramWindow, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN) != DD_OK) { + return -1; + } + + if (IDirectDraw_SetDisplayMode(gDirectDraw, 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(gDirectDraw, &ddsd, &gDirectDrawSurface1, NULL) != DD_OK) { + return -1; + } + + gDirectDrawSurface2 = gDirectDrawSurface1; + + 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(gDirectDraw, DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &gDirectDrawPalette, NULL) != DD_OK) { + return -1; + } + + if (IDirectDrawSurface_SetPalette(gDirectDrawSurface1, gDirectDrawPalette) != DD_OK) { + return -1; + } + + return 0; + } else { + DDPIXELFORMAT ddpf; + ddpf.dwSize = sizeof(DDPIXELFORMAT); + + if (IDirectDrawSurface_GetPixelFormat(gDirectDrawSurface1, &ddpf) != DD_OK) { + return -1; + } + + gRedMask = ddpf.dwRBitMask; + gGreenMask = ddpf.dwGBitMask; + gBlueMask = ddpf.dwBBitMask; + + gRedShift = getShiftForBitMask(gRedMask) - 7; + gGreenShift = getShiftForBitMask(gGreenMask) - 7; + gBlueShift = getShiftForBitMask(gBlueMask) - 7; + + return 0; + } +} + +// 0x4CB1B0 +void directDrawFree() +{ + if (gDirectDraw != NULL) { + IDirectDraw_RestoreDisplayMode(gDirectDraw); + + if (gDirectDrawSurface1 != NULL) { + IDirectDrawSurface_Release(gDirectDrawSurface1); + gDirectDrawSurface1 = NULL; + gDirectDrawSurface2 = NULL; + } + + if (gDirectDrawPalette != NULL) { + IDirectDrawPalette_Release(gDirectDrawPalette); + gDirectDrawPalette = NULL; + } + + IDirectDraw_Release(gDirectDraw); + gDirectDraw = NULL; + } +} + +// 0x4CB310 +void directDrawSetPaletteInRange(unsigned char* palette, int start, int count) +{ + if (gDirectDrawPalette != 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(gDirectDrawPalette, 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 = gRedShift > 0 ? (r << gRedShift) : (r >> -gRedShift); + r &= gRedMask; + + g = gGreenShift > 0 ? (g << gGreenShift) : (g >> -gGreenShift); + g &= gGreenMask; + + b = gBlueShift > 0 ? (b << gBlueShift) : (b >> -gBlueShift); + b &= gBlueMask; + + unsigned short rgb = r | g | b; + gSixteenBppPalette[index] = rgb; + } + + windowRefreshAll(&_scr_size); + } + + if (_update_palette_func != NULL) { + _update_palette_func(); + } +} + +// 0x4CB568 +void directDrawSetPalette(unsigned char* palette) +{ + if (gDirectDrawPalette != 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(gDirectDrawPalette, 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 = gRedShift > 0 ? (r << gRedShift) : (r >> -gRedShift); + r &= gRedMask; + + g = gGreenShift > 0 ? (g << gGreenShift) : (g >> -gGreenShift); + g &= gGreenMask; + + b = gBlueShift > 0 ? (b << gBlueShift) : (b >> -gBlueShift); + b &= gBlueMask; + + unsigned short rgb = r | g | b; + gSixteenBppPalette[index] = rgb; + } + + windowRefreshAll(&_scr_size); + } + + if (_update_palette_func != NULL) { + _update_palette_func(); + } +} + +// 0x4CB68C +unsigned char* directDrawGetPalette() +{ + if (gDirectDrawPalette != NULL) { + PALETTEENTRY paletteEntries[256]; + if (IDirectDrawPalette_GetEntries(gDirectDrawPalette, 0, 0, 256, paletteEntries) != DD_OK) { + return NULL; + } + + for (int index = 0; index < 256; index++) { + PALETTEENTRY* paletteEntry = &(paletteEntries[index]); + gLastVideoModePalette[index * 3] = paletteEntry->peRed >> 2; + gLastVideoModePalette[index * 3 + 1] = paletteEntry->peGreen >> 2; + gLastVideoModePalette[index * 3 + 2] = paletteEntry->peBlue >> 2; + } + + return gLastVideoModePalette; + } + + int redShift = gRedShift + 2; + int greenShift = gGreenShift + 2; + int blueShift = gBlueShift + 2; + + for (int index = 0; index < 256; index++) { + unsigned short rgb = gSixteenBppPalette[index]; + + unsigned short r = redShift > 0 ? ((rgb & gRedMask) >> redShift) : ((rgb & gRedMask) << -redShift); + unsigned short g = greenShift > 0 ? ((rgb & gGreenMask) >> greenShift) : ((rgb & gGreenMask) << -greenShift); + unsigned short b = blueShift > 0 ? ((rgb & gBlueMask) >> blueShift) : ((rgb & gBlueMask) << -blueShift); + + gLastVideoModePalette[index * 3] = (r >> 2) & 0xFF; + gLastVideoModePalette[index * 3 + 1] = (g >> 2) & 0xFF; + gLastVideoModePalette[index * 3 + 2] = (b >> 2) & 0xFF; + } + + return gLastVideoModePalette; +} + +// 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 (!gProgramIsActive) { + return; + } + + while (1) { + ddsd.dwSize = sizeof(DDSURFACEDESC); + + hr = IDirectDrawSurface_Lock(gDirectDrawSurface1, NULL, &ddsd, 1, NULL); + if (hr == DD_OK) { + break; + } + + if (hr == DDERR_SURFACELOST) { + if (IDirectDrawSurface_Restore(gDirectDrawSurface2) != DD_OK) { + return; + } + } + } + + blitBufferToBuffer(src + srcPitch * srcY + srcX, srcWidth, srcHeight, srcPitch, (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + destX, ddsd.lPitch); + + IDirectDrawSurface_Unlock(gDirectDrawSurface1, 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 (!gProgramIsActive) { + return; + } + + while (1) { + ddsd.dwSize = sizeof(ddsd); + + hr = IDirectDrawSurface_Lock(gDirectDrawSurface1, NULL, &ddsd, 1, NULL); + if (hr == DD_OK) { + break; + } + + if (hr == DDERR_SURFACELOST) { + if (IDirectDrawSurface_Restore(gDirectDrawSurface2) != 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 = gSixteenBppPalette[*srcPtr]; + destPtr++; + srcPtr++; + } + + dest += ddsd.lPitch; + src += srcPitch; + } + + IDirectDrawSurface_Unlock(gDirectDrawSurface1, 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 (!gProgramIsActive) { + return; + } + + while (1) { + ddsd.dwSize = sizeof(ddsd); + + hr = IDirectDrawSurface_Lock(gDirectDrawSurface1, NULL, &ddsd, 1, NULL); + if (hr == DD_OK) { + break; + } + + if (hr == DDERR_SURFACELOST) { + if (IDirectDrawSurface_Restore(gDirectDrawSurface2) != 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 = gSixteenBppPalette[*srcPtr]; + } + destPtr++; + srcPtr++; + } + + dest += ddsd.lPitch; + src += srcPitch; + } + + IDirectDrawSurface_Unlock(gDirectDrawSurface1, ddsd.lpSurface); +} + +// Clears drawing surface. +// +// 0x4CBBC8 +void _GNW95_zero_vid_mem() +{ + DDSURFACEDESC ddsd; + HRESULT hr; + unsigned char* surface; + + if (!gProgramIsActive) { + return; + } + + while (1) { + ddsd.dwSize = sizeof(DDSURFACEDESC); + + hr = IDirectDrawSurface_Lock(gDirectDrawSurface1, NULL, &ddsd, 1, NULL); + if (hr == DD_OK) { + break; + } + + if (hr == DDERR_SURFACELOST) { + if (IDirectDrawSurface_Restore(gDirectDrawSurface2) != 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(gDirectDrawSurface1, ddsd.lpSurface); +} + +// 0x4CBC90 +int keyboardInit() +{ + if (_kb_installed) { + return -1; + } + + _kb_installed = 1; + gPressedPhysicalKeysCount = 0; + + memset(gPressedPhysicalKeys, 0, 256); + + gKeyboardEventQueueWriteIndex = 0; + gKeyboardEventQueueReadIndex = 0; + + keyboardDeviceReset(); + _GNW95_clear_time_stamps(); + _kb_init_lock_status(); + keyboardSetLayout(KEYBOARD_LAYOUT_QWERTY); + + _kb_idle_start_time = _get_time(); + + return 0; +} + +// 0x4CBD00 +void keyboardFree() +{ + if (_kb_installed) { + _kb_installed = 0; + } +} + +// 0x4CBDA8 +void keyboardReset() +{ + if (_kb_installed) { + gPressedPhysicalKeysCount = 0; + + memset(&gPressedPhysicalKeys, 0, 256); + + gKeyboardEventQueueWriteIndex = 0; + gKeyboardEventQueueReadIndex = 0; + } + + keyboardDeviceReset(); + _GNW95_clear_time_stamps(); +} + +int _kb_getch() +{ + int rc = -1; + + if (_kb_installed != 0) { + rc = _kb_scan_to_ascii(); + } + + return rc; +} + +// 0x4CBE00 +void keyboardDisable() +{ + gKeyboardDisabled = true; +} + +// 0x4CBE0C +void keyboardEnable() +{ + gKeyboardDisabled = false; +} + +// 0x4CBE18 +int keyboardIsDisabled() +{ + return gKeyboardDisabled; +} + +// 0x4CBE74 +void keyboardSetLayout(int keyboardLayout) +{ + int oldKeyboardLayout = gKeyboardLayout; + gKeyboardLayout = keyboardLayout; + + switch (keyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + _kb_scan_to_ascii = _kb_next_ascii_English_US; + keyboardBuildQwertyConfiguration(); + 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: + gKeyboardLayout = oldKeyboardLayout; + break; + } +} + +// 0x4CBEEC +int keyboardGetLayout() +{ + return gKeyboardLayout; +} + +// TODO: Key type is likely short. +void _kb_simulate_key(int key) +{ + if (_vcr_state == 0) { + if (_vcr_buffer_index != 4095) { + STRUCT_51E2F0* ptr = &(_vcr_buffer[_vcr_buffer_index]); + ptr->type = 2; + ptr->type_2_field_C = key & 0xFFFF; + ptr->field_4 = _vcr_time; + ptr->field_8 = _vcr_counter; + _vcr_buffer_index++; + } + } + + _kb_idle_start_time = _get_bk_time(); + + if (key == 224) { + word_51E2E8 = 0x80; + } else { + int keyState; + if (key & 0x80) { + key &= ~0x80; + keyState = KEY_STATE_UP; + } else { + keyState = KEY_STATE_DOWN; + } + + int physicalKey = key | word_51E2E8; + + if (keyState != KEY_STATE_UP && gPressedPhysicalKeys[physicalKey] != KEY_STATE_UP) { + keyState = KEY_STATE_REPEAT; + } + + if (gPressedPhysicalKeys[physicalKey] != keyState) { + gPressedPhysicalKeys[physicalKey] = keyState; + if (keyState == KEY_STATE_DOWN) { + gPressedPhysicalKeysCount++; + } else if (keyState == KEY_STATE_UP) { + gPressedPhysicalKeysCount--; + } + } + + if (keyState != KEY_STATE_UP) { + gLastKeyboardEvent.scanCode = physicalKey & 0xFF; + gLastKeyboardEvent.modifiers = 0; + + if (physicalKey == DIK_CAPITAL) { + if (gPressedPhysicalKeys[DIK_LCONTROL] == KEY_STATE_UP && gPressedPhysicalKeys[DIK_RCONTROL] == KEY_STATE_UP) { + // TODO: Missing check for QWERTY keyboard layout. + if ((gModifierKeysState & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) { + // TODO: There is some strange code checking for _kb_layout, check in + // debugger. + gModifierKeysState &= ~MODIFIER_KEY_STATE_CAPS_LOCK; + } else { + gModifierKeysState |= MODIFIER_KEY_STATE_CAPS_LOCK; + } + } + } else if (physicalKey == DIK_NUMLOCK) { + if (gPressedPhysicalKeys[DIK_LCONTROL] == KEY_STATE_UP && gPressedPhysicalKeys[DIK_RCONTROL] == KEY_STATE_UP) { + if ((gModifierKeysState & MODIFIER_KEY_STATE_NUM_LOCK) != 0) { + gModifierKeysState &= ~MODIFIER_KEY_STATE_NUM_LOCK; + } else { + gModifierKeysState |= MODIFIER_KEY_STATE_NUM_LOCK; + } + } + } else if (physicalKey == DIK_SCROLL) { + if (gPressedPhysicalKeys[DIK_LCONTROL] == KEY_STATE_UP && gPressedPhysicalKeys[DIK_RCONTROL] == KEY_STATE_UP) { + if ((gModifierKeysState & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) { + gModifierKeysState &= ~MODIFIER_KEY_STATE_SCROLL_LOCK; + } else { + gModifierKeysState |= MODIFIER_KEY_STATE_SCROLL_LOCK; + } + } + } else if ((physicalKey == DIK_LSHIFT || physicalKey == DIK_RSHIFT) && (gModifierKeysState & MODIFIER_KEY_STATE_CAPS_LOCK) != 0 && gKeyboardLayout != 0) { + if (gPressedPhysicalKeys[DIK_LCONTROL] == KEY_STATE_UP && gPressedPhysicalKeys[DIK_RCONTROL] == KEY_STATE_UP) { + if (gModifierKeysState & MODIFIER_KEY_STATE_CAPS_LOCK) { + gModifierKeysState &= ~MODIFIER_KEY_STATE_CAPS_LOCK; + } else { + gModifierKeysState |= MODIFIER_KEY_STATE_CAPS_LOCK; + } + } + } + + if (gModifierKeysState != 0) { + if ((gModifierKeysState & MODIFIER_KEY_STATE_NUM_LOCK) != 0 && !gKeyboardNumlockDisabled) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_NUM_LOCK; + } + + if ((gModifierKeysState & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_CAPS_LOCK; + } + + if ((gModifierKeysState & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_SCROLL_LOCK; + } + } + + if (gPressedPhysicalKeys[DIK_LSHIFT] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT; + } + + if (gPressedPhysicalKeys[DIK_RSHIFT] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT; + } + + if (gPressedPhysicalKeys[DIK_LMENU] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_ALT; + } + + if (gPressedPhysicalKeys[DIK_RMENU] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_ALT; + } + + if (gPressedPhysicalKeys[DIK_LCONTROL] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL; + } + + if (gPressedPhysicalKeys[DIK_RCONTROL] != KEY_STATE_UP) { + gLastKeyboardEvent.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL; + } + + if (((gKeyboardEventQueueWriteIndex + 1) & 0x3F) != gKeyboardEventQueueReadIndex) { + gKeyboardEventsQueue[gKeyboardEventQueueWriteIndex] = gLastKeyboardEvent; + gKeyboardEventQueueWriteIndex++; + gKeyboardEventQueueWriteIndex &= 0x3F; + } + } + + word_51E2E8 = 0; + } + + if (gPressedPhysicalKeys[198] != KEY_STATE_UP) { + // NOTE: Uninline + keyboardReset(); + } +} + +// 0x4CC2F0 +int _kb_next_ascii_English_US() +{ + KeyboardEvent* keyboardEvent; + if (keyboardPeekEvent(0, &keyboardEvent) != 0) { + return -1; + } + + if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_CAPS_LOCK) != 0) { + unsigned char a = (gKeyboardLayout != KEYBOARD_LAYOUT_FRENCH ? DIK_A : DIK_Q); + unsigned char m = (gKeyboardLayout != KEYBOARD_LAYOUT_FRENCH ? DIK_M : DIK_SEMICOLON); + unsigned char q = (gKeyboardLayout != KEYBOARD_LAYOUT_FRENCH ? DIK_Q : DIK_A); + unsigned char w = (gKeyboardLayout != KEYBOARD_LAYOUT_FRENCH ? DIK_W : DIK_Z); + + unsigned char y; + switch (gKeyboardLayout) { + 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 (gKeyboardLayout) { + 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->scanCode; + 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 keyboardDequeueLogicalKeyCode(); +} + +// 0x4CDA4C +int keyboardDequeueLogicalKeyCode() +{ + KeyboardEvent* keyboardEvent; + if (keyboardPeekEvent(0, &keyboardEvent) != 0) { + return -1; + } + + switch (keyboardEvent->scanCode) { + case DIK_DIVIDE: + case DIK_MULTIPLY: + case DIK_SUBTRACT: + case DIK_ADD: + case DIK_NUMPADENTER: + if (gKeyboardNumpadDisabled) { + if (gKeyboardEventQueueReadIndex != gKeyboardEventQueueWriteIndex) { + gKeyboardEventQueueReadIndex++; + gKeyboardEventQueueReadIndex &= (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 (gKeyboardNumpadDisabled) { + if (gKeyboardEventQueueReadIndex != gKeyboardEventQueueWriteIndex) { + gKeyboardEventQueueReadIndex++; + gKeyboardEventQueueReadIndex &= (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; + + LogicalKeyEntry* logicalKeyDescription = &(gLogicalKeyEntries[keyboardEvent->scanCode]); + if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_CONTROL) != 0) { + logicalKey = logicalKeyDescription->ctrl; + } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_RIGHT_ALT) != 0) { + logicalKey = logicalKeyDescription->rmenu; + } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_LEFT_ALT) != 0) { + logicalKey = logicalKeyDescription->lmenu; + } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) != 0) { + logicalKey = logicalKeyDescription->shift; + } else { + logicalKey = logicalKeyDescription->unmodified; + } + + if (gKeyboardEventQueueReadIndex != gKeyboardEventQueueWriteIndex) { + gKeyboardEventQueueReadIndex++; + gKeyboardEventQueueReadIndex &= (KEY_QUEUE_SIZE - 1); + } + + return logicalKey; +} + +// 0x4CDC08 +void keyboardBuildQwertyConfiguration() +{ + int k; + + for (k = 0; k < 256; k++) { + gLogicalKeyEntries[k].field_0 = -1; + gLogicalKeyEntries[k].unmodified = -1; + gLogicalKeyEntries[k].shift = -1; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + } + + gLogicalKeyEntries[DIK_ESCAPE].unmodified = KEY_ESCAPE; + gLogicalKeyEntries[DIK_ESCAPE].shift = KEY_ESCAPE; + gLogicalKeyEntries[DIK_ESCAPE].lmenu = KEY_ESCAPE; + gLogicalKeyEntries[DIK_ESCAPE].rmenu = KEY_ESCAPE; + gLogicalKeyEntries[DIK_ESCAPE].ctrl = KEY_ESCAPE; + + gLogicalKeyEntries[DIK_F1].unmodified = KEY_F1; + gLogicalKeyEntries[DIK_F1].shift = KEY_SHIFT_F1; + gLogicalKeyEntries[DIK_F1].lmenu = KEY_ALT_F1; + gLogicalKeyEntries[DIK_F1].rmenu = KEY_ALT_F1; + gLogicalKeyEntries[DIK_F1].ctrl = KEY_CTRL_F1; + + gLogicalKeyEntries[DIK_F2].unmodified = KEY_F2; + gLogicalKeyEntries[DIK_F2].shift = KEY_SHIFT_F2; + gLogicalKeyEntries[DIK_F2].lmenu = KEY_ALT_F2; + gLogicalKeyEntries[DIK_F2].rmenu = KEY_ALT_F2; + gLogicalKeyEntries[DIK_F2].ctrl = KEY_CTRL_F2; + + gLogicalKeyEntries[DIK_F3].unmodified = KEY_F3; + gLogicalKeyEntries[DIK_F3].shift = KEY_SHIFT_F3; + gLogicalKeyEntries[DIK_F3].lmenu = KEY_ALT_F3; + gLogicalKeyEntries[DIK_F3].rmenu = KEY_ALT_F3; + gLogicalKeyEntries[DIK_F3].ctrl = KEY_CTRL_F3; + + gLogicalKeyEntries[DIK_F4].unmodified = KEY_F4; + gLogicalKeyEntries[DIK_F4].shift = KEY_SHIFT_F4; + gLogicalKeyEntries[DIK_F4].lmenu = KEY_ALT_F4; + gLogicalKeyEntries[DIK_F4].rmenu = KEY_ALT_F4; + gLogicalKeyEntries[DIK_F4].ctrl = KEY_CTRL_F4; + + gLogicalKeyEntries[DIK_F5].unmodified = KEY_F5; + gLogicalKeyEntries[DIK_F5].shift = KEY_SHIFT_F5; + gLogicalKeyEntries[DIK_F5].lmenu = KEY_ALT_F5; + gLogicalKeyEntries[DIK_F5].rmenu = KEY_ALT_F5; + gLogicalKeyEntries[DIK_F5].ctrl = KEY_CTRL_F5; + + gLogicalKeyEntries[DIK_F6].unmodified = KEY_F6; + gLogicalKeyEntries[DIK_F6].shift = KEY_SHIFT_F6; + gLogicalKeyEntries[DIK_F6].lmenu = KEY_ALT_F6; + gLogicalKeyEntries[DIK_F6].rmenu = KEY_ALT_F6; + gLogicalKeyEntries[DIK_F6].ctrl = KEY_CTRL_F6; + + gLogicalKeyEntries[DIK_F7].unmodified = KEY_F7; + gLogicalKeyEntries[DIK_F7].shift = KEY_SHIFT_F7; + gLogicalKeyEntries[DIK_F7].lmenu = KEY_ALT_F7; + gLogicalKeyEntries[DIK_F7].rmenu = KEY_ALT_F7; + gLogicalKeyEntries[DIK_F7].ctrl = KEY_CTRL_F7; + + gLogicalKeyEntries[DIK_F8].unmodified = KEY_F8; + gLogicalKeyEntries[DIK_F8].shift = KEY_SHIFT_F8; + gLogicalKeyEntries[DIK_F8].lmenu = KEY_ALT_F8; + gLogicalKeyEntries[DIK_F8].rmenu = KEY_ALT_F8; + gLogicalKeyEntries[DIK_F8].ctrl = KEY_CTRL_F8; + + gLogicalKeyEntries[DIK_F9].unmodified = KEY_F9; + gLogicalKeyEntries[DIK_F9].shift = KEY_SHIFT_F9; + gLogicalKeyEntries[DIK_F9].lmenu = KEY_ALT_F9; + gLogicalKeyEntries[DIK_F9].rmenu = KEY_ALT_F9; + gLogicalKeyEntries[DIK_F9].ctrl = KEY_CTRL_F9; + + gLogicalKeyEntries[DIK_F10].unmodified = KEY_F10; + gLogicalKeyEntries[DIK_F10].shift = KEY_SHIFT_F10; + gLogicalKeyEntries[DIK_F10].lmenu = KEY_ALT_F10; + gLogicalKeyEntries[DIK_F10].rmenu = KEY_ALT_F10; + gLogicalKeyEntries[DIK_F10].ctrl = KEY_CTRL_F10; + + gLogicalKeyEntries[DIK_F11].unmodified = KEY_F11; + gLogicalKeyEntries[DIK_F11].shift = KEY_SHIFT_F11; + gLogicalKeyEntries[DIK_F11].lmenu = KEY_ALT_F11; + gLogicalKeyEntries[DIK_F11].rmenu = KEY_ALT_F11; + gLogicalKeyEntries[DIK_F11].ctrl = KEY_CTRL_F11; + + gLogicalKeyEntries[DIK_F12].unmodified = KEY_F12; + gLogicalKeyEntries[DIK_F12].shift = KEY_SHIFT_F12; + gLogicalKeyEntries[DIK_F12].lmenu = KEY_ALT_F12; + gLogicalKeyEntries[DIK_F12].rmenu = KEY_ALT_F12; + gLogicalKeyEntries[DIK_F12].ctrl = KEY_CTRL_F12; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_GRAVE; + gLogicalKeyEntries[k].shift = KEY_TILDE; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_1].unmodified = KEY_1; + gLogicalKeyEntries[DIK_1].shift = KEY_EXCLAMATION; + gLogicalKeyEntries[DIK_1].lmenu = -1; + gLogicalKeyEntries[DIK_1].rmenu = -1; + gLogicalKeyEntries[DIK_1].ctrl = -1; + + gLogicalKeyEntries[DIK_2].unmodified = KEY_2; + gLogicalKeyEntries[DIK_2].shift = KEY_AT; + gLogicalKeyEntries[DIK_2].lmenu = -1; + gLogicalKeyEntries[DIK_2].rmenu = -1; + gLogicalKeyEntries[DIK_2].ctrl = -1; + + gLogicalKeyEntries[DIK_3].unmodified = KEY_3; + gLogicalKeyEntries[DIK_3].shift = KEY_NUMBER_SIGN; + gLogicalKeyEntries[DIK_3].lmenu = -1; + gLogicalKeyEntries[DIK_3].rmenu = -1; + gLogicalKeyEntries[DIK_3].ctrl = -1; + + gLogicalKeyEntries[DIK_4].unmodified = KEY_4; + gLogicalKeyEntries[DIK_4].shift = KEY_DOLLAR; + gLogicalKeyEntries[DIK_4].lmenu = -1; + gLogicalKeyEntries[DIK_4].rmenu = -1; + gLogicalKeyEntries[DIK_4].ctrl = -1; + + gLogicalKeyEntries[DIK_5].unmodified = KEY_5; + gLogicalKeyEntries[DIK_5].shift = KEY_PERCENT; + gLogicalKeyEntries[DIK_5].lmenu = -1; + gLogicalKeyEntries[DIK_5].rmenu = -1; + gLogicalKeyEntries[DIK_5].ctrl = -1; + + gLogicalKeyEntries[DIK_6].unmodified = KEY_6; + gLogicalKeyEntries[DIK_6].shift = KEY_CARET; + gLogicalKeyEntries[DIK_6].lmenu = -1; + gLogicalKeyEntries[DIK_6].rmenu = -1; + gLogicalKeyEntries[DIK_6].ctrl = -1; + + gLogicalKeyEntries[DIK_7].unmodified = KEY_7; + gLogicalKeyEntries[DIK_7].shift = KEY_AMPERSAND; + gLogicalKeyEntries[DIK_7].lmenu = -1; + gLogicalKeyEntries[DIK_7].rmenu = -1; + gLogicalKeyEntries[DIK_7].ctrl = -1; + + gLogicalKeyEntries[DIK_8].unmodified = KEY_8; + gLogicalKeyEntries[DIK_8].shift = KEY_ASTERISK; + gLogicalKeyEntries[DIK_8].lmenu = -1; + gLogicalKeyEntries[DIK_8].rmenu = -1; + gLogicalKeyEntries[DIK_8].ctrl = -1; + + gLogicalKeyEntries[DIK_9].unmodified = KEY_9; + gLogicalKeyEntries[DIK_9].shift = KEY_PAREN_LEFT; + gLogicalKeyEntries[DIK_9].lmenu = -1; + gLogicalKeyEntries[DIK_9].rmenu = -1; + gLogicalKeyEntries[DIK_9].ctrl = -1; + + gLogicalKeyEntries[DIK_0].unmodified = KEY_0; + gLogicalKeyEntries[DIK_0].shift = KEY_PAREN_RIGHT; + gLogicalKeyEntries[DIK_0].lmenu = -1; + gLogicalKeyEntries[DIK_0].rmenu = -1; + gLogicalKeyEntries[DIK_0].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_MINUS; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_6; + break; + default: + k = DIK_SLASH; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_MINUS; + gLogicalKeyEntries[k].shift = KEY_UNDERSCORE; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_EQUALS; + break; + default: + k = DIK_0; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_EQUAL; + gLogicalKeyEntries[k].shift = KEY_PLUS; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_BACK].unmodified = KEY_BACKSPACE; + gLogicalKeyEntries[DIK_BACK].shift = KEY_BACKSPACE; + gLogicalKeyEntries[DIK_BACK].lmenu = KEY_BACKSPACE; + gLogicalKeyEntries[DIK_BACK].rmenu = KEY_BACKSPACE; + gLogicalKeyEntries[DIK_BACK].ctrl = KEY_DEL; + + gLogicalKeyEntries[DIK_TAB].unmodified = KEY_TAB; + gLogicalKeyEntries[DIK_TAB].shift = KEY_TAB; + gLogicalKeyEntries[DIK_TAB].lmenu = KEY_TAB; + gLogicalKeyEntries[DIK_TAB].rmenu = KEY_TAB; + gLogicalKeyEntries[DIK_TAB].ctrl = KEY_TAB; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_A; + break; + default: + k = DIK_Q; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_Q; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_Q; + gLogicalKeyEntries[k].lmenu = KEY_ALT_Q; + gLogicalKeyEntries[k].rmenu = KEY_ALT_Q; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_Q; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_Z; + break; + default: + k = DIK_W; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_W; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_W; + gLogicalKeyEntries[k].lmenu = KEY_ALT_W; + gLogicalKeyEntries[k].rmenu = KEY_ALT_W; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_W; + + gLogicalKeyEntries[DIK_E].unmodified = KEY_LOWERCASE_E; + gLogicalKeyEntries[DIK_E].shift = KEY_UPPERCASE_E; + gLogicalKeyEntries[DIK_E].lmenu = KEY_ALT_E; + gLogicalKeyEntries[DIK_E].rmenu = KEY_ALT_E; + gLogicalKeyEntries[DIK_E].ctrl = KEY_CTRL_E; + + gLogicalKeyEntries[DIK_R].unmodified = KEY_LOWERCASE_R; + gLogicalKeyEntries[DIK_R].shift = KEY_UPPERCASE_R; + gLogicalKeyEntries[DIK_R].lmenu = KEY_ALT_R; + gLogicalKeyEntries[DIK_R].rmenu = KEY_ALT_R; + gLogicalKeyEntries[DIK_R].ctrl = KEY_CTRL_R; + + gLogicalKeyEntries[DIK_T].unmodified = KEY_LOWERCASE_T; + gLogicalKeyEntries[DIK_T].shift = KEY_UPPERCASE_T; + gLogicalKeyEntries[DIK_T].lmenu = KEY_ALT_T; + gLogicalKeyEntries[DIK_T].rmenu = KEY_ALT_T; + gLogicalKeyEntries[DIK_T].ctrl = KEY_CTRL_T; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_Y; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_Y; + gLogicalKeyEntries[k].lmenu = KEY_ALT_Y; + gLogicalKeyEntries[k].rmenu = KEY_ALT_Y; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_Y; + + gLogicalKeyEntries[DIK_U].unmodified = KEY_LOWERCASE_U; + gLogicalKeyEntries[DIK_U].shift = KEY_UPPERCASE_U; + gLogicalKeyEntries[DIK_U].lmenu = KEY_ALT_U; + gLogicalKeyEntries[DIK_U].rmenu = KEY_ALT_U; + gLogicalKeyEntries[DIK_U].ctrl = KEY_CTRL_U; + + gLogicalKeyEntries[DIK_I].unmodified = KEY_LOWERCASE_I; + gLogicalKeyEntries[DIK_I].shift = KEY_UPPERCASE_I; + gLogicalKeyEntries[DIK_I].lmenu = KEY_ALT_I; + gLogicalKeyEntries[DIK_I].rmenu = KEY_ALT_I; + gLogicalKeyEntries[DIK_I].ctrl = KEY_CTRL_I; + + gLogicalKeyEntries[DIK_O].unmodified = KEY_LOWERCASE_O; + gLogicalKeyEntries[DIK_O].shift = KEY_UPPERCASE_O; + gLogicalKeyEntries[DIK_O].lmenu = KEY_ALT_O; + gLogicalKeyEntries[DIK_O].rmenu = KEY_ALT_O; + gLogicalKeyEntries[DIK_O].ctrl = KEY_CTRL_O; + + gLogicalKeyEntries[DIK_P].unmodified = KEY_LOWERCASE_P; + gLogicalKeyEntries[DIK_P].shift = KEY_UPPERCASE_P; + gLogicalKeyEntries[DIK_P].lmenu = KEY_ALT_P; + gLogicalKeyEntries[DIK_P].rmenu = KEY_ALT_P; + gLogicalKeyEntries[DIK_P].ctrl = KEY_CTRL_P; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_BRACKET_LEFT; + gLogicalKeyEntries[k].shift = KEY_BRACE_LEFT; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[k].shift = KEY_BRACE_RIGHT; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_BACKSLASH; + gLogicalKeyEntries[k].shift = KEY_BAR; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_BACKSLASH; + + gLogicalKeyEntries[DIK_CAPITAL].unmodified = -1; + gLogicalKeyEntries[DIK_CAPITAL].shift = -1; + gLogicalKeyEntries[DIK_CAPITAL].lmenu = -1; + gLogicalKeyEntries[DIK_CAPITAL].rmenu = -1; + gLogicalKeyEntries[DIK_CAPITAL].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_Q; + break; + default: + k = DIK_A; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_A; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_A; + gLogicalKeyEntries[k].lmenu = KEY_ALT_A; + gLogicalKeyEntries[k].rmenu = KEY_ALT_A; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_A; + + gLogicalKeyEntries[DIK_S].unmodified = KEY_LOWERCASE_S; + gLogicalKeyEntries[DIK_S].shift = KEY_UPPERCASE_S; + gLogicalKeyEntries[DIK_S].lmenu = KEY_ALT_S; + gLogicalKeyEntries[DIK_S].rmenu = KEY_ALT_S; + gLogicalKeyEntries[DIK_S].ctrl = KEY_CTRL_S; + + gLogicalKeyEntries[DIK_D].unmodified = KEY_LOWERCASE_D; + gLogicalKeyEntries[DIK_D].shift = KEY_UPPERCASE_D; + gLogicalKeyEntries[DIK_D].lmenu = KEY_ALT_D; + gLogicalKeyEntries[DIK_D].rmenu = KEY_ALT_D; + gLogicalKeyEntries[DIK_D].ctrl = KEY_CTRL_D; + + gLogicalKeyEntries[DIK_F].unmodified = KEY_LOWERCASE_F; + gLogicalKeyEntries[DIK_F].shift = KEY_UPPERCASE_F; + gLogicalKeyEntries[DIK_F].lmenu = KEY_ALT_F; + gLogicalKeyEntries[DIK_F].rmenu = KEY_ALT_F; + gLogicalKeyEntries[DIK_F].ctrl = KEY_CTRL_F; + + gLogicalKeyEntries[DIK_G].unmodified = KEY_LOWERCASE_G; + gLogicalKeyEntries[DIK_G].shift = KEY_UPPERCASE_G; + gLogicalKeyEntries[DIK_G].lmenu = KEY_ALT_G; + gLogicalKeyEntries[DIK_G].rmenu = KEY_ALT_G; + gLogicalKeyEntries[DIK_G].ctrl = KEY_CTRL_G; + + gLogicalKeyEntries[DIK_H].unmodified = KEY_LOWERCASE_H; + gLogicalKeyEntries[DIK_H].shift = KEY_UPPERCASE_H; + gLogicalKeyEntries[DIK_H].lmenu = KEY_ALT_H; + gLogicalKeyEntries[DIK_H].rmenu = KEY_ALT_H; + gLogicalKeyEntries[DIK_H].ctrl = KEY_CTRL_H; + + gLogicalKeyEntries[DIK_J].unmodified = KEY_LOWERCASE_J; + gLogicalKeyEntries[DIK_J].shift = KEY_UPPERCASE_J; + gLogicalKeyEntries[DIK_J].lmenu = KEY_ALT_J; + gLogicalKeyEntries[DIK_J].rmenu = KEY_ALT_J; + gLogicalKeyEntries[DIK_J].ctrl = KEY_CTRL_J; + + gLogicalKeyEntries[DIK_K].unmodified = KEY_LOWERCASE_K; + gLogicalKeyEntries[DIK_K].shift = KEY_UPPERCASE_K; + gLogicalKeyEntries[DIK_K].lmenu = KEY_ALT_K; + gLogicalKeyEntries[DIK_K].rmenu = KEY_ALT_K; + gLogicalKeyEntries[DIK_K].ctrl = KEY_CTRL_K; + + gLogicalKeyEntries[DIK_L].unmodified = KEY_LOWERCASE_L; + gLogicalKeyEntries[DIK_L].shift = KEY_UPPERCASE_L; + gLogicalKeyEntries[DIK_L].lmenu = KEY_ALT_L; + gLogicalKeyEntries[DIK_L].rmenu = KEY_ALT_L; + gLogicalKeyEntries[DIK_L].ctrl = KEY_CTRL_L; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_SEMICOLON; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_SEMICOLON; + gLogicalKeyEntries[k].shift = KEY_COLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_APOSTROPHE; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_3; + break; + default: + k = DIK_2; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_SINGLE_QUOTE; + gLogicalKeyEntries[k].shift = KEY_QUOTE; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_RETURN].unmodified = KEY_RETURN; + gLogicalKeyEntries[DIK_RETURN].shift = KEY_RETURN; + gLogicalKeyEntries[DIK_RETURN].lmenu = KEY_RETURN; + gLogicalKeyEntries[DIK_RETURN].rmenu = KEY_RETURN; + gLogicalKeyEntries[DIK_RETURN].ctrl = KEY_CTRL_J; + + gLogicalKeyEntries[DIK_LSHIFT].unmodified = -1; + gLogicalKeyEntries[DIK_LSHIFT].shift = -1; + gLogicalKeyEntries[DIK_LSHIFT].lmenu = -1; + gLogicalKeyEntries[DIK_LSHIFT].rmenu = -1; + gLogicalKeyEntries[DIK_LSHIFT].ctrl = -1; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_Z; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_Z; + gLogicalKeyEntries[k].lmenu = KEY_ALT_Z; + gLogicalKeyEntries[k].rmenu = KEY_ALT_Z; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_Z; + + gLogicalKeyEntries[DIK_X].unmodified = KEY_LOWERCASE_X; + gLogicalKeyEntries[DIK_X].shift = KEY_UPPERCASE_X; + gLogicalKeyEntries[DIK_X].lmenu = KEY_ALT_X; + gLogicalKeyEntries[DIK_X].rmenu = KEY_ALT_X; + gLogicalKeyEntries[DIK_X].ctrl = KEY_CTRL_X; + + gLogicalKeyEntries[DIK_C].unmodified = KEY_LOWERCASE_C; + gLogicalKeyEntries[DIK_C].shift = KEY_UPPERCASE_C; + gLogicalKeyEntries[DIK_C].lmenu = KEY_ALT_C; + gLogicalKeyEntries[DIK_C].rmenu = KEY_ALT_C; + gLogicalKeyEntries[DIK_C].ctrl = KEY_CTRL_C; + + gLogicalKeyEntries[DIK_V].unmodified = KEY_LOWERCASE_V; + gLogicalKeyEntries[DIK_V].shift = KEY_UPPERCASE_V; + gLogicalKeyEntries[DIK_V].lmenu = KEY_ALT_V; + gLogicalKeyEntries[DIK_V].rmenu = KEY_ALT_V; + gLogicalKeyEntries[DIK_V].ctrl = KEY_CTRL_V; + + gLogicalKeyEntries[DIK_B].unmodified = KEY_LOWERCASE_B; + gLogicalKeyEntries[DIK_B].shift = KEY_UPPERCASE_B; + gLogicalKeyEntries[DIK_B].lmenu = KEY_ALT_B; + gLogicalKeyEntries[DIK_B].rmenu = KEY_ALT_B; + gLogicalKeyEntries[DIK_B].ctrl = KEY_CTRL_B; + + gLogicalKeyEntries[DIK_N].unmodified = KEY_LOWERCASE_N; + gLogicalKeyEntries[DIK_N].shift = KEY_UPPERCASE_N; + gLogicalKeyEntries[DIK_N].lmenu = KEY_ALT_N; + gLogicalKeyEntries[DIK_N].rmenu = KEY_ALT_N; + gLogicalKeyEntries[DIK_N].ctrl = KEY_CTRL_N; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_SEMICOLON; + break; + default: + k = DIK_M; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_M; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_M; + gLogicalKeyEntries[k].lmenu = KEY_ALT_M; + gLogicalKeyEntries[k].rmenu = KEY_ALT_M; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_M; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_COMMA; + gLogicalKeyEntries[k].shift = KEY_LESS; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_COMMA; + break; + default: + k = DIK_PERIOD; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_DOT; + gLogicalKeyEntries[k].shift = KEY_GREATER; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_SLASH; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_PERIOD; + break; + default: + k = DIK_7; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_SLASH; + gLogicalKeyEntries[k].shift = KEY_QUESTION; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_RSHIFT].unmodified = -1; + gLogicalKeyEntries[DIK_RSHIFT].shift = -1; + gLogicalKeyEntries[DIK_RSHIFT].lmenu = -1; + gLogicalKeyEntries[DIK_RSHIFT].rmenu = -1; + gLogicalKeyEntries[DIK_RSHIFT].ctrl = -1; + + gLogicalKeyEntries[DIK_LCONTROL].unmodified = -1; + gLogicalKeyEntries[DIK_LCONTROL].shift = -1; + gLogicalKeyEntries[DIK_LCONTROL].lmenu = -1; + gLogicalKeyEntries[DIK_LCONTROL].rmenu = -1; + gLogicalKeyEntries[DIK_LCONTROL].ctrl = -1; + + gLogicalKeyEntries[DIK_LMENU].unmodified = -1; + gLogicalKeyEntries[DIK_LMENU].shift = -1; + gLogicalKeyEntries[DIK_LMENU].lmenu = -1; + gLogicalKeyEntries[DIK_LMENU].rmenu = -1; + gLogicalKeyEntries[DIK_LMENU].ctrl = -1; + + gLogicalKeyEntries[DIK_SPACE].unmodified = KEY_SPACE; + gLogicalKeyEntries[DIK_SPACE].shift = KEY_SPACE; + gLogicalKeyEntries[DIK_SPACE].lmenu = KEY_SPACE; + gLogicalKeyEntries[DIK_SPACE].rmenu = KEY_SPACE; + gLogicalKeyEntries[DIK_SPACE].ctrl = KEY_SPACE; + + gLogicalKeyEntries[DIK_RMENU].unmodified = -1; + gLogicalKeyEntries[DIK_RMENU].shift = -1; + gLogicalKeyEntries[DIK_RMENU].lmenu = -1; + gLogicalKeyEntries[DIK_RMENU].rmenu = -1; + gLogicalKeyEntries[DIK_RMENU].ctrl = -1; + + gLogicalKeyEntries[DIK_RCONTROL].unmodified = -1; + gLogicalKeyEntries[DIK_RCONTROL].shift = -1; + gLogicalKeyEntries[DIK_RCONTROL].lmenu = -1; + gLogicalKeyEntries[DIK_RCONTROL].rmenu = -1; + gLogicalKeyEntries[DIK_RCONTROL].ctrl = -1; + + gLogicalKeyEntries[DIK_INSERT].unmodified = KEY_INSERT; + gLogicalKeyEntries[DIK_INSERT].shift = KEY_INSERT; + gLogicalKeyEntries[DIK_INSERT].lmenu = KEY_ALT_INSERT; + gLogicalKeyEntries[DIK_INSERT].rmenu = KEY_ALT_INSERT; + gLogicalKeyEntries[DIK_INSERT].ctrl = KEY_CTRL_INSERT; + + gLogicalKeyEntries[DIK_HOME].unmodified = KEY_HOME; + gLogicalKeyEntries[DIK_HOME].shift = KEY_HOME; + gLogicalKeyEntries[DIK_HOME].lmenu = KEY_ALT_HOME; + gLogicalKeyEntries[DIK_HOME].rmenu = KEY_ALT_HOME; + gLogicalKeyEntries[DIK_HOME].ctrl = KEY_CTRL_HOME; + + gLogicalKeyEntries[DIK_PRIOR].unmodified = KEY_PAGE_UP; + gLogicalKeyEntries[DIK_PRIOR].shift = KEY_PAGE_UP; + gLogicalKeyEntries[DIK_PRIOR].lmenu = KEY_ALT_PAGE_UP; + gLogicalKeyEntries[DIK_PRIOR].rmenu = KEY_ALT_PAGE_UP; + gLogicalKeyEntries[DIK_PRIOR].ctrl = KEY_CTRL_PAGE_UP; + + gLogicalKeyEntries[DIK_DELETE].unmodified = KEY_DELETE; + gLogicalKeyEntries[DIK_DELETE].shift = KEY_DELETE; + gLogicalKeyEntries[DIK_DELETE].lmenu = KEY_ALT_DELETE; + gLogicalKeyEntries[DIK_DELETE].rmenu = KEY_ALT_DELETE; + gLogicalKeyEntries[DIK_DELETE].ctrl = KEY_CTRL_DELETE; + + gLogicalKeyEntries[DIK_END].unmodified = KEY_END; + gLogicalKeyEntries[DIK_END].shift = KEY_END; + gLogicalKeyEntries[DIK_END].lmenu = KEY_ALT_END; + gLogicalKeyEntries[DIK_END].rmenu = KEY_ALT_END; + gLogicalKeyEntries[DIK_END].ctrl = KEY_CTRL_END; + + gLogicalKeyEntries[DIK_NEXT].unmodified = KEY_PAGE_DOWN; + gLogicalKeyEntries[DIK_NEXT].shift = KEY_PAGE_DOWN; + gLogicalKeyEntries[DIK_NEXT].lmenu = KEY_ALT_PAGE_DOWN; + gLogicalKeyEntries[DIK_NEXT].rmenu = KEY_ALT_PAGE_DOWN; + gLogicalKeyEntries[DIK_NEXT].ctrl = KEY_CTRL_PAGE_DOWN; + + gLogicalKeyEntries[DIK_UP].unmodified = KEY_ARROW_UP; + gLogicalKeyEntries[DIK_UP].shift = KEY_ARROW_UP; + gLogicalKeyEntries[DIK_UP].lmenu = KEY_ALT_ARROW_UP; + gLogicalKeyEntries[DIK_UP].rmenu = KEY_ALT_ARROW_UP; + gLogicalKeyEntries[DIK_UP].ctrl = KEY_CTRL_ARROW_UP; + + gLogicalKeyEntries[DIK_DOWN].unmodified = KEY_ARROW_DOWN; + gLogicalKeyEntries[DIK_DOWN].shift = KEY_ARROW_DOWN; + gLogicalKeyEntries[DIK_DOWN].lmenu = KEY_ALT_ARROW_DOWN; + gLogicalKeyEntries[DIK_DOWN].rmenu = KEY_ALT_ARROW_DOWN; + gLogicalKeyEntries[DIK_DOWN].ctrl = KEY_CTRL_ARROW_DOWN; + + gLogicalKeyEntries[DIK_LEFT].unmodified = KEY_ARROW_LEFT; + gLogicalKeyEntries[DIK_LEFT].shift = KEY_ARROW_LEFT; + gLogicalKeyEntries[DIK_LEFT].lmenu = KEY_ALT_ARROW_LEFT; + gLogicalKeyEntries[DIK_LEFT].rmenu = KEY_ALT_ARROW_LEFT; + gLogicalKeyEntries[DIK_LEFT].ctrl = KEY_CTRL_ARROW_LEFT; + + gLogicalKeyEntries[DIK_RIGHT].unmodified = KEY_ARROW_RIGHT; + gLogicalKeyEntries[DIK_RIGHT].shift = KEY_ARROW_RIGHT; + gLogicalKeyEntries[DIK_RIGHT].lmenu = KEY_ALT_ARROW_RIGHT; + gLogicalKeyEntries[DIK_RIGHT].rmenu = KEY_ALT_ARROW_RIGHT; + gLogicalKeyEntries[DIK_RIGHT].ctrl = KEY_CTRL_ARROW_RIGHT; + + gLogicalKeyEntries[DIK_NUMLOCK].unmodified = -1; + gLogicalKeyEntries[DIK_NUMLOCK].shift = -1; + gLogicalKeyEntries[DIK_NUMLOCK].lmenu = -1; + gLogicalKeyEntries[DIK_NUMLOCK].rmenu = -1; + gLogicalKeyEntries[DIK_NUMLOCK].ctrl = -1; + + gLogicalKeyEntries[DIK_DIVIDE].unmodified = KEY_SLASH; + gLogicalKeyEntries[DIK_DIVIDE].shift = KEY_SLASH; + gLogicalKeyEntries[DIK_DIVIDE].lmenu = -1; + gLogicalKeyEntries[DIK_DIVIDE].rmenu = -1; + gLogicalKeyEntries[DIK_DIVIDE].ctrl = 3; + + gLogicalKeyEntries[DIK_MULTIPLY].unmodified = KEY_ASTERISK; + gLogicalKeyEntries[DIK_MULTIPLY].shift = KEY_ASTERISK; + gLogicalKeyEntries[DIK_MULTIPLY].lmenu = -1; + gLogicalKeyEntries[DIK_MULTIPLY].rmenu = -1; + gLogicalKeyEntries[DIK_MULTIPLY].ctrl = -1; + + gLogicalKeyEntries[DIK_SUBTRACT].unmodified = KEY_MINUS; + gLogicalKeyEntries[DIK_SUBTRACT].shift = KEY_MINUS; + gLogicalKeyEntries[DIK_SUBTRACT].lmenu = -1; + gLogicalKeyEntries[DIK_SUBTRACT].rmenu = -1; + gLogicalKeyEntries[DIK_SUBTRACT].ctrl = -1; + + gLogicalKeyEntries[DIK_NUMPAD7].unmodified = KEY_HOME; + gLogicalKeyEntries[DIK_NUMPAD7].shift = KEY_7; + gLogicalKeyEntries[DIK_NUMPAD7].lmenu = KEY_ALT_HOME; + gLogicalKeyEntries[DIK_NUMPAD7].rmenu = KEY_ALT_HOME; + gLogicalKeyEntries[DIK_NUMPAD7].ctrl = KEY_CTRL_HOME; + + gLogicalKeyEntries[DIK_NUMPAD8].unmodified = KEY_ARROW_UP; + gLogicalKeyEntries[DIK_NUMPAD8].shift = KEY_8; + gLogicalKeyEntries[DIK_NUMPAD8].lmenu = KEY_ALT_ARROW_UP; + gLogicalKeyEntries[DIK_NUMPAD8].rmenu = KEY_ALT_ARROW_UP; + gLogicalKeyEntries[DIK_NUMPAD8].ctrl = KEY_CTRL_ARROW_UP; + + gLogicalKeyEntries[DIK_NUMPAD9].unmodified = KEY_PAGE_UP; + gLogicalKeyEntries[DIK_NUMPAD9].shift = KEY_9; + gLogicalKeyEntries[DIK_NUMPAD9].lmenu = KEY_ALT_PAGE_UP; + gLogicalKeyEntries[DIK_NUMPAD9].rmenu = KEY_ALT_PAGE_UP; + gLogicalKeyEntries[DIK_NUMPAD9].ctrl = KEY_CTRL_PAGE_UP; + + gLogicalKeyEntries[DIK_ADD].unmodified = KEY_PLUS; + gLogicalKeyEntries[DIK_ADD].shift = KEY_PLUS; + gLogicalKeyEntries[DIK_ADD].lmenu = -1; + gLogicalKeyEntries[DIK_ADD].rmenu = -1; + gLogicalKeyEntries[DIK_ADD].ctrl = -1; + + gLogicalKeyEntries[DIK_NUMPAD4].unmodified = KEY_ARROW_LEFT; + gLogicalKeyEntries[DIK_NUMPAD4].shift = KEY_4; + gLogicalKeyEntries[DIK_NUMPAD4].lmenu = KEY_ALT_ARROW_LEFT; + gLogicalKeyEntries[DIK_NUMPAD4].rmenu = KEY_ALT_ARROW_LEFT; + gLogicalKeyEntries[DIK_NUMPAD4].ctrl = KEY_CTRL_ARROW_LEFT; + + gLogicalKeyEntries[DIK_NUMPAD5].unmodified = KEY_NUMBERPAD_5; + gLogicalKeyEntries[DIK_NUMPAD5].shift = KEY_5; + gLogicalKeyEntries[DIK_NUMPAD5].lmenu = KEY_ALT_NUMBERPAD_5; + gLogicalKeyEntries[DIK_NUMPAD5].rmenu = KEY_ALT_NUMBERPAD_5; + gLogicalKeyEntries[DIK_NUMPAD5].ctrl = KEY_CTRL_NUMBERPAD_5; + + gLogicalKeyEntries[DIK_NUMPAD6].unmodified = KEY_ARROW_RIGHT; + gLogicalKeyEntries[DIK_NUMPAD6].shift = KEY_6; + gLogicalKeyEntries[DIK_NUMPAD6].lmenu = KEY_ALT_ARROW_RIGHT; + gLogicalKeyEntries[DIK_NUMPAD6].rmenu = KEY_ALT_ARROW_RIGHT; + gLogicalKeyEntries[DIK_NUMPAD6].ctrl = KEY_CTRL_ARROW_RIGHT; + + gLogicalKeyEntries[DIK_NUMPAD1].unmodified = KEY_END; + gLogicalKeyEntries[DIK_NUMPAD1].shift = KEY_1; + gLogicalKeyEntries[DIK_NUMPAD1].lmenu = KEY_ALT_END; + gLogicalKeyEntries[DIK_NUMPAD1].rmenu = KEY_ALT_END; + gLogicalKeyEntries[DIK_NUMPAD1].ctrl = KEY_CTRL_END; + + gLogicalKeyEntries[DIK_NUMPAD2].unmodified = KEY_ARROW_DOWN; + gLogicalKeyEntries[DIK_NUMPAD2].shift = KEY_2; + gLogicalKeyEntries[DIK_NUMPAD2].lmenu = KEY_ALT_ARROW_DOWN; + gLogicalKeyEntries[DIK_NUMPAD2].rmenu = KEY_ALT_ARROW_DOWN; + gLogicalKeyEntries[DIK_NUMPAD2].ctrl = KEY_CTRL_ARROW_DOWN; + + gLogicalKeyEntries[DIK_NUMPAD3].unmodified = KEY_PAGE_DOWN; + gLogicalKeyEntries[DIK_NUMPAD3].shift = KEY_3; + gLogicalKeyEntries[DIK_NUMPAD3].lmenu = KEY_ALT_PAGE_DOWN; + gLogicalKeyEntries[DIK_NUMPAD3].rmenu = KEY_ALT_PAGE_DOWN; + gLogicalKeyEntries[DIK_NUMPAD3].ctrl = KEY_CTRL_PAGE_DOWN; + + gLogicalKeyEntries[DIK_NUMPADENTER].unmodified = KEY_RETURN; + gLogicalKeyEntries[DIK_NUMPADENTER].shift = KEY_RETURN; + gLogicalKeyEntries[DIK_NUMPADENTER].lmenu = -1; + gLogicalKeyEntries[DIK_NUMPADENTER].rmenu = -1; + gLogicalKeyEntries[DIK_NUMPADENTER].ctrl = -1; + + gLogicalKeyEntries[DIK_NUMPAD0].unmodified = KEY_INSERT; + gLogicalKeyEntries[DIK_NUMPAD0].shift = KEY_0; + gLogicalKeyEntries[DIK_NUMPAD0].lmenu = KEY_ALT_INSERT; + gLogicalKeyEntries[DIK_NUMPAD0].rmenu = KEY_ALT_INSERT; + gLogicalKeyEntries[DIK_NUMPAD0].ctrl = KEY_CTRL_INSERT; + + gLogicalKeyEntries[DIK_DECIMAL].unmodified = KEY_DELETE; + gLogicalKeyEntries[DIK_DECIMAL].shift = KEY_DOT; + gLogicalKeyEntries[DIK_DECIMAL].lmenu = -1; + gLogicalKeyEntries[DIK_DECIMAL].rmenu = KEY_ALT_DELETE; + gLogicalKeyEntries[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE; +} + +// 0x4D0400 +void keyboardBuildFrenchConfiguration() +{ + int k; + + keyboardBuildQwertyConfiguration(); + + gLogicalKeyEntries[DIK_GRAVE].unmodified = KEY_178; + gLogicalKeyEntries[DIK_GRAVE].shift = -1; + gLogicalKeyEntries[DIK_GRAVE].lmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].rmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].ctrl = -1; + + gLogicalKeyEntries[DIK_1].unmodified = KEY_AMPERSAND; + gLogicalKeyEntries[DIK_1].shift = KEY_1; + gLogicalKeyEntries[DIK_1].lmenu = -1; + gLogicalKeyEntries[DIK_1].rmenu = -1; + gLogicalKeyEntries[DIK_1].ctrl = -1; + + gLogicalKeyEntries[DIK_2].unmodified = KEY_233; + gLogicalKeyEntries[DIK_2].shift = KEY_2; + gLogicalKeyEntries[DIK_2].lmenu = -1; + gLogicalKeyEntries[DIK_2].rmenu = KEY_152; + gLogicalKeyEntries[DIK_2].ctrl = -1; + + gLogicalKeyEntries[DIK_3].unmodified = KEY_QUOTE; + gLogicalKeyEntries[DIK_3].shift = KEY_3; + gLogicalKeyEntries[DIK_3].lmenu = -1; + gLogicalKeyEntries[DIK_3].rmenu = KEY_NUMBER_SIGN; + gLogicalKeyEntries[DIK_3].ctrl = -1; + + gLogicalKeyEntries[DIK_4].unmodified = KEY_SINGLE_QUOTE; + gLogicalKeyEntries[DIK_4].shift = KEY_4; + gLogicalKeyEntries[DIK_4].lmenu = -1; + gLogicalKeyEntries[DIK_4].rmenu = KEY_BRACE_LEFT; + gLogicalKeyEntries[DIK_4].ctrl = -1; + + gLogicalKeyEntries[DIK_5].unmodified = KEY_PAREN_LEFT; + gLogicalKeyEntries[DIK_5].shift = KEY_5; + gLogicalKeyEntries[DIK_5].lmenu = -1; + gLogicalKeyEntries[DIK_5].rmenu = KEY_BRACKET_LEFT; + gLogicalKeyEntries[DIK_5].ctrl = -1; + + gLogicalKeyEntries[DIK_6].unmodified = KEY_150; + gLogicalKeyEntries[DIK_6].shift = KEY_6; + gLogicalKeyEntries[DIK_6].lmenu = -1; + gLogicalKeyEntries[DIK_6].rmenu = KEY_166; + gLogicalKeyEntries[DIK_6].ctrl = -1; + + gLogicalKeyEntries[DIK_7].unmodified = KEY_232; + gLogicalKeyEntries[DIK_7].shift = KEY_7; + gLogicalKeyEntries[DIK_7].lmenu = -1; + gLogicalKeyEntries[DIK_7].rmenu = KEY_GRAVE; + gLogicalKeyEntries[DIK_7].ctrl = -1; + + gLogicalKeyEntries[DIK_8].unmodified = KEY_UNDERSCORE; + gLogicalKeyEntries[DIK_8].shift = KEY_8; + gLogicalKeyEntries[DIK_8].lmenu = -1; + gLogicalKeyEntries[DIK_8].rmenu = KEY_BACKSLASH; + gLogicalKeyEntries[DIK_8].ctrl = -1; + + gLogicalKeyEntries[DIK_9].unmodified = KEY_231; + gLogicalKeyEntries[DIK_9].shift = KEY_9; + gLogicalKeyEntries[DIK_9].lmenu = -1; + gLogicalKeyEntries[DIK_9].rmenu = KEY_136; + gLogicalKeyEntries[DIK_9].ctrl = -1; + + gLogicalKeyEntries[DIK_0].unmodified = KEY_224; + gLogicalKeyEntries[DIK_0].shift = KEY_0; + gLogicalKeyEntries[DIK_0].lmenu = -1; + gLogicalKeyEntries[DIK_0].rmenu = KEY_AT; + gLogicalKeyEntries[DIK_0].ctrl = -1; + + gLogicalKeyEntries[DIK_MINUS].unmodified = KEY_PAREN_RIGHT; + gLogicalKeyEntries[DIK_MINUS].shift = KEY_176; + gLogicalKeyEntries[DIK_MINUS].lmenu = -1; + gLogicalKeyEntries[DIK_MINUS].rmenu = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[DIK_MINUS].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_EQUALS; + break; + default: + k = DIK_0; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_EQUAL; + gLogicalKeyEntries[k].shift = KEY_PLUS; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = KEY_BRACE_RIGHT; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_LBRACKET].unmodified = KEY_136; + gLogicalKeyEntries[DIK_LBRACKET].shift = KEY_168; + gLogicalKeyEntries[DIK_LBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].rmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_RBRACKET].unmodified = KEY_DOLLAR; + gLogicalKeyEntries[DIK_RBRACKET].shift = KEY_163; + gLogicalKeyEntries[DIK_RBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_RBRACKET].rmenu = KEY_164; + gLogicalKeyEntries[DIK_RBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_APOSTROPHE].unmodified = KEY_249; + gLogicalKeyEntries[DIK_APOSTROPHE].shift = KEY_PERCENT; + gLogicalKeyEntries[DIK_APOSTROPHE].lmenu = -1; + gLogicalKeyEntries[DIK_APOSTROPHE].rmenu = -1; + gLogicalKeyEntries[DIK_APOSTROPHE].ctrl = -1; + + gLogicalKeyEntries[DIK_BACKSLASH].unmodified = KEY_ASTERISK; + gLogicalKeyEntries[DIK_BACKSLASH].shift = KEY_181; + gLogicalKeyEntries[DIK_BACKSLASH].lmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].rmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].ctrl = -1; + + gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_COMMA; + gLogicalKeyEntries[k].shift = KEY_QUESTION; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_SEMICOLON; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_SEMICOLON; + gLogicalKeyEntries[k].shift = KEY_DOT; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + 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; + } + + gLogicalKeyEntries[k].unmodified = KEY_COLON; + gLogicalKeyEntries[k].shift = KEY_SLASH; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_SLASH].unmodified = KEY_EXCLAMATION; + gLogicalKeyEntries[DIK_SLASH].shift = KEY_167; + gLogicalKeyEntries[DIK_SLASH].lmenu = -1; + gLogicalKeyEntries[DIK_SLASH].rmenu = -1; + gLogicalKeyEntries[DIK_SLASH].ctrl = -1; +} + +// 0x4D0C54 +void keyboardBuildGermanConfiguration() +{ + int k; + + keyboardBuildQwertyConfiguration(); + + gLogicalKeyEntries[DIK_GRAVE].unmodified = KEY_136; + gLogicalKeyEntries[DIK_GRAVE].shift = KEY_186; + gLogicalKeyEntries[DIK_GRAVE].lmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].rmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].ctrl = -1; + + gLogicalKeyEntries[DIK_2].unmodified = KEY_2; + gLogicalKeyEntries[DIK_2].shift = KEY_QUOTE; + gLogicalKeyEntries[DIK_2].lmenu = -1; + gLogicalKeyEntries[DIK_2].rmenu = KEY_178; + gLogicalKeyEntries[DIK_2].ctrl = -1; + + gLogicalKeyEntries[DIK_3].unmodified = KEY_3; + gLogicalKeyEntries[DIK_3].shift = KEY_167; + gLogicalKeyEntries[DIK_3].lmenu = -1; + gLogicalKeyEntries[DIK_3].rmenu = KEY_179; + gLogicalKeyEntries[DIK_3].ctrl = -1; + + gLogicalKeyEntries[DIK_6].unmodified = KEY_6; + gLogicalKeyEntries[DIK_6].shift = KEY_AMPERSAND; + gLogicalKeyEntries[DIK_6].lmenu = -1; + gLogicalKeyEntries[DIK_6].rmenu = -1; + gLogicalKeyEntries[DIK_6].ctrl = -1; + + gLogicalKeyEntries[DIK_7].unmodified = KEY_7; + gLogicalKeyEntries[DIK_7].shift = KEY_166; + gLogicalKeyEntries[DIK_7].lmenu = -1; + gLogicalKeyEntries[DIK_7].rmenu = KEY_BRACE_LEFT; + gLogicalKeyEntries[DIK_7].ctrl = -1; + + gLogicalKeyEntries[DIK_8].unmodified = KEY_8; + gLogicalKeyEntries[DIK_8].shift = KEY_PAREN_LEFT; + gLogicalKeyEntries[DIK_8].lmenu = -1; + gLogicalKeyEntries[DIK_8].rmenu = KEY_BRACKET_LEFT; + gLogicalKeyEntries[DIK_8].ctrl = -1; + + gLogicalKeyEntries[DIK_9].unmodified = KEY_9; + gLogicalKeyEntries[DIK_9].shift = KEY_PAREN_RIGHT; + gLogicalKeyEntries[DIK_9].lmenu = -1; + gLogicalKeyEntries[DIK_9].rmenu = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[DIK_9].ctrl = -1; + + gLogicalKeyEntries[DIK_0].unmodified = KEY_0; + gLogicalKeyEntries[DIK_0].shift = KEY_EQUAL; + gLogicalKeyEntries[DIK_0].lmenu = -1; + gLogicalKeyEntries[DIK_0].rmenu = KEY_BRACE_RIGHT; + gLogicalKeyEntries[DIK_0].ctrl = -1; + + gLogicalKeyEntries[DIK_MINUS].unmodified = KEY_223; + gLogicalKeyEntries[DIK_MINUS].shift = KEY_QUESTION; + gLogicalKeyEntries[DIK_MINUS].lmenu = -1; + gLogicalKeyEntries[DIK_MINUS].rmenu = KEY_BACKSLASH; + gLogicalKeyEntries[DIK_MINUS].ctrl = -1; + + gLogicalKeyEntries[DIK_EQUALS].unmodified = KEY_180; + gLogicalKeyEntries[DIK_EQUALS].shift = KEY_GRAVE; + gLogicalKeyEntries[DIK_EQUALS].lmenu = -1; + gLogicalKeyEntries[DIK_EQUALS].rmenu = -1; + gLogicalKeyEntries[DIK_EQUALS].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_A; + break; + default: + k = DIK_Q; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_Q; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_Q; + gLogicalKeyEntries[k].lmenu = KEY_ALT_Q; + gLogicalKeyEntries[k].rmenu = KEY_AT; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_Q; + + gLogicalKeyEntries[DIK_LBRACKET].unmodified = KEY_252; + gLogicalKeyEntries[DIK_LBRACKET].shift = KEY_220; + gLogicalKeyEntries[DIK_LBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].rmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_EQUALS; + break; + default: + k = DIK_RBRACKET; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_PLUS; + gLogicalKeyEntries[k].shift = KEY_ASTERISK; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = KEY_152; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_SEMICOLON].unmodified = KEY_246; + gLogicalKeyEntries[DIK_SEMICOLON].shift = KEY_214; + gLogicalKeyEntries[DIK_SEMICOLON].lmenu = -1; + gLogicalKeyEntries[DIK_SEMICOLON].rmenu = -1; + gLogicalKeyEntries[DIK_SEMICOLON].ctrl = -1; + + gLogicalKeyEntries[DIK_APOSTROPHE].unmodified = KEY_228; + gLogicalKeyEntries[DIK_APOSTROPHE].shift = KEY_196; + gLogicalKeyEntries[DIK_APOSTROPHE].lmenu = -1; + gLogicalKeyEntries[DIK_APOSTROPHE].rmenu = -1; + gLogicalKeyEntries[DIK_APOSTROPHE].ctrl = -1; + + gLogicalKeyEntries[DIK_BACKSLASH].unmodified = KEY_NUMBER_SIGN; + gLogicalKeyEntries[DIK_BACKSLASH].shift = KEY_SINGLE_QUOTE; + gLogicalKeyEntries[DIK_BACKSLASH].lmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].rmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].ctrl = -1; + + gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].rmenu = KEY_166; + gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_SEMICOLON; + break; + default: + k = DIK_M; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_LOWERCASE_M; + gLogicalKeyEntries[k].shift = KEY_UPPERCASE_M; + gLogicalKeyEntries[k].lmenu = KEY_ALT_M; + gLogicalKeyEntries[k].rmenu = KEY_181; + gLogicalKeyEntries[k].ctrl = KEY_CTRL_M; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_COMMA; + gLogicalKeyEntries[k].shift = KEY_SEMICOLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_COMMA; + break; + default: + k = DIK_PERIOD; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_DOT; + gLogicalKeyEntries[k].shift = KEY_COLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_MINUS; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_6; + break; + default: + k = DIK_SLASH; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_150; + gLogicalKeyEntries[k].shift = KEY_151; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + gLogicalKeyEntries[DIK_DIVIDE].unmodified = KEY_247; + gLogicalKeyEntries[DIK_DIVIDE].shift = KEY_247; + gLogicalKeyEntries[DIK_DIVIDE].lmenu = -1; + gLogicalKeyEntries[DIK_DIVIDE].rmenu = -1; + gLogicalKeyEntries[DIK_DIVIDE].ctrl = -1; + + gLogicalKeyEntries[DIK_MULTIPLY].unmodified = KEY_215; + gLogicalKeyEntries[DIK_MULTIPLY].shift = KEY_215; + gLogicalKeyEntries[DIK_MULTIPLY].lmenu = -1; + gLogicalKeyEntries[DIK_MULTIPLY].rmenu = -1; + gLogicalKeyEntries[DIK_MULTIPLY].ctrl = -1; + + gLogicalKeyEntries[DIK_DECIMAL].unmodified = KEY_DELETE; + gLogicalKeyEntries[DIK_DECIMAL].shift = KEY_COMMA; + gLogicalKeyEntries[DIK_DECIMAL].lmenu = -1; + gLogicalKeyEntries[DIK_DECIMAL].rmenu = KEY_ALT_DELETE; + gLogicalKeyEntries[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE; +} + +// 0x4D1758 +void keyboardBuildItalianConfiguration() +{ + int k; + + keyboardBuildQwertyConfiguration(); + + gLogicalKeyEntries[DIK_GRAVE].unmodified = KEY_BACKSLASH; + gLogicalKeyEntries[DIK_GRAVE].shift = KEY_BAR; + gLogicalKeyEntries[DIK_GRAVE].lmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].rmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].ctrl = -1; + + gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + + gLogicalKeyEntries[DIK_1].unmodified = KEY_1; + gLogicalKeyEntries[DIK_1].shift = KEY_EXCLAMATION; + gLogicalKeyEntries[DIK_1].lmenu = -1; + gLogicalKeyEntries[DIK_1].rmenu = -1; + gLogicalKeyEntries[DIK_1].ctrl = -1; + + gLogicalKeyEntries[DIK_2].unmodified = KEY_2; + gLogicalKeyEntries[DIK_2].shift = KEY_QUOTE; + gLogicalKeyEntries[DIK_2].lmenu = -1; + gLogicalKeyEntries[DIK_2].rmenu = -1; + gLogicalKeyEntries[DIK_2].ctrl = -1; + + gLogicalKeyEntries[DIK_3].unmodified = KEY_3; + gLogicalKeyEntries[DIK_3].shift = KEY_163; + gLogicalKeyEntries[DIK_3].lmenu = -1; + gLogicalKeyEntries[DIK_3].rmenu = -1; + gLogicalKeyEntries[DIK_3].ctrl = -1; + + gLogicalKeyEntries[DIK_6].unmodified = KEY_6; + gLogicalKeyEntries[DIK_6].shift = KEY_AMPERSAND; + gLogicalKeyEntries[DIK_6].lmenu = -1; + gLogicalKeyEntries[DIK_6].rmenu = -1; + gLogicalKeyEntries[DIK_6].ctrl = -1; + + gLogicalKeyEntries[DIK_7].unmodified = KEY_7; + gLogicalKeyEntries[DIK_7].shift = KEY_SLASH; + gLogicalKeyEntries[DIK_7].lmenu = -1; + gLogicalKeyEntries[DIK_7].rmenu = -1; + gLogicalKeyEntries[DIK_7].ctrl = -1; + + gLogicalKeyEntries[DIK_8].unmodified = KEY_8; + gLogicalKeyEntries[DIK_8].shift = KEY_PAREN_LEFT; + gLogicalKeyEntries[DIK_8].lmenu = -1; + gLogicalKeyEntries[DIK_8].rmenu = -1; + gLogicalKeyEntries[DIK_8].ctrl = -1; + + gLogicalKeyEntries[DIK_9].unmodified = KEY_9; + gLogicalKeyEntries[DIK_9].shift = KEY_PAREN_RIGHT; + gLogicalKeyEntries[DIK_9].lmenu = -1; + gLogicalKeyEntries[DIK_9].rmenu = -1; + gLogicalKeyEntries[DIK_9].ctrl = -1; + + gLogicalKeyEntries[DIK_0].unmodified = KEY_0; + gLogicalKeyEntries[DIK_0].shift = KEY_EQUAL; + gLogicalKeyEntries[DIK_0].lmenu = -1; + gLogicalKeyEntries[DIK_0].rmenu = -1; + gLogicalKeyEntries[DIK_0].ctrl = -1; + + gLogicalKeyEntries[DIK_MINUS].unmodified = KEY_SINGLE_QUOTE; + gLogicalKeyEntries[DIK_MINUS].shift = KEY_QUESTION; + gLogicalKeyEntries[DIK_MINUS].lmenu = -1; + gLogicalKeyEntries[DIK_MINUS].rmenu = -1; + gLogicalKeyEntries[DIK_MINUS].ctrl = -1; + + gLogicalKeyEntries[DIK_LBRACKET].unmodified = KEY_232; + gLogicalKeyEntries[DIK_LBRACKET].shift = KEY_233; + gLogicalKeyEntries[DIK_LBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].rmenu = KEY_BRACKET_LEFT; + gLogicalKeyEntries[DIK_LBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_RBRACKET].unmodified = KEY_PLUS; + gLogicalKeyEntries[DIK_RBRACKET].shift = KEY_ASTERISK; + gLogicalKeyEntries[DIK_RBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_RBRACKET].rmenu = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[DIK_RBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_BACKSLASH].unmodified = KEY_249; + gLogicalKeyEntries[DIK_BACKSLASH].shift = KEY_167; + gLogicalKeyEntries[DIK_BACKSLASH].lmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].rmenu = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[DIK_BACKSLASH].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_COMMA; + gLogicalKeyEntries[k].shift = KEY_SEMICOLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_COMMA; + break; + default: + k = DIK_PERIOD; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_DOT; + gLogicalKeyEntries[k].shift = KEY_COLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_MINUS; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_6; + break; + default: + k = DIK_SLASH; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_MINUS; + gLogicalKeyEntries[k].shift = KEY_UNDERSCORE; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; +} + +// 0x4D1E24 +void keyboardBuildSpanishConfiguration() +{ + int k; + + keyboardBuildQwertyConfiguration(); + + gLogicalKeyEntries[DIK_1].unmodified = KEY_1; + gLogicalKeyEntries[DIK_1].shift = KEY_EXCLAMATION; + gLogicalKeyEntries[DIK_1].lmenu = -1; + gLogicalKeyEntries[DIK_1].rmenu = KEY_BAR; + gLogicalKeyEntries[DIK_1].ctrl = -1; + + gLogicalKeyEntries[DIK_2].unmodified = KEY_2; + gLogicalKeyEntries[DIK_2].shift = KEY_QUOTE; + gLogicalKeyEntries[DIK_2].lmenu = -1; + gLogicalKeyEntries[DIK_2].rmenu = KEY_AT; + gLogicalKeyEntries[DIK_2].ctrl = -1; + + gLogicalKeyEntries[DIK_3].unmodified = KEY_3; + gLogicalKeyEntries[DIK_3].shift = KEY_149; + gLogicalKeyEntries[DIK_3].lmenu = -1; + gLogicalKeyEntries[DIK_3].rmenu = KEY_NUMBER_SIGN; + gLogicalKeyEntries[DIK_3].ctrl = -1; + + gLogicalKeyEntries[DIK_6].unmodified = KEY_6; + gLogicalKeyEntries[DIK_6].shift = KEY_AMPERSAND; + gLogicalKeyEntries[DIK_6].lmenu = -1; + gLogicalKeyEntries[DIK_6].rmenu = KEY_172; + gLogicalKeyEntries[DIK_6].ctrl = -1; + + gLogicalKeyEntries[DIK_7].unmodified = KEY_7; + gLogicalKeyEntries[DIK_7].shift = KEY_SLASH; + gLogicalKeyEntries[DIK_7].lmenu = -1; + gLogicalKeyEntries[DIK_7].rmenu = -1; + gLogicalKeyEntries[DIK_7].ctrl = -1; + + gLogicalKeyEntries[DIK_8].unmodified = KEY_8; + gLogicalKeyEntries[DIK_8].shift = KEY_PAREN_LEFT; + gLogicalKeyEntries[DIK_8].lmenu = -1; + gLogicalKeyEntries[DIK_8].rmenu = -1; + gLogicalKeyEntries[DIK_8].ctrl = -1; + + gLogicalKeyEntries[DIK_9].unmodified = KEY_9; + gLogicalKeyEntries[DIK_9].shift = KEY_PAREN_RIGHT; + gLogicalKeyEntries[DIK_9].lmenu = -1; + gLogicalKeyEntries[DIK_9].rmenu = -1; + gLogicalKeyEntries[DIK_9].ctrl = -1; + + gLogicalKeyEntries[DIK_0].unmodified = KEY_0; + gLogicalKeyEntries[DIK_0].shift = KEY_EQUAL; + gLogicalKeyEntries[DIK_0].lmenu = -1; + gLogicalKeyEntries[DIK_0].rmenu = -1; + gLogicalKeyEntries[DIK_0].ctrl = -1; + + gLogicalKeyEntries[DIK_MINUS].unmodified = KEY_146; + gLogicalKeyEntries[DIK_MINUS].shift = KEY_QUESTION; + gLogicalKeyEntries[DIK_MINUS].lmenu = -1; + gLogicalKeyEntries[DIK_MINUS].rmenu = -1; + gLogicalKeyEntries[DIK_MINUS].ctrl = -1; + + gLogicalKeyEntries[DIK_EQUALS].unmodified = KEY_161; + gLogicalKeyEntries[DIK_EQUALS].shift = KEY_191; + gLogicalKeyEntries[DIK_EQUALS].lmenu = -1; + gLogicalKeyEntries[DIK_EQUALS].rmenu = -1; + gLogicalKeyEntries[DIK_EQUALS].ctrl = -1; + + gLogicalKeyEntries[DIK_GRAVE].unmodified = KEY_176; + gLogicalKeyEntries[DIK_GRAVE].shift = KEY_170; + gLogicalKeyEntries[DIK_GRAVE].lmenu = -1; + gLogicalKeyEntries[DIK_GRAVE].rmenu = KEY_BACKSLASH; + gLogicalKeyEntries[DIK_GRAVE].ctrl = -1; + + gLogicalKeyEntries[DIK_LBRACKET].unmodified = KEY_GRAVE; + gLogicalKeyEntries[DIK_LBRACKET].shift = KEY_CARET; + gLogicalKeyEntries[DIK_LBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_LBRACKET].rmenu = KEY_BRACKET_LEFT; + gLogicalKeyEntries[DIK_LBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_RBRACKET].unmodified = KEY_PLUS; + gLogicalKeyEntries[DIK_RBRACKET].shift = KEY_ASTERISK; + gLogicalKeyEntries[DIK_RBRACKET].lmenu = -1; + gLogicalKeyEntries[DIK_RBRACKET].rmenu = KEY_BRACKET_RIGHT; + gLogicalKeyEntries[DIK_RBRACKET].ctrl = -1; + + gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + + gLogicalKeyEntries[DIK_SEMICOLON].unmodified = KEY_241; + gLogicalKeyEntries[DIK_SEMICOLON].shift = KEY_209; + gLogicalKeyEntries[DIK_SEMICOLON].lmenu = -1; + gLogicalKeyEntries[DIK_SEMICOLON].rmenu = -1; + gLogicalKeyEntries[DIK_SEMICOLON].ctrl = -1; + + gLogicalKeyEntries[DIK_APOSTROPHE].unmodified = KEY_168; + gLogicalKeyEntries[DIK_APOSTROPHE].shift = KEY_180; + gLogicalKeyEntries[DIK_APOSTROPHE].lmenu = -1; + gLogicalKeyEntries[DIK_APOSTROPHE].rmenu = KEY_BRACE_LEFT; + gLogicalKeyEntries[DIK_APOSTROPHE].ctrl = -1; + + gLogicalKeyEntries[DIK_BACKSLASH].unmodified = KEY_231; + gLogicalKeyEntries[DIK_BACKSLASH].shift = KEY_199; + gLogicalKeyEntries[DIK_BACKSLASH].lmenu = -1; + gLogicalKeyEntries[DIK_BACKSLASH].rmenu = KEY_BRACE_RIGHT; + gLogicalKeyEntries[DIK_BACKSLASH].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_M; + break; + default: + k = DIK_COMMA; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_COMMA; + gLogicalKeyEntries[k].shift = KEY_SEMICOLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_COMMA; + break; + default: + k = DIK_PERIOD; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_DOT; + gLogicalKeyEntries[k].shift = KEY_COLON; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; + + switch (gKeyboardLayout) { + case KEYBOARD_LAYOUT_QWERTY: + k = DIK_MINUS; + break; + case KEYBOARD_LAYOUT_FRENCH: + k = DIK_6; + break; + default: + k = DIK_SLASH; + break; + } + + gLogicalKeyEntries[k].unmodified = KEY_MINUS; + gLogicalKeyEntries[k].shift = KEY_UNDERSCORE; + gLogicalKeyEntries[k].lmenu = -1; + gLogicalKeyEntries[k].rmenu = -1; + gLogicalKeyEntries[k].ctrl = -1; +} + +// 0x4D24F8 +void _kb_init_lock_status() +{ + if (GetKeyState(VK_CAPITAL) & 1) { + gModifierKeysState |= MODIFIER_KEY_STATE_CAPS_LOCK; + } + + if (GetKeyState(VK_NUMLOCK) & 1) { + gModifierKeysState |= MODIFIER_KEY_STATE_NUM_LOCK; + } + + if (GetKeyState(VK_SCROLL) & 1) { + gModifierKeysState |= MODIFIER_KEY_STATE_SCROLL_LOCK; + } +} + +// Get pointer to pending key event from the queue but do not consume it. +// +// 0x4D2614 +int keyboardPeekEvent(int index, KeyboardEvent** keyboardEventPtr) +{ + int rc = -1; + + if (gKeyboardEventQueueReadIndex != gKeyboardEventQueueWriteIndex) { + int end; + if (gKeyboardEventQueueWriteIndex <= gKeyboardEventQueueReadIndex) { + end = gKeyboardEventQueueWriteIndex + KEY_QUEUE_SIZE - gKeyboardEventQueueReadIndex - 1; + } else { + end = gKeyboardEventQueueWriteIndex - gKeyboardEventQueueReadIndex - 1; + } + + if (index <= end) { + int eventIndex = (gKeyboardEventQueueReadIndex + index) & (KEY_QUEUE_SIZE - 1); + *keyboardEventPtr = &(gKeyboardEventsQueue[eventIndex]); + rc = 0; + } + } + + return rc; +} + +// 0x4D2680 +bool _vcr_record(const char* fileName) +{ + if (_vcr_state != 2) { + return false; + } + + if (fileName == NULL) { + return false; + } + + if (_vcr_buffer != NULL) { + return false; + } + + _vcr_buffer = internal_malloc(sizeof(*_vcr_buffer) * 4096); + if (_vcr_buffer == NULL) { + return false; + } + + _vcr_clear_buffer(); + + _vcr_file = fileOpen(fileName, "wb"); + if (_vcr_file == NULL) { + if (_vcr_buffer != NULL) { + _vcr_clear_buffer(); + internal_free(_vcr_buffer); + _vcr_buffer = NULL; + } + return false; + } + + if (_vcr_registered_atexit == 0) { + _vcr_registered_atexit = atexit(_vcr_stop); + } + + STRUCT_51E2F0* entry = &(_vcr_buffer[_vcr_buffer_index]); + entry->type = 1; + entry->field_4 = 0; + entry->field_8 = 0; + entry->type_1_field_14 = keyboardGetLayout(); + + while (mouseGetEvent() != 0) { + _mouse_info(); + } + + mouseGetPosition(&(entry->type_1_field_C), &(entry->type_1_field_10)); + + _vcr_counter = 1; + _vcr_buffer_index++; + _vcr_start_time = _get_time(); + keyboardReset(); + _vcr_state = 0; + + return true; +} + +// 0x4D28F4 +int _vcr_stop(void) +{ + if (_vcr_state == 0 || _vcr_state == 1) { + _vcr_state |= 0x80000000; + } + + keyboardReset(); + + return 1; +} + +// 0x4D2918 +int _vcr_status() +{ + return _vcr_state; +} + +// 0x4D2930 +int _vcr_update() +{ + // TODO: Incomplete. + return 0; +} + +// 0x4D2CD0 +bool _vcr_clear_buffer() +{ + if (_vcr_buffer == NULL) { + return false; + } + + _vcr_buffer_index = 0; + + return true; +} + +// 0x4D2CF0 +int _vcr_dump_buffer() +{ + if (!_vcr_buffer || !_vcr_file) { + return 0; + } + + for (int index = 0; index < _vcr_buffer_index; index++) { + if (_vcr_save_record(&(_vcr_buffer[index]), _vcr_file)) { + _vcr_buffer_index = 0; + return 1; + } + } + + return 0; +} + +// 0x4D2E00 +bool _vcr_save_record(STRUCT_51E2F0* ptr, File* stream) +{ + if (_db_fwriteLong(stream, ptr->type) == -1) goto err; + if (_db_fwriteLong(stream, ptr->field_4) == -1) goto err; + if (_db_fwriteLong(stream, ptr->field_8) == -1) goto err; + + switch (ptr->type) { + case 1: + if (_db_fwriteLong(stream, ptr->type_1_field_C) == -1) goto err; + if (_db_fwriteLong(stream, ptr->type_1_field_10) == -1) goto err; + if (_db_fwriteLong(stream, ptr->type_1_field_14) == -1) goto err; + + return true; + case 2: + if (fileWriteInt16(stream, ptr->type_2_field_C) == -1) goto err; + + return true; + case 3: + if (_db_fwriteLong(stream, ptr->dx) == -1) goto err; + if (_db_fwriteLong(stream, ptr->dy) == -1) goto err; + if (_db_fwriteLong(stream, ptr->buttons) == -1) goto err; + + return true; + } + +err: + + return false; +} + +// 0x4D2EE4 +bool _vcr_load_record(STRUCT_51E2F0* ptr, File* stream) +{ + if (_db_freadInt(stream, &(ptr->type)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->field_4)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->field_8)) == -1) goto err; + + switch (ptr->type) { + case 1: + if (_db_freadInt(stream, &(ptr->type_1_field_C)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->type_1_field_10)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->type_1_field_14)) == -1) goto err; + + return true; + case 2: + if (fileReadInt16(stream, &(ptr->type_2_field_C)) == -1) goto err; + + return true; + case 3: + if (_db_freadInt(stream, &(ptr->dx)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->dy)) == -1) goto err; + if (_db_freadInt(stream, &(ptr->buttons)) == -1) goto err; + + return true; + } + +err: + + return false; +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..1089f9d --- /dev/null +++ b/src/core.h @@ -0,0 +1,637 @@ +#ifndef CORE_H +#define CORE_H + +#include "db.h" +#include "dinput.h" +#include "geometry.h" +#include "window.h" + +#include + +#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 + +#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 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; + +typedef enum KeyboardLayout { + KEYBOARD_LAYOUT_QWERTY, + KEYBOARD_LAYOUT_FRENCH, + KEYBOARD_LAYOUT_GERMAN, + KEYBOARD_LAYOUT_ITALIAN, + KEYBOARD_LAYOUT_SPANISH, +} KeyboardLayout; + +typedef struct STRUCT_6ABF50 { + // Time when appropriate key was pressed down or -1 if it's up. + int tick; + int repeatCount; +} STRUCT_6ABF50; + +typedef struct InputEvent { + // 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 logicalKey; + int mouseX; + int mouseY; +} InputEvent; + +typedef void TickerProc(); + +typedef struct TickerListNode { + int flags; + TickerProc* proc; + struct TickerListNode* next; +} TickerListNode; + +typedef struct STRUCT_51E2F0 { + int type; + int field_4; + int field_8; + union { + struct { + int type_1_field_C; // mouse x + int type_1_field_10; // mouse y + int type_1_field_14; // keyboard layout + }; + struct { + short type_2_field_C; + }; + struct { + int dx; + int dy; + int buttons; + }; + }; +} STRUCT_51E2F0; + +static_assert(sizeof(STRUCT_51E2F0) == 24, "wrong size"); + +typedef struct LogicalKeyEntry { + short field_0; + short unmodified; + short shift; + short lmenu; + short rmenu; + short ctrl; +} LogicalKeyEntry; + +typedef struct KeyboardEvent { + unsigned char scanCode; + unsigned short modifiers; +} KeyboardEvent; + +typedef int(PauseHandler)(); +typedef int(ScreenshotHandler)(int width, int height, unsigned char* buffer, unsigned char* palette); + +extern void (*_idle_func)(); +extern void (*_focus_func)(int); +extern int gKeyboardKeyRepeatRate; +extern int gKeyboardKeyRepeatDelay; +extern bool _keyboard_hooked; +extern unsigned char gMouseDefaultCursor[MOUSE_DEFAULT_CURSOR_SIZE]; +extern int _mouse_idling; +extern unsigned char* gMouseCursorData; +extern unsigned char* _mouse_shape; +extern unsigned char* _mouse_fptr; +extern double gMouseSensitivity; +extern unsigned int _ticker_; +extern int gMouseButtonsState; + +extern LPDIRECTDRAW gDirectDraw; +extern LPDIRECTDRAWSURFACE gDirectDrawSurface1; +extern LPDIRECTDRAWSURFACE gDirectDrawSurface2; +extern LPDIRECTDRAWPALETTE gDirectDrawPalette; +extern void (*_update_palette_func)(); +extern bool gMmxEnabled; +extern bool gMmxProbed; + +extern unsigned char _kb_installed; +extern bool gKeyboardDisabled; +extern bool gKeyboardNumpadDisabled; +extern bool gKeyboardNumlockDisabled; +extern int gKeyboardEventQueueWriteIndex; +extern int gKeyboardEventQueueReadIndex; +extern short word_51E2E8; +extern int gModifierKeysState; +extern int (*_kb_scan_to_ascii)(); +extern STRUCT_51E2F0* _vcr_buffer; +extern int _vcr_buffer_index; +extern int _vcr_state; +extern int _vcr_time; +extern int _vcr_counter; +extern int _vcr_terminate_flags; +extern int _vcr_terminated_condition; +extern int _vcr_start_time; +extern int _vcr_registered_atexit; +extern File* _vcr_file; +extern int _vcr_buffer_end; + +extern unsigned char gNormalizedQwertyKeys[256]; +extern InputEvent gInputEventQueue[40]; +extern STRUCT_6ABF50 _GNW95_key_time_stamps[256]; +extern int _input_mx; +extern int _input_my; +extern HHOOK _GNW95_keyboardHandle; +extern bool gPaused; +extern int gScreenshotKeyCode; +extern int _using_msec_timer; +extern int gPauseKeyCode; +extern ScreenshotHandler* gScreenshotHandler; +extern int gInputEventQueueReadIndex; +extern unsigned char* gScreenshotBuffer; +extern PauseHandler* gPauseHandler; +extern int gInputEventQueueWriteIndex; +extern bool gRunLoopDisabled; +extern TickerListNode* gTickerListHead; +extern unsigned int gTickerLastTimestamp; +extern bool gCursorIsHidden; +extern int _raw_x; +extern int gMouseCursorHeight; +extern int _raw_y; +extern int _raw_buttons; +extern int gMouseCursorY; +extern int gMouseCursorX; +extern int _mouse_disabled; +extern int gMouseEvent; +extern unsigned int _mouse_speed; +extern int _mouse_curr_frame; +extern bool gMouseInitialized; +extern int gMouseCursorPitch; +extern int gMouseCursorWidth; +extern int _mouse_num_frames; +extern int _mouse_hoty; +extern int _mouse_hotx; +extern unsigned int _mouse_idle_start_time; +extern WindowDrawingProc2* _mouse_blit_trans; +extern WINDOWDRAWINGPROC _mouse_blit; +extern unsigned char _mouse_trans; +extern int gMouseRightButtonDownTimestamp; +extern int gMouseLeftButtonDownTimestamp; +extern int gMousePreviousEvent; +extern unsigned short gSixteenBppPalette[256]; +extern Rect _scr_size; +extern int gRedMask; +extern int gGreenMask; +extern int gBlueMask; +extern int gBlueShift; +extern int gRedShift; +extern int gGreenShift; +extern void (*_scr_blit)(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); +extern void (*_zero_mem)(); +extern bool gMmxSupported; +extern unsigned char gLastVideoModePalette[268]; +extern KeyboardEvent gKeyboardEventsQueue[KEY_QUEUE_SIZE]; +extern LogicalKeyEntry gLogicalKeyEntries[256]; +extern unsigned char gPressedPhysicalKeys[256]; +extern unsigned int _kb_idle_start_time; +extern KeyboardEvent gLastKeyboardEvent; +extern int gKeyboardLayout; +extern unsigned char gPressedPhysicalKeysCount; + +int coreInit(int a1); +void coreExit(); +int _get_input(); +void _process_bk(); +void enqueueInputEvent(int a1); +int dequeueInputEvent(); +void inputEventQueueReset(); +void tickersExecute(); +void tickersAdd(TickerProc* fn); +void tickersRemove(TickerProc* fn); +void tickersEnable(); +void tickersDisable(); +void pauseGame(); +int pauseHandlerDefaultImpl(); +void pauseHandlerConfigure(int keyCode, PauseHandler* fn); +void takeScreenshot(); +void screenshotBlitter(unsigned char* src, int src_pitch, int a3, int x, int y, int width, int height, int dest_x, int dest_y); +int screenshotHandlerDefaultImpl(int width, int height, unsigned char* data, unsigned char* palette); +void screenshotHandlerConfigure(int keyCode, ScreenshotHandler* handler); +unsigned int _get_time(); +void coreDelayProcessingEvents(unsigned int ms); +void coreDelay(unsigned int ms); +unsigned int getTicksSince(unsigned int a1); +unsigned int getTicksBetween(unsigned int a1, unsigned int a2); +unsigned int _get_bk_time(); +void buildNormalizedQwertyKeys(); +void _GNW95_hook_input(int a1); +int _GNW95_input_init(); +int _GNW95_hook_keyboard(int a1); +LRESULT CALLBACK _GNW95_keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam); +void _GNW95_process_message(); +void _GNW95_clear_time_stamps(); +void _GNW95_process_key(KeyboardData* data); +void _GNW95_lost_focus(); +int mouseInit(); +void mouseFree(); +void mousePrepareDefaultCursor(); +int mouseSetFrame(unsigned char* a1, int width, int height, int pitch, int a5, int a6, int a7); +void _mouse_anim(); +void mouseShowCursor(); +void mouseHideCursor(); +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 mouseGetRect(Rect* rect); +void mouseGetPosition(int* out_x, int* out_y); +void _mouse_set_position(int a1, int a2); +void _mouse_clip(); +int mouseGetEvent(); +bool cursorIsHidden(); +void _mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons); +void mouseSetSensitivity(double value); +void mmxSetEnabled(bool a1); +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(); +void _get_start_mode_(); +void _zero_vid_mem(); +int _GNW95_init_mode_ex(int width, int height, int bpp); +int _init_vesa_mode(int width, int height); +int _GNW95_init_window(); +int getShiftForBitMask(int mask); +int directDrawInit(int width, int height, int bpp); +void directDrawFree(); +void directDrawSetPaletteInRange(unsigned char* a1, int a2, int a3); +void directDrawSetPalette(unsigned char* palette); +unsigned char* directDrawGetPalette(); +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(); +int keyboardInit(); +void keyboardFree(); +void keyboardReset(); +int _kb_getch(); +void keyboardDisable(); +void keyboardEnable(); +int keyboardIsDisabled(); +void keyboardSetLayout(int new_language); +int keyboardGetLayout(); +void _kb_simulate_key(int a1); +int _kb_next_ascii_English_US(); +int keyboardDequeueLogicalKeyCode(); +void keyboardBuildQwertyConfiguration(); +void keyboardBuildFrenchConfiguration(); +void keyboardBuildGermanConfiguration(); +void keyboardBuildItalianConfiguration(); +void keyboardBuildSpanishConfiguration(); +void _kb_init_lock_status(); +int keyboardPeekEvent(int index, KeyboardEvent** keyboardEventPtr); +bool _vcr_record(const char* fileName); +int _vcr_stop(void); +int _vcr_status(); +int _vcr_update(); +bool _vcr_clear_buffer(); +int _vcr_dump_buffer(); +bool _vcr_save_record(STRUCT_51E2F0* ptr, File* stream); +bool _vcr_load_record(STRUCT_51E2F0* ptr, File* stream); + +#endif /* CORE_H */ diff --git a/src/credits.c b/src/credits.c new file mode 100644 index 0000000..ae95166 --- /dev/null +++ b/src/credits.c @@ -0,0 +1,266 @@ +#include "credits.h" + +#include "art.h" +#include "color.h" +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "draw.h" +#include "game_mouse.h" +#include "memory.h" +#include "message.h" +#include "palette.h" +#include "sound.h" +#include "text_font.h" +#include "window_manager.h" + +#include + +// 0x56D740 +File* gCreditsFile; + +// 0x56D744 +int gCreditsWindowNameColor; + +// 0x56D748 +int gCreditsWindowTitleFont; + +// 0x56D74C +int gCreditsWindowNameFont; + +// 0x56D750 +int gCreditsWindowTitleColor; + +// 0x42C860 +void creditsOpen(const char* filePath, int backgroundFid, bool useReversedStyle) +{ + int oldFont = fontGetCurrent(); + + colorPaletteLoad("color.pal"); + + if (useReversedStyle) { + gCreditsWindowTitleColor = _colorTable[18917]; + gCreditsWindowNameFont = 103; + gCreditsWindowTitleFont = 104; + gCreditsWindowNameColor = _colorTable[13673]; + } else { + gCreditsWindowTitleColor = _colorTable[13673]; + gCreditsWindowNameFont = 104; + gCreditsWindowTitleFont = 103; + gCreditsWindowNameColor = _colorTable[18917]; + } + + soundContinueAll(); + + char localizedPath[MAX_PATH]; + if (_message_make_path(localizedPath, filePath)) { + gCreditsFile = fileOpen(localizedPath, "rt"); + if (gCreditsFile != NULL) { + soundContinueAll(); + + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + bool cursorWasHidden = cursorIsHidden(); + if (cursorWasHidden) { + mouseShowCursor(); + } + + int window = windowCreate(0, 0, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, _colorTable[0], 20); + soundContinueAll(); + if (window != -1) { + unsigned char* windowBuffer = windowGetBuffer(window); + if (windowBuffer != NULL) { + unsigned char* backgroundBuffer = internal_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 = artLock(backgroundFid, &backgroundFrmHandle); + if (frm != NULL) { + int width = artGetWidth(frm, 0, 0); + int height = artGetHeight(frm, 0, 0); + unsigned char* backgroundFrmData = artGetFrameData(frm, 0, 0); + blitBufferToBuffer(backgroundFrmData, + width, + height, + width, + backgroundBuffer + CREDITS_WINDOW_WIDTH * ((CREDITS_WINDOW_HEIGHT - height) / 2) + (CREDITS_WINDOW_WIDTH - width) / 2, + CREDITS_WINDOW_WIDTH); + artUnlock(backgroundFrmHandle); + } + } + + unsigned char* intermediateBuffer = internal_malloc(CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); + if (intermediateBuffer != NULL) { + memset(intermediateBuffer, 0, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); + + fontSetCurrent(gCreditsWindowTitleFont); + int titleFontLineHeight = fontGetLineHeight(); + + fontSetCurrent(gCreditsWindowNameFont); + int nameFontLineHeight = fontGetLineHeight(); + + int lineHeight = nameFontLineHeight + (titleFontLineHeight >= nameFontLineHeight ? titleFontLineHeight - nameFontLineHeight : 0); + int stringBufferSize = CREDITS_WINDOW_WIDTH * lineHeight; + unsigned char* stringBuffer = internal_malloc(stringBufferSize); + if (stringBuffer != NULL) { + blitBufferToBuffer(backgroundBuffer, + CREDITS_WINDOW_WIDTH, + CREDITS_WINDOW_HEIGHT, + CREDITS_WINDOW_WIDTH, + windowBuffer, + CREDITS_WINDOW_WIDTH); + + windowRefresh(window); + + paletteFadeTo(_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 (creditsFileParseNextLine(str, &font, &color)) { + fontSetCurrent(font); + + int v19 = fontGetStringWidth(str); + if (v19 >= CREDITS_WINDOW_WIDTH) { + continue; + } + + memset(stringBuffer, 0, stringBufferSize); + fontDrawText(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); + + blitBufferToBuffer(backgroundBuffer, + CREDITS_WINDOW_WIDTH, + CREDITS_WINDOW_HEIGHT, + CREDITS_WINDOW_WIDTH, + windowBuffer, + CREDITS_WINDOW_WIDTH); + + blitBufferToBufferTrans(intermediateBuffer, + CREDITS_WINDOW_WIDTH, + CREDITS_WINDOW_HEIGHT, + CREDITS_WINDOW_WIDTH, + windowBuffer, + CREDITS_WINDOW_WIDTH); + + while (getTicksSince(tick) < CREDITS_WINDOW_SCROLLING_DELAY) { + } + + tick = _get_time(); + + windowRefresh(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); + + blitBufferToBuffer(backgroundBuffer, + CREDITS_WINDOW_WIDTH, + CREDITS_WINDOW_HEIGHT, + CREDITS_WINDOW_WIDTH, + windowBuffer, + CREDITS_WINDOW_WIDTH); + + blitBufferToBufferTrans(intermediateBuffer, + CREDITS_WINDOW_WIDTH, + CREDITS_WINDOW_HEIGHT, + CREDITS_WINDOW_WIDTH, + windowBuffer, + CREDITS_WINDOW_WIDTH); + + while (getTicksSince(tick) < CREDITS_WINDOW_SCROLLING_DELAY) { + } + + tick = _get_time(); + + windowRefresh(window); + } + } + + internal_free(stringBuffer); + } + internal_free(intermediateBuffer); + } + internal_free(backgroundBuffer); + } + } + + soundContinueAll(); + paletteFadeTo(gPaletteBlack); + soundContinueAll(); + windowDestroy(window); + } + + if (cursorWasHidden) { + mouseHideCursor(); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + colorCycleEnable(); + fileClose(gCreditsFile); + } + } + + fontSetCurrent(oldFont); +} + +// 0x42CE6C +bool creditsFileParseNextLine(char* dest, int* font, int* color) +{ + char string[256]; + while (fileReadString(string, 256, gCreditsFile)) { + char* pch; + if (string[0] == ';') { + continue; + } else if (string[0] == '@') { + *font = gCreditsWindowTitleFont; + *color = gCreditsWindowTitleColor; + pch = string + 1; + } else if (string[0] == '#') { + *font = gCreditsWindowNameFont; + *color = _colorTable[17969]; + pch = string + 1; + } else { + *font = gCreditsWindowNameFont; + *color = gCreditsWindowNameColor; + pch = string; + } + + strcpy(dest, pch); + + return true; + } + + return false; +} diff --git a/src/credits.h b/src/credits.h new file mode 100644 index 0000000..2eace56 --- /dev/null +++ b/src/credits.h @@ -0,0 +1,21 @@ +#ifndef CREDITS_H +#define CREDITS_H + +#include "db.h" + +#include + +#define CREDITS_WINDOW_WIDTH (640) +#define CREDITS_WINDOW_HEIGHT (480) +#define CREDITS_WINDOW_SCROLLING_DELAY (38) + +extern File* gCreditsFile; +extern int gCreditsWindowNameColor; +extern int gCreditsWindowTitleFont; +extern int gCreditsWindowNameFont; +extern int gCreditsWindowTitleColor; + +void creditsOpen(const char* path, int fid, bool useReversedStyle); +bool creditsFileParseNextLine(char* dest, int* font, int* color); + +#endif /* CREDITS_H */ diff --git a/src/critter.c b/src/critter.c new file mode 100644 index 0000000..7b76fc8 --- /dev/null +++ b/src/critter.c @@ -0,0 +1,1326 @@ +#include "critter.h" + +#include "animation.h" +#include "character_editor.h" +#include "combat.h" +#include "debug.h" +#include "display_monitor.h" +#include "endgame.h" +#include "game.h" +#include "geometry.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "party_member.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "reaction.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "tile.h" +#include "trait.h" +#include "world_map.h" + +#include +#include + +// 0x50141C +char _aCorpse[] = "corpse"; + +// 0x501494 +char byte_501494[] = ""; + +// 0x51833C +char* _name_critter = _aCorpse; + +// Modifiers to endurance for performing radiation damage check. +// +// 0x518340 +const int gRadiationEnduranceModifiers[RADIATION_LEVEL_COUNT] = { + 2, + 0, + -2, + -4, + -6, + -8, +}; + +// 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 [gRadiationEffectPenalties] 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 +const int gRadiationEffectStats[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 [gRadiationEffectStats] 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 +const int gRadiationEffectPenalties[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 +Object* _critterClearObj = NULL; + +// scrname.msg +// +// 0x56D754 +MessageList gCritterMessageList; + +// 0x56D75C +char gDudeName[DUDE_NAME_MAX_LENGTH]; + +// 0x56D77C +int _sneak_working; + +// 0x56D780 +int gKillsByType[KILL_TYPE_COUNT]; + +// Something with radiation. +// +// 0x56D7CC +int _old_rad_level; + +// scrname_init +// 0x42CF50 +int critterInit() +{ + dudeResetName(); + + memset(gKillsByType, 0, sizeof(gKillsByType)); + + if (!messageListInit(&gCritterMessageList)) { + debugPrint("\nError: Initing critter name message file!"); + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%sscrname.msg", asc_5186C8); + + if (!messageListLoad(&gCritterMessageList, path)) { + debugPrint("\nError: Loading critter name message file!"); + return -1; + } + + return 0; +} + +// 0x42CFE4 +void critterReset() +{ + dudeResetName(); + memset(gKillsByType, 0, sizeof(gKillsByType)); +} + +// 0x42D004 +void critterExit() +{ + messageListFree(&gCritterMessageList); +} + +// 0x42D01C +int critterLoad(File* stream) +{ + if (fileReadInt32(stream, &_sneak_working) == -1) { + return -1; + } + + Proto* proto; + protoGetProto(gDude->pid, &proto); + + return protoCritterDataRead(stream, &(proto->critter.data)); +} + +// 0x42D058 +int critterSave(File* stream) +{ + if (fileWriteInt32(stream, _sneak_working) == -1) { + return -1; + } + + Proto* proto; + protoGetProto(gDude->pid, &proto); + + return protoCritterDataWrite(stream, &(proto->critter.data)); +} + +// 0x42D094 +void critterProtoDataCopy(CritterProtoData* dest, CritterProtoData* src) +{ + memcpy(dest, src, sizeof(CritterProtoData)); +} + +// 0x42D0A8 +char* critterGetName(Object* obj) +{ + if (obj == gDude) { + return gDudeName; + } + + if (obj->field_80 == -1) { + if (obj->sid != -1) { + Script* script; + if (scriptGetScript(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 (messageListGetItem(&gCritterMessageList, &messageListItem)) { + name = messageListItem.text; + } + } + + if (name == NULL || *name == '\0') { + name = protoGetName(obj->pid); + } + + _name_critter = name; + + return name; +} + +// 0x42D138 +int dudeSetName(const char* name) +{ + if (strlen(name) <= DUDE_NAME_MAX_LENGTH) { + strncpy(gDudeName, name, DUDE_NAME_MAX_LENGTH); + return 0; + } + + return -1; +} + +// 0x42D170 +void dudeResetName() +{ + strncpy(gDudeName, "None", DUDE_NAME_MAX_LENGTH); +} + +// 0x42D18C +int critterGetHitPoints(Object* critter) +{ + return (critter->pid >> 24) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0; +} + +// 0x42D1A4 +int critterAdjustHitPoints(Object* critter, int a2) +{ + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return 0; + } + + int maximumHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS); + int newHp = critter->data.critter.hp + a2; + + critter->data.critter.hp = newHp; + if (maximumHp >= newHp) { + if (newHp <= 0 && (critter->data.critter.combat.results & DAM_DEAD) == 0) { + critterKill(critter, -1, true); + } + } else { + critter->data.critter.hp = maximumHp; + } + + return 0; +} + +// 0x42D1F8 +int critterGetPoison(Object* critter) +{ + return (critter->pid >> 24) == 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 critterAdjustPoison(Object* critter, int amount) +{ + MessageListItem messageListItem; + + if (critter != gDude) { + return -1; + } + + if (amount > 0) { + // Take poison resistance into account. + amount -= amount * critterGetStat(critter, STAT_POISON_RESISTANCE) / 100; + } else { + if (gDude->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); + queueAddEvent(10 * (505 - 5 * newPoison), gDude, 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 (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + if (critter == gDude) { + indicatorBarRefresh(); + } + + return 0; +} + +// 0x42D318 +int poisonEventProcess(Object* obj, void* data) +{ + if (obj != gDude) { + return 0; + } + + critterAdjustPoison(obj, -2); + critterAdjustHitPoints(obj, -1); + + interfaceRenderHitPoints(false); + + MessageListItem messageListItem; + // You take damage from poison. + messageListItem.num = 3001; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + // NOTE: Uninline. + int hitPoints = critterGetHitPoints(obj); + if (hitPoints > 5) { + return 0; + } + + return 1; +} + +// 0x42D38C +int critterGetRadiation(Object* obj) +{ + return (obj->pid >> 24) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0; +} + +// 0x42D3A4 +int critterAdjustRadiation(Object* obj, int amount) +{ + MessageListItem messageListItem; + + if (obj != gDude) { + return -1; + } + + Proto* proto; + protoGetProto(gDude->pid, &proto); + + if (amount > 0) { + amount -= critterGetStat(obj, STAT_RADIATION_RESISTANCE) * amount / 100; + } + + if (amount > 0) { + proto->critter.data.flags |= 0x02; + } + + if (amount > 0) { + Object* geigerCounter = NULL; + + Object* item1 = critterGetItem1(gDude); + if (item1 != NULL) { + if (item1->pid == PROTO_ID_GEIGER_COUNTER_I || item1->pid == PROTO_ID_GEIGER_COUNTER_II) { + geigerCounter = item1; + } + } + + Object* item2 = critterGetItem2(gDude); + if (item2 != NULL) { + if (item2->pid == PROTO_ID_GEIGER_COUNTER_I || item2->pid == PROTO_ID_GEIGER_COUNTER_II) { + geigerCounter = item2; + } + } + + if (geigerCounter != NULL) { + if (miscItemIsOn(geigerCounter)) { + if (amount > 5) { + // The geiger counter is clicking wildly. + messageListItem.num = 1009; + } else { + // The geiger counter is clicking. + messageListItem.num = 1008; + } + + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + + if (amount >= 10) { + // You have received a large dose of radiation. + messageListItem.num = 1007; + + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + + obj->data.critter.radiation += amount; + if (obj->data.critter.radiation < 0) { + obj->data.critter.radiation = 0; + } + + if (obj == gDude) { + indicatorBarRefresh(); + } + + return 0; +} + +// 0x42D4F4 +int _critter_check_rads(Object* obj) +{ + if (obj != gDude) { + return 0; + } + + Proto* proto; + protoGetProto(obj->pid, &proto); + if ((proto->critter.data.flags & 0x02) == 0) { + return 0; + } + + _old_rad_level = 0; + + _queue_clear_type(EVENT_TYPE_RADIATION, _get_rad_damage_level); + + // NOTE: Uninline + int radiation = critterGetRadiation(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 (statRoll(obj, STAT_ENDURANCE, gRadiationEnduranceModifiers[radiationLevel], NULL) <= ROLL_FAILURE) { + radiationLevel++; + } + + if (radiationLevel > _old_rad_level) { + // Create timer event for applying radiation damage. + RadiationEvent* radiationEvent = internal_malloc(sizeof(*radiationEvent)); + if (radiationEvent == NULL) { + return 0; + } + + radiationEvent->radiationLevel = radiationLevel; + radiationEvent->isHealing = 0; + queueAddEvent(36000 * randomBetween(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION); + } + + proto->critter.data.flags &= ~(0x02); + + return 0; +} + +// 0x42D618 +int _get_rad_damage_level(Object* obj, void* data) +{ + RadiationEvent* radiationEvent = data; + + _old_rad_level = radiationEvent->radiationLevel; + + return 0; +} + +// 0x42D624 +int _clear_rad_damage(Object* obj, void* data) +{ + RadiationEvent* radiationEvent = data; + + if (radiationEvent->isHealing) { + _process_rads(obj, radiationEvent->radiationLevel, true); + } + + return 1; +} + +// Applies radiation. +// +// 0x42D63C +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 == gDude) { + // Radiation level message, higher is worse. + messageListItem.num = 1000 + radiationLevelIndex; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + + for (int effect = 0; effect < RADIATION_EFFECT_COUNT; effect++) { + int value = critterGetBonusStat(obj, gRadiationEffectStats[effect]); + value += modifier * gRadiationEffectPenalties[radiationLevelIndex][effect]; + critterSetBonusStat(obj, gRadiationEffectStats[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 = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]); + int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]); + if (base + bonus < PRIMARY_STAT_MIN) { + critterKill(obj, -1, 1); + break; + } + } + } + + if ((obj->data.critter.combat.results & DAM_DEAD) != 0) { + if (obj == gDude) { + // You have died from radiation sickness. + messageListItem.num = 1006; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } +} + +// 0x42D740 +int radiationEventProcess(Object* obj, void* data) +{ + RadiationEvent* radiationEvent = data; + if (!radiationEvent->isHealing) { + // Schedule healing stats event in 7 days. + RadiationEvent* newRadiationEvent = internal_malloc(sizeof(*newRadiationEvent)); + if (newRadiationEvent != NULL) { + _queue_clear_type(EVENT_TYPE_RADIATION, _clear_rad_damage); + newRadiationEvent->radiationLevel = radiationEvent->radiationLevel; + newRadiationEvent->isHealing = 1; + queueAddEvent(GAME_TIME_TICKS_PER_DAY * 7, obj, newRadiationEvent, EVENT_TYPE_RADIATION); + } + } + + _process_rads(obj, radiationEvent->radiationLevel, radiationEvent->isHealing); + + return 1; +} + +// 0x42D7A0 +int radiationEventRead(File* stream, void** dataPtr) +{ + RadiationEvent* radiationEvent = internal_malloc(sizeof(*radiationEvent)); + if (radiationEvent == NULL) { + return -1; + } + + if (fileReadInt32(stream, &(radiationEvent->radiationLevel)) == -1) goto err; + if (fileReadInt32(stream, &(radiationEvent->isHealing)) == -1) goto err; + + *dataPtr = radiationEvent; + return 0; + +err: + + internal_free(radiationEvent); + return -1; +} + +// 0x42D7FC +int radiationEventWrite(File* stream, void* data) +{ + RadiationEvent* radiationEvent = data; + + if (fileWriteInt32(stream, radiationEvent->radiationLevel) == -1) return -1; + if (fileWriteInt32(stream, radiationEvent->isHealing) == -1) return -1; + + return 0; +} + +// 0x42D82C +int critterGetDamageType(Object* obj) +{ + if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) { + return 0; + } + + Proto* proto; + if (protoGetProto(obj->pid, &proto) == -1) { + return 0; + } + + return proto->critter.data.damageType; +} + +// 0x42D878 +int killsIncByType(int killType) +{ + if (killType != -1 && killType < KILL_TYPE_COUNT) { + gKillsByType[killType]++; + return 0; + } + + return -1; +} + +// 0x42D8A8 +int killsGetByType(int killType) +{ + if (killType != -1 && killType < KILL_TYPE_COUNT) { + return gKillsByType[killType]; + } + + return 0; +} + +// 0x42D8C0 +int killsLoad(File* stream) +{ + if (fileReadInt32List(stream, gKillsByType, KILL_TYPE_COUNT) == -1) { + fileClose(stream); + return -1; + } + + return 0; +} + +// 0x42D8F0 +int killsSave(File* stream) +{ + if (fileWriteInt32List(stream, gKillsByType, KILL_TYPE_COUNT) == -1) { + fileClose(stream); + return -1; + } + + return 0; +} + +// 0x42D920 +int critterGetKillType(Object* obj) +{ + if (obj == gDude) { + int gender = critterGetStat(obj, STAT_GENDER); + if (gender == GENDER_FEMALE) { + return KILL_TYPE_WOMAN; + } + return KILL_TYPE_MAN; + } + + if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + Proto* proto; + protoGetProto(obj->pid, &proto); + + return proto->critter.data.killType; +} + +// 0x42D974 +char* killTypeGetName(int killType) +{ + if (killType != -1 && killType < KILL_TYPE_COUNT) { + if (killType >= 0 && killType < KILL_TYPE_COUNT) { + MessageListItem messageListItem; + return getmsg(&gProtoMessageList, &messageListItem, 1450 + killType); + } else { + return NULL; + } + } else { + return byte_501494; + } +} + +// 0x42D9B4 +char* killTypeGetDescription(int killType) +{ + if (killType != -1 && killType < KILL_TYPE_COUNT) { + if (killType >= 0 && killType < KILL_TYPE_COUNT) { + MessageListItem messageListItem; + return getmsg(&gProtoMessageList, &messageListItem, 1469 + killType); + } else { + return NULL; + } + } else { + return byte_501494; + } +} + +// 0x42D9F4 +int _critter_heal_hours(Object* critter, int a2) +{ + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + if (critter->data.critter.hp < critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS)) { + critterAdjustHitPoints(critter, 14 * (a2 / 3)); + } + + critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; + + return 0; +} + +// 0x42DA54 +int _critterClearObjDrugs(Object* obj, void* data) +{ + return obj == _critterClearObj; +} + +// 0x42DA64 +void critterKill(Object* critter, int anim, bool a3) +{ + if ((critter->pid >> 24) != 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 = (critter->fid & 0xFF0000) >> 16; + if (current == ANIM_FALL_BACK || current == ANIM_FALL_FRONT) { + bool back = false; + if (current == ANIM_FALL_BACK) { + back = true; + } else { + fid = buildFid(1, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + if (!artExists(fid)) { + back = true; + } + } + + if (back) { + fid = buildFid(1, 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) { + debugPrint("\nError: Critter Kill: death_frame out of range!"); + anim = LAST_SF_DEATH_ANIM; + } + + fid = buildFid(1, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + _obj_fix_violence_settings(&fid); + if (!artExists(fid)) { + debugPrint("\nError: Critter Kill: Can't match fid!"); + + fid = buildFid(1, 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) { + objectSetFrame(critter, 0, &updatedRect); + + objectSetFid(critter, fid, &tempRect); + rectUnion(&updatedRect, &tempRect, &updatedRect); + } + + if (!_critter_flag_check(critter->pid, 2048)) { + critter->flags |= OBJECT_NO_BLOCK; + _obj_toggle_flat(critter, &tempRect); + } + + // NOTE: using uninitialized updatedRect/tempRect if fid was not set. + + rectUnion(&updatedRect, &tempRect, &updatedRect); + + _obj_turn_off_light(critter, &tempRect); + rectUnion(&updatedRect, &tempRect, &updatedRect); + + critter->data.critter.hp = 0; + critter->data.critter.combat.results |= DAM_DEAD; + + if (critter->sid != -1) { + scriptRemove(critter->sid); + critter->sid = -1; + } + + _critterClearObj = critter; + _queue_clear_type(EVENT_TYPE_DRUG, _critterClearObjDrugs); + + _item_destroy_all_hidden(critter); + + if (a3) { + tileWindowRefreshRect(&updatedRect, elevation); + } + + if (critter == gDude) { + endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH); + _game_user_wants_to_quit = 2; + } +} + +// Returns experience for killing [critter]. +// +// 0x42DCB8 +int critterGetExp(Object* critter) +{ + Proto* proto; + protoGetProto(critter->pid, &proto); + return proto->critter.data.experience; +} + +// 0x42DCDC +bool critterIsActive(Object* critter) +{ + if (critter == NULL) { + return false; + } + + if ((critter->pid >> 24) != 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 critterIsDead(Object* critter) +{ + if (critter == NULL) { + return false; + } + + if ((critter->pid >> 24) != 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 critterIsCrippled(Object* critter) +{ + if (critter == NULL) { + return false; + } + + if ((critter->pid >> 24) != 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 ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + int anim = (critter->fid & 0xFF0000) >> 16; + + 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 critterGetBodyType(Object* critter) +{ + if (critter == NULL) { + debugPrint("\nError: critter_body_type: pobj was NULL!"); + return 0; + } + + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return 0; + } + + Proto* proto; + protoGetProto(critter->pid, &proto); + return proto->critter.data.bodyType; +} + +// 0x42DE58 +int gcdLoad(const char* path) +{ + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(gDude->pid, &proto); + + if (protoCritterDataRead(stream, &(proto->critter.data)) == -1) { + fileClose(stream); + return -1; + } + + fileRead(gDudeName, DUDE_NAME_MAX_LENGTH, 1, stream); + + if (skillsLoad(stream) == -1) { + fileClose(stream); + return -1; + } + + if (traitsLoad(stream) == -1) { + fileClose(stream); + return -1; + } + + if (fileReadInt32(stream, &characterEditorRemainingCharacterPoints) == -1) { + fileClose(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; + + fileClose(stream); + return 0; +} + +// 0x42DF70 +int protoCritterDataRead(File* stream, CritterProtoData* critterData) +{ + if (fileReadInt32(stream, &(critterData->flags)) == -1) return -1; + if (fileReadInt32List(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1; + if (fileReadInt32List(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1; + if (fileReadInt32List(stream, critterData->skills, SKILL_COUNT) == -1) return -1; + if (fileReadInt32(stream, &(critterData->bodyType)) == -1) return -1; + if (fileReadInt32(stream, &(critterData->experience)) == -1) return -1; + if (fileReadInt32(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 (fileReadInt32(stream, &(critterData->damageType)) == -1) { + critterData->damageType = DAMAGE_TYPE_NORMAL; + } + + return 0; +} + +// 0x42E08C +int gcdSave(const char* path) +{ + File* stream = fileOpen(path, "wb"); + if (stream == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(gDude->pid, &proto); + + if (protoCritterDataWrite(stream, &(proto->critter.data)) == -1) { + fileClose(stream); + return -1; + } + + fileWrite(gDudeName, DUDE_NAME_MAX_LENGTH, 1, stream); + + if (skillsSave(stream) == -1) { + fileClose(stream); + return -1; + } + + if (traitsSave(stream) == -1) { + fileClose(stream); + return -1; + } + + if (fileWriteInt32(stream, characterEditorRemainingCharacterPoints) == -1) { + fileClose(stream); + return -1; + } + + fileClose(stream); + return 0; +} + +// 0x42E174 +int protoCritterDataWrite(File* stream, CritterProtoData* critterData) +{ + if (fileWriteInt32(stream, critterData->flags) == -1) return -1; + if (fileWriteInt32List(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1; + if (fileWriteInt32List(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1; + if (fileWriteInt32List(stream, critterData->skills, SKILL_COUNT) == -1) return -1; + if (fileWriteInt32(stream, critterData->bodyType) == -1) return -1; + if (fileWriteInt32(stream, critterData->experience) == -1) return -1; + if (fileWriteInt32(stream, critterData->killType) == -1) return -1; + if (fileWriteInt32(stream, critterData->damageType) == -1) return -1; + + return 0; +} + +// 0x42E220 +void dudeDisableState(int state) +{ + Proto* proto; + protoGetProto(gDude->pid, &proto); + + proto->critter.data.flags &= ~(1 << state); + + if (state == DUDE_STATE_SNEAKING) { + queueRemoveEventsByType(gDude, EVENT_TYPE_SNEAK); + } + + indicatorBarRefresh(); +} + +// 0x42E26C +void dudeEnableState(int state) +{ + Proto* proto; + protoGetProto(gDude->pid, &proto); + + proto->critter.data.flags |= (1 << state); + + if (state == DUDE_STATE_SNEAKING) { + sneakEventProcess(NULL, NULL); + } + + indicatorBarRefresh(); +} + +// 0x42E2B0 +void dudeToggleState(int state) +{ + // NOTE: Uninline. + if (dudeHasState(state)) { + dudeDisableState(state); + } else { + dudeEnableState(state); + } +} + +// 0x42E2F8 +bool dudeHasState(int state) +{ + Proto* proto; + protoGetProto(gDude->pid, &proto); + return (proto->critter.data.flags & (1 << state)) != 0; +} + +// 0x42E32C +int sneakEventProcess(Object* obj, void* data) +{ + int time; + + int sneak = skillGetValue(gDude, SKILL_SNEAK); + if (skillRoll(gDude, 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; + } + + queueAddEvent(time, gDude, NULL, EVENT_TYPE_SNEAK); + + return 0; +} + +// 0x42E3E4 +int _critter_sneak_clear(Object* obj, void* data) +{ + dudeDisableState(DUDE_STATE_SNEAKING); + return 1; +} + +// Returns true if dude is really sneaking. +// +// 0x42E3F4 +bool dudeIsSneaking() +{ + // NOTE: Uninline. + if (dudeHasState(DUDE_STATE_SNEAKING)) { + return _sneak_working; + } + + return false; +} + +// 0x42E424 +int knockoutEventProcess(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 ((obj->pid >> 24) != 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 = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1); + objectSetFid(obj, fid, 0); + + return 0; +} + +// 0x42E4C0 +int _critter_set_who_hit_me(Object* a1, Object* a2) +{ + if (a1 == NULL) { + return -1; + } + + if (a2 != NULL && ((a2->fid & 0xF000000) >> 24) != OBJ_TYPE_CRITTER) { + return -1; + } + + if ((a1->pid >> 24) == OBJ_TYPE_CRITTER) { + if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || statRoll(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!objectIsPartyMember(a1) || !objectIsPartyMember(a2))) { + a1->data.critter.combat.whoHitMe = a2; + if (a2 == gDude) { + reactionSetValue(a1, -3); + } + } + } + + return 0; +} + +// 0x42E564 +bool _critter_can_obj_dude_rest() +{ + bool v1 = false; + if (!_wmMapCanRestHere(gElevation)) { + v1 = true; + } + + bool result = true; + + Object** critterList; + int critterListLength = objectListCreate(-1, gElevation, 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 == gDude) { + continue; + } + + if (critter->data.critter.combat.whoHitMe != gDude) { + if (!v1 || critter->data.critter.combat.team == gDude->data.critter.combat.team) { + continue; + } + } + + result = false; + break; + } + + if (critterListLength != 0) { + objectListFree(critterList); + } + + return result; +} + +// 0x42E62C +int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int actionPoints) +{ + if ((critter->pid >> 24) != 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_LEFT | DAM_CRIP_LEG_RIGHT)) != 0) { + return 4 * actionPoints; + } else { + return actionPoints; + } +} + +// 0x42E66C +bool critterIsEncumbered(Object* critter) +{ + int maxWeight = critterGetStat(critter, STAT_CARRY_WEIGHT); + int currentWeight = objectGetInventoryWeight(critter); + return maxWeight < currentWeight; +} + +// 0x42E690 +bool critterIsFleeing(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 >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + Proto* proto; + protoGetProto(pid, &proto); + return (proto->critter.data.flags & flag) != 0; +} diff --git a/src/critter.h b/src/critter.h new file mode 100644 index 0000000..5c84789 --- /dev/null +++ b/src/critter.h @@ -0,0 +1,128 @@ +#ifndef CRITTER_H +#define CRITTER_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "proto_types.h" + +#include + +// 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 [gRadiationEffectStats], and it's amount is specified +// in [gRadiationEffectPenalties] 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 char _aCorpse[]; +extern char byte_501494[]; + +extern char* _name_critter; +extern const int gRadiationEnduranceModifiers[RADIATION_LEVEL_COUNT]; +extern const int gRadiationEffectStats[RADIATION_EFFECT_COUNT]; +extern const int gRadiationEffectPenalties[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT]; +extern Object* _critterClearObj; + +extern MessageList gCritterMessageList; +extern char gDudeName[DUDE_NAME_MAX_LENGTH]; +extern int _sneak_working; +extern int gKillsByType[KILL_TYPE_COUNT]; +extern int _old_rad_level; + +int critterInit(); +void critterReset(); +void critterExit(); +int critterLoad(File* stream); +int critterSave(File* stream); +char* critterGetName(Object* obj); +void critterProtoDataCopy(CritterProtoData* dest, CritterProtoData* src); +int dudeSetName(const char* name); +void dudeResetName(); +int critterGetHitPoints(Object* critter); +int critterAdjustHitPoints(Object* critter, int amount); +int critterGetPoison(Object* critter); +int critterAdjustPoison(Object* obj, int amount); +int poisonEventProcess(Object* obj, void* data); +int critterGetRadiation(Object* critter); +int critterAdjustRadiation(Object* obj, int amount); +int _critter_check_rads(Object* critter); +int _get_rad_damage_level(Object* obj, void* data); +int _clear_rad_damage(Object* obj, void* data); +void _process_rads(Object* obj, int radiationLevel, bool direction); +int radiationEventProcess(Object* obj, void* data); +int radiationEventRead(File* stream, void** dataPtr); +int radiationEventWrite(File* stream, void* data); +int critterGetDamageType(Object* critter); +int killsIncByType(int killType); +int killsGetByType(int killType); +int killsLoad(File* stream); +int killsSave(File* stream); +int critterGetKillType(Object* critter); +char* killTypeGetName(int killType); +char* killTypeGetDescription(int killType); +int _critter_heal_hours(Object* obj, int a2); +int _critterClearObjDrugs(Object* obj, void* data); +void critterKill(Object* critter, int anim, bool a3); +int critterGetExp(Object* critter); +bool critterIsActive(Object* critter); +bool critterIsDead(Object* critter); +bool critterIsCrippled(Object* critter); +bool _critter_is_prone(Object* critter); +int critterGetBodyType(Object* critter); +int gcdLoad(const char* path); +int protoCritterDataRead(File* stream, CritterProtoData* critterData); +int gcdSave(const char* path); +int protoCritterDataWrite(File* stream, CritterProtoData* critterData); +void dudeDisableState(int state); +void dudeEnableState(int state); +void dudeToggleState(int state); +bool dudeHasState(int state); +int sneakEventProcess(Object* obj, void* data); +int _critter_sneak_clear(Object* obj, void* data); +bool dudeIsSneaking(); +int knockoutEventProcess(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 critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int a2); +bool critterIsEncumbered(Object* critter); +bool critterIsFleeing(Object* a1); +bool _critter_flag_check(int pid, int flag); + +#endif /* CRITTER_H */ diff --git a/src/cycle.c b/src/cycle.c new file mode 100644 index 0000000..ca3dfdf --- /dev/null +++ b/src/cycle.c @@ -0,0 +1,329 @@ +#include "cycle.h" + +#include "color.h" +#include "core.h" +#include "game_config.h" +#include "palette.h" + +// 0x51843C +int gColorCycleSpeedFactor = 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 +bool gColorCycleInitialized = false; + +// 0x518490 +bool gColorCycleEnabled = false; + +// 0x518494 +int _slime_start = 0; + +// 0x518498 +int _shoreline_start = 0; + +// 0x51849C +int _fire_slow_start = 0; + +// 0x5184A0 +int _fire_fast_start = 0; + +// 0x5184A4 +int _monitors_start = 0; + +// 0x5184A8 +unsigned char _bobber_red = 0; + +// 0x5184A9 +signed char _bobber_diff = -4; + +// 0x56D7D0 +unsigned int gColorCycleTimestamp3; + +// 0x56D7D4 +unsigned int gColorCycleTimestamp1; + +// 0x56D7D8 +unsigned int gColorCycleTimestamp2; + +// 0x56D7DC +unsigned int gColorCycleTimestamp4; + +// 0x42E780 +void colorCycleInit() +{ + if (gColorCycleInitialized) { + return; + } + + bool colorCycling; + if (!configGetBool(&gGameConfig, 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; + } + + tickersAdd(colorCycleTicker); + + gColorCycleInitialized = true; + gColorCycleEnabled = true; + + int cycleSpeedFactor; + if (!configGetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, &cycleSpeedFactor)) { + cycleSpeedFactor = 1; + } + + cycleSetSpeedFactor(cycleSpeedFactor); +} + +// 0x42E8CC +void colorCycleReset() +{ + if (gColorCycleInitialized) { + gColorCycleTimestamp1 = 0; + gColorCycleTimestamp2 = 0; + gColorCycleTimestamp3 = 0; + gColorCycleTimestamp4 = 0; + tickersAdd(colorCycleTicker); + gColorCycleEnabled = true; + } +} + +// 0x42E90C +void colorCycleFree() +{ + if (gColorCycleInitialized) { + tickersRemove(colorCycleTicker); + gColorCycleInitialized = false; + gColorCycleEnabled = false; + } +} + +// 0x42E930 +void colorCycleDisable() +{ + gColorCycleEnabled = false; +} + +// 0x42E93C +void colorCycleEnable() +{ + gColorCycleEnabled = true; +} + +// 0x42E948 +bool colorCycleEnabled() +{ + return gColorCycleEnabled; +} + +// 0x42E950 +void cycleSetSpeedFactor(int value) +{ + gColorCycleSpeedFactor = value; + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, value); +} + +// 0x42E97C +void colorCycleTicker() +{ + if (!gColorCycleEnabled) { + return; + } + + bool changed = false; + + unsigned char* palette = _getSystemPalette(); + unsigned int time = _get_time(); + + if (getTicksBetween(time, gColorCycleTimestamp1) >= COLOR_CYCLE_PERIOD_1 * gColorCycleSpeedFactor) { + changed = true; + gColorCycleTimestamp1 = 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 (getTicksBetween(time, gColorCycleTimestamp2) >= COLOR_CYCLE_PERIOD_2 * gColorCycleSpeedFactor) { + changed = true; + gColorCycleTimestamp2 = 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 (getTicksBetween(time, gColorCycleTimestamp3) >= COLOR_CYCLE_PERIOD_3 * gColorCycleSpeedFactor) { + changed = true; + gColorCycleTimestamp3 = 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 (getTicksBetween(time, gColorCycleTimestamp4) >= COLOR_CYCLE_PERIOD_4 * gColorCycleSpeedFactor) { + changed = true; + gColorCycleTimestamp4 = 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) { + paletteSetEntriesInRange(palette + 229 * 3, 229, 255); + } +} diff --git a/src/cycle.h b/src/cycle.h new file mode 100644 index 0000000..4c3e7c5 --- /dev/null +++ b/src/cycle.h @@ -0,0 +1,34 @@ +#ifndef CYCLE_H +#define CYCLE_H + +#include + +#define COLOR_CYCLE_PERIOD_1 (200U) +#define COLOR_CYCLE_PERIOD_2 (142U) +#define COLOR_CYCLE_PERIOD_3 (100U) +#define COLOR_CYCLE_PERIOD_4 (33U) + +extern int gColorCycleSpeedFactor; +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]; +extern bool gColorCycleEnabled; +extern bool gColorCycleInitialized; + +extern unsigned int gColorCycleTimestamp3; +extern unsigned int gColorCycleTimestamp1; +extern unsigned int gColorCycleTimestamp2; +extern unsigned int gColorCycleTimestamp4; + +void colorCycleInit(); +void colorCycleReset(); +void colorCycleFree(); +void colorCycleDisable(); +void colorCycleEnable(); +bool colorCycleEnabled(); +void cycleSetSpeedFactor(int value); +void colorCycleTicker(); + +#endif /* CYCLE_H */ diff --git a/src/datafile.c b/src/datafile.c new file mode 100644 index 0000000..c4ff41f --- /dev/null +++ b/src/datafile.c @@ -0,0 +1,10 @@ +#include "datafile.h" + +// 0x56D7E0 +unsigned char _pal[768]; + +// 0x42F0E4 +unsigned char* _datafileGetPalette() +{ + return _pal; +} diff --git a/src/datafile.h b/src/datafile.h new file mode 100644 index 0000000..ee7704b --- /dev/null +++ b/src/datafile.h @@ -0,0 +1,8 @@ +#ifndef DATAFILE_H +#define DATAFILE_H + +extern unsigned char _pal[768]; + +unsigned char* _datafileGetPalette(); + +#endif /* DATAFILE_H */ diff --git a/src/db.c b/src/db.c new file mode 100644 index 0000000..f66f0d1 --- /dev/null +++ b/src/db.c @@ -0,0 +1,734 @@ +#include "db.h" + +#include "xfile.h" + +#include +#include +#include + +// Generic file progress report handler. +// +// 0x51DEEC +FileReadProgressHandler* gFileReadProgressHandler = NULL; + +// Bytes read so far while tracking progress. +// +// Once this value reaches [gFileReadProgressChunkSize] the handler is called +// and this value resets to zero. +// +// 0x51DEF0 +int gFileReadProgressBytesRead = 0; + +// The number of bytes to read between calls to progress handler. +// +// 0x673040 +int gFileReadProgressChunkSize; + +// 0x673044 +FileList* gFileListHead; + +// 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 dbOpen(const char* filePath1, int a2, const char* filePath2, int a4) +{ + if (filePath1 != NULL) { + if (!xbaseOpen(filePath1)) { + return -1; + } + } + + if (filePath2 != NULL) { + xbaseOpen(filePath2); + } + + return 0; +} + +// NOTE: This function simply returns 0, but it definitely accept one parameter +// via eax, as seen at every call site. This value is ignored. It's impossible +// to guess it's name. +// +// 0x4C5D54 +int _db_current(int a1) +{ + return 0; +} + +// 0x4C5D58 +bool _db_total() +{ + return true; +} + +// 0x4C5D60 +void dbExit() +{ + xbaseReopenAll(NULL); +} + +// TODO: sizePtr should be long*. +// +// 0x4C5D68 +int dbGetFileSize(const char* filePath, int* sizePtr) +{ + assert(filePath); // "filename", "db.c", 108 + assert(sizePtr); // "de", "db.c", 109 + + File* stream = xfileOpen(filePath, "rb"); + if (stream == NULL) { + return -1; + } + + *sizePtr = xfileGetSize(stream); + + xfileClose(stream); + + return 0; +} + +// 0x4C5DD4 +int dbGetFileContents(const char* filePath, void* ptr) +{ + assert(filePath); // "filename", "db.c", 141 + assert(ptr); // "buf", "db.c", 142 + + File* stream = xfileOpen(filePath, "rb"); + if (stream == NULL) { + return -1; + } + + long size = xfileGetSize(stream); + if (gFileReadProgressHandler != NULL) { + unsigned char* byteBuffer = (unsigned char*)ptr; + + long remainingSize = size; + long chunkSize = gFileReadProgressChunkSize - gFileReadProgressBytesRead; + + while (remainingSize >= chunkSize) { + size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), chunkSize, stream); + byteBuffer += bytesRead; + remainingSize -= bytesRead; + + gFileReadProgressBytesRead = 0; + gFileReadProgressHandler(); + + chunkSize = gFileReadProgressChunkSize; + } + + if (remainingSize != 0) { + gFileReadProgressBytesRead += xfileRead(byteBuffer, sizeof(*byteBuffer), remainingSize, stream); + } + } else { + xfileRead(ptr, 1, size, stream); + } + + xfileClose(stream); + + return 0; +} + +// 0x4C5EB4 +int fileClose(File* stream) +{ + return xfileClose(stream); +} + +// 0x4C5EC8 +File* fileOpen(const char* filename, const char* mode) +{ + return xfileOpen(filename, mode); +} + +// 0x4C5ED0 +int filePrintFormatted(File* stream, const char* format, ...) +{ + assert(format); // "format", "db.c", 224 + + va_list args; + va_start(args, format); + + int rc = xfilePrintFormattedArgs(stream, format, args); + + va_end(args); + + return rc; +} + +// 0x4C5F24 +int fileReadChar(File* stream) +{ + if (gFileReadProgressHandler != NULL) { + int ch = xfileReadChar(stream); + + gFileReadProgressBytesRead++; + if (gFileReadProgressBytesRead >= gFileReadProgressChunkSize) { + gFileReadProgressHandler(); + gFileReadProgressBytesRead = 0; + } + + return ch; + } + + return xfileReadChar(stream); +} + +// 0x4C5F70 +char* fileReadString(char* string, size_t size, File* stream) +{ + if (gFileReadProgressHandler != NULL) { + if (xfileReadString(string, size, stream) == NULL) { + return NULL; + } + + gFileReadProgressBytesRead += strlen(string); + while (gFileReadProgressBytesRead >= gFileReadProgressChunkSize) { + gFileReadProgressHandler(); + gFileReadProgressBytesRead -= gFileReadProgressChunkSize; + } + + return string; + } + + return xfileReadString(string, size, stream); +} + +// 0x4C5FEC +int fileWriteString(const char* string, File* stream) +{ + return xfileWriteString(string, stream); +} + +// 0x4C5FFC +size_t fileRead(void* ptr, size_t size, size_t count, File* stream) +{ + if (gFileReadProgressHandler != NULL) { + unsigned char* byteBuffer = (unsigned char*)ptr; + + size_t totalBytesRead = 0; + long remainingSize = size * count; + long chunkSize = gFileReadProgressChunkSize - gFileReadProgressBytesRead; + + while (remainingSize >= chunkSize) { + size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), chunkSize, stream); + byteBuffer += bytesRead; + totalBytesRead += bytesRead; + remainingSize -= bytesRead; + + gFileReadProgressBytesRead = 0; + gFileReadProgressHandler(); + + chunkSize = gFileReadProgressChunkSize; + } + + if (remainingSize != 0) { + size_t bytesRead = xfileRead(byteBuffer, sizeof(*byteBuffer), remainingSize, stream); + gFileReadProgressBytesRead += bytesRead; + totalBytesRead += bytesRead; + } + + return totalBytesRead / size; + } + + return xfileRead(ptr, size, count, stream); +} + +// 0x4C60B8 +size_t fileWrite(const void* buf, size_t size, size_t count, File* stream) +{ + return xfileWrite(buf, size, count, stream); +} + +// 0x4C60C0 +int fileSeek(File* stream, long offset, int origin) +{ + return xfileSeek(stream, offset, origin); +} + +// 0x4C60C8 +long fileTell(File* stream) +{ + return xfileTell(stream); +} + +// 0x4C60D0 +void fileRewind(File* stream) +{ + xfileRewind(stream); +} + +// 0x4C60D8 +int fileEof(File* stream) +{ + return xfileEof(stream); +} + +// NOTE: Not sure about signness. +// +// 0x4C60E0 +int fileReadUInt8(File* stream, unsigned char* valuePtr) +{ + int value = fileReadChar(stream); + if (value == -1) { + return -1; + } + + *valuePtr = value & 0xFF; + + return 0; +} + +// NOTE: Not sure about signness. +// +// 0x4C60F4 +int fileReadInt16(File* stream, short* valuePtr) +{ + unsigned char high; + // NOTE: Uninline. + if (fileReadUInt8(stream, &high) == -1) { + return -1; + } + + unsigned char low; + // NOTE: Uninline. + if (fileReadUInt8(stream, &low) == -1) { + return -1; + } + + *valuePtr = (high << 8) | low; + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C60F4. There are only couple of places where +// the game reads/writes 16-bit integers. I'm not sure there are unsigned +// shorts used, but there are definitely signed (art offsets can be both +// positive and negative). Provided just in case. +int fileReadUInt16(File* stream, unsigned short* valuePtr) +{ + return fileReadInt16(stream, (short*)valuePtr); +} + +// 0x4C614C +int fileReadInt32(File* stream, int* valuePtr) +{ + int value; + + if (xfileRead(&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. The opposite of [_db_fwriteLong]. It can be either +// signed vs. unsigned variant, as well as int vs. long. It's provided here to +// identify places where data was written with [_db_fwriteLong]. +int _db_freadInt(File* stream, int* valuePtr) +{ + return fileReadInt32(stream, valuePtr); +} + +// NOTE: Probably uncollapsed 0x4C614C. +int fileReadUInt32(File* stream, unsigned int* valuePtr) +{ + return _db_freadInt(stream, (int*)valuePtr); +} + +// NOTE: Uncollapsed 0x4C614C. The opposite of [fileWriteFloat]. +int fileReadFloat(File* stream, float* valuePtr) +{ + return fileReadInt32(stream, (int*)valuePtr); +} + +int fileReadBool(File* stream, bool* valuePtr) +{ + int value; + if (fileReadInt32(stream, &value) == -1) { + return -1; + } + + *valuePtr = (value != 0); + + return 0; +} + +// NOTE: Not sure about signness. +// +// 0x4C61AC +int fileWriteUInt8(File* stream, unsigned char value) +{ + return xfileWriteChar(value, stream); +}; + +// 0x4C61C8 +int fileWriteInt16(File* stream, short value) +{ + // NOTE: Uninline. + if (fileWriteUInt8(stream, (value >> 8) & 0xFF) == -1) { + return -1; + } + + // NOTE: Uninline. + if (fileWriteUInt8(stream, value & 0xFF) == -1) { + return -1; + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C61C8. +int fileWriteUInt16(File* stream, unsigned short value) +{ + return fileWriteInt16(stream, (short)value); +} + +// NOTE: Not sure about signness and int vs. long. +// +// 0x4C6214 +int fileWriteInt32(File* stream, int value) +{ + // NOTE: Uninline. + return _db_fwriteLong(stream, value); +} + +// NOTE: Can either be signed vs. unsigned variant of [fileWriteInt32], +// or int vs. long. +// +// 0x4C6244 +int _db_fwriteLong(File* stream, int value) +{ + if (fileWriteInt16(stream, (value >> 16) & 0xFFFF) == -1) { + return -1; + } + + if (fileWriteInt16(stream, value & 0xFFFF) == -1) { + return -1; + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C6214 or 0x4C6244. +int fileWriteUInt32(File* stream, unsigned int value) +{ + return _db_fwriteLong(stream, (int)value); +} + +// 0x4C62C4 +int fileWriteFloat(File* stream, float value) +{ + // NOTE: Uninline. + return _db_fwriteLong(stream, *(int*)&value); +} + +int fileWriteBool(File* stream, bool value) +{ + return _db_fwriteLong(stream, value ? 1 : 0); +} + +// 0x4C62FC +int fileReadUInt8List(File* stream, unsigned char* arr, int count) +{ + for (int index = 0; index < count; index++) { + unsigned char ch; + // NOTE: Uninline. + if (fileReadUInt8(stream, &ch) == -1) { + return -1; + } + + arr[index] = ch; + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C62FC. There are couple of places where +// [fileReadUInt8List] is used to read strings of fixed length. I'm not +// pretty sure this function existed in the original code, but at least +// it increases visibility of these places. +int fileReadFixedLengthString(File* stream, char* string, int length) +{ + return fileReadUInt8List(stream, (unsigned char*)string, length); +} + +// 0x4C6330 +int fileReadInt16List(File* stream, short* arr, int count) +{ + for (int index = 0; index < count; index++) { + short value; + // NOTE: Uninline. + if (fileReadInt16(stream, &value) == -1) { + return -1; + } + + arr[index] = value; + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C6330. +int fileReadUInt16List(File* stream, unsigned short* arr, int count) +{ + return fileReadInt16List(stream, (short*)arr, count); +} + +// NOTE: Not sure about signed/unsigned int/long. +// +// 0x4C63BC +int fileReadInt32List(File* stream, int* arr, int count) +{ + if (count == 0) { + return 0; + } + + if (fileRead(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. The opposite of [_db_fwriteLongCount]. +int _db_freadIntCount(File* stream, int* arr, int count) +{ + return fileReadInt32List(stream, arr, count); +} + +// NOTE: Probably uncollapsed 0x4C63BC. +int fileReadUInt32List(File* stream, unsigned int* arr, int count) +{ + return fileReadInt32List(stream, (int*)arr, count); +} + +// 0x4C6464 +int fileWriteUInt8List(File* stream, unsigned char* arr, int count) +{ + for (int index = 0; index < count; index++) { + // NOTE: Uninline. + if (fileWriteUInt8(stream, arr[index]) == -1) { + return -1; + } + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C6464. See [fileReadFixedLengthString]. +int fileWriteFixedLengthString(File* stream, char* string, int length) +{ + return fileWriteUInt8List(stream, (unsigned char*)string, length); +} + +// 0x4C6490 +int fileWriteInt16List(File* stream, short* arr, int count) +{ + for (int index = 0; index < count; index++) { + // NOTE: Uninline. + if (fileWriteInt16(stream, arr[index]) == -1) { + return -1; + } + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C6490. +int fileWriteUInt16List(File* stream, unsigned short* arr, int count) +{ + return fileWriteInt16List(stream, (short*)arr, count); +} + +// NOTE: Can be either signed/unsigned + int/long variant. +// +// 0x4C64F8 +int fileWriteInt32List(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; +} + +// NOTE: Not sure about signed/unsigned int/long. +// +// 0x4C6550 +int _db_fwriteLongCount(File* stream, int* arr, int count) +{ + for (int index = 0; index < count; index++) { + int value = arr[index]; + + // NOTE: Uninline. + if (fileWriteInt16(stream, (value >> 16) & 0xFFFF) == -1) { + return -1; + } + + // NOTE: Uninline. + if (fileWriteInt16(stream, value & 0xFFFF) == -1) { + return -1; + } + } + + return 0; +} + +// NOTE: Probably uncollapsed 0x4C64F8 or 0x4C6550. +int fileWriteUInt32List(File* stream, unsigned int* arr, int count) +{ + return fileWriteInt32List(stream, (int*)arr, count); +} + +// 0x4C6628 +int fileNameListInit(const char* pattern, char*** fileNameListPtr, int a3, int a4) +{ + FileList* fileList = malloc(sizeof(*fileList)); + if (fileList == NULL) { + return 0; + } + + memset(fileList, 0, sizeof(*fileList)); + + XList* xlist = &(fileList->xlist); + if (!xlistInit(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 v1 = *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); + + bool v2 = false; + if (v1) { + char* pch = dir; + while (*pch != '\0' && *pch != '\\') { + pch++; + } + v2 = *pch != '\0'; + } + + if (!v2) { + sprintf(xlist->fileNames[index], "%s%s", fileName, extension); + length++; + } + } + } + + fileList->next = gFileListHead; + gFileListHead = fileList; + + *fileNameListPtr = xlist->fileNames; + + return length; +} + +// 0x4C6868 +void fileNameListFree(char*** fileNameListPtr, int a2) +{ + if (gFileListHead == NULL) { + return; + } + + FileList* currentFileList = gFileListHead; + FileList* previousFileList = gFileListHead; + while (*fileNameListPtr != currentFileList->xlist.fileNames) { + previousFileList = currentFileList; + currentFileList = currentFileList->next; + if (currentFileList == NULL) { + return; + } + } + + if (previousFileList == gFileListHead) { + gFileListHead = currentFileList->next; + } else { + previousFileList->next = currentFileList->next; + } + + xlistFree(&(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 fileGetSize(File* stream) +{ + return xfileGetSize(stream); +} + +// 0x4C68C4 +void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size) +{ + if (handler != NULL && size != 0) { + gFileReadProgressHandler = handler; + gFileReadProgressChunkSize = size; + } else { + gFileReadProgressHandler = NULL; + gFileReadProgressChunkSize = 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 +int _db_list_compare(const void* p1, const void* p2) +{ + return stricmp(*(const char**)p1, *(const char**)p2); +} diff --git a/src/db.h b/src/db.h new file mode 100644 index 0000000..0d9e45e --- /dev/null +++ b/src/db.h @@ -0,0 +1,81 @@ +#ifndef DB_H +#define DB_H + +#include "memory_defs.h" +#include "xfile.h" + +#include +#include + +typedef XFile File; +typedef void FileReadProgressHandler(); +typedef char* StrdupProc(const char* string); + +typedef struct FileList { + XList xlist; + struct FileList* next; +} FileList; + +extern FileReadProgressHandler* gFileReadProgressHandler; +extern int gFileReadProgressBytesRead; + +extern int gFileReadProgressChunkSize; +extern FileList* gFileListHead; + +int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4); +int _db_current(int a1); +bool _db_total(); +void dbExit(); +int dbGetFileSize(const char* filePath, int* sizePtr); +int dbGetFileContents(const char* filePath, void* ptr); +int fileClose(File* stream); +File* fileOpen(const char* filename, const char* mode); +int filePrintFormatted(File* stream, const char* format, ...); +int fileReadChar(File* stream); +char* fileReadString(char* str, size_t size, File* stream); +int fileWriteString(const char* s, File* stream); +size_t fileRead(void* buf, size_t size, size_t count, File* stream); +size_t fileWrite(const void* buf, size_t size, size_t count, File* stream); +int fileSeek(File* stream, long offset, int origin); +long fileTell(File* stream); +void fileRewind(File* stream); +int fileEof(File* stream); +int fileReadUInt8(File* stream, unsigned char* valuePtr); +int fileReadInt16(File* stream, short* valuePtr); +int fileReadUInt16(File* stream, unsigned short* valuePtr); +int fileReadInt32(File* stream, int* valuePtr); +int fileReadUInt32(File* stream, unsigned int* valuePtr); +int _db_freadInt(File* stream, int* valuePtr); +int fileReadFloat(File* stream, float* valuePtr); +int fileReadBool(File* stream, bool* valuePtr); +int fileWriteUInt8(File* stream, unsigned char value); +int fileWriteInt16(File* stream, short value); +int fileWriteUInt16(File* stream, unsigned short value); +int fileWriteInt32(File* stream, int value); +int _db_fwriteLong(File* stream, int value); +int fileWriteUInt32(File* stream, unsigned int value); +int fileWriteFloat(File* stream, float value); +int fileWriteBool(File* stream, bool value); +int fileReadUInt8List(File* stream, unsigned char* arr, int count); +int fileReadFixedLengthString(File* stream, char* string, int length); +int fileReadInt16List(File* stream, short* arr, int count); +int fileReadUInt16List(File* stream, unsigned short* arr, int count); +int fileReadInt32List(File* stream, int* arr, int count); +int _db_freadIntCount(File* stream, int* arr, int count); +int fileReadUInt32List(File* stream, unsigned int* arr, int count); +int fileWriteUInt8List(File* stream, unsigned char* arr, int count); +int fileWriteFixedLengthString(File* stream, char* string, int length); +int fileWriteInt16List(File* stream, short* arr, int count); +int fileWriteUInt16List(File* stream, unsigned short* arr, int count); +int fileWriteInt32List(File* stream, int* arr, int count); +int _db_fwriteLongCount(File* stream, int* arr, int count); +int fileWriteUInt32List(File* stream, unsigned int* arr, int count); +int fileNameListInit(const char* pattern, char*** fileNames, int a3, int a4); +void fileNameListFree(char*** fileNames, int a2); +void _db_register_mem(MallocProc* mallocProc, StrdupProc* strdupProc, FreeProc* freeProc); +int fileGetSize(File* stream); +void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size); +void _db_enable_hash_table_(); +int _db_list_compare(const void* p1, const void* p2); + +#endif /* DB_H */ diff --git a/src/dbox.c b/src/dbox.c new file mode 100644 index 0000000..1815348 --- /dev/null +++ b/src/dbox.c @@ -0,0 +1,620 @@ +#include "dbox.h" + +#include "art.h" +#include "color.h" +#include "core.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_sound.h" +#include "message.h" +#include "text_font.h" +#include "window_manager.h" +#include "word_wrap.h" + +#include +#include + +// 0x5108C8 +const int gDialogBoxBackgroundFrmIds[DIALOG_TYPE_COUNT] = { + 218, // MEDIALOG.FRM - Medium generic dialog box + 217, // LGDIALOG.FRM - Large generic dialog box +}; + +// 0x5108D0 +const int _ytable[DIALOG_TYPE_COUNT] = { + 23, + 27, +}; + +// 0x5108D8 +const int _xtable[DIALOG_TYPE_COUNT] = { + 29, + 29, +}; + +// 0x5108E0 +const int _doneY[DIALOG_TYPE_COUNT] = { + 81, + 98, +}; + +// 0x5108E8 +const int _doneX[DIALOG_TYPE_COUNT] = { + 51, + 37, +}; + +// 0x5108F0 +const int _dblines[DIALOG_TYPE_COUNT] = { + 5, + 6, +}; + +// 0x510900 +int _flgids[7] = { + 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[7] = { + 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 showDialogBox(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 = fontGetCurrent(); + + 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 = fontGetStringWidth(title); + } + + int linesCount = 0; + for (int index = 0; index < bodyLength; index++) { + // NOTE: Calls [fontGetStringWidth] twice because of [max] macro. + maximumLineWidth = max(fontGetStringWidth(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 = buildFid(6, gDialogBoxBackgroundFrmIds[dialogType], 0, 0, 0); + unsigned char* background = artLockFrameDataReturningSize(fid, &backgroundHandle, &backgroundWidth, &backgroundHeight); + if (background == NULL) { + fontSetCurrent(savedFont); + return -1; + } + + int win = windowCreate(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + if (win == -1) { + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + return -1; + } + + unsigned char* windowBuf = windowGetBuffer(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 = buildFid(6, 209, 0, 0, 0); + doneBox = artLockFrameDataReturningSize(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight); + if (doneBox == NULL) { + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + int downButtonFid = buildFid(6, 9, 0, 0, 0); + downButton = artLockFrameDataReturningSize(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight); + if (downButton == NULL) { + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + int upButtonFid = buildFid(6, 8, 0, 0, 0); + upButton = artLockFrameData(upButtonFid, 0, 0, &upButtonHandle); + if (upButton == NULL) { + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + int v27 = hasTwoButtons ? _doneX[dialogType] : (backgroundWidth - doneBoxWidth) / 2; + blitBufferToBuffer(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * _doneY[dialogType] + v27, backgroundWidth); + + if (!messageListInit(&messageList)) { + artUnlock(upButtonHandle); + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "DBOX.MSG"); + + if (!messageListLoad(&messageList, path)) { + artUnlock(upButtonHandle); + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + // FIXME: Window is not removed. + return -1; + } + + fontSetCurrent(103); + + // 100 - DONE + // 101 - YES + messageListItem.num = (flags & DIALOG_BOX_YES_NO) == 0 ? 100 : 101; + if (messageListGetItem(&messageList, &messageListItem)) { + fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + v27 + 35, messageListItem.text, backgroundWidth, backgroundWidth, _colorTable[18979]); + } + + int btn = buttonCreate(win, v27 + 13, _doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 500, upButton, downButton, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(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); + } + + fontSetCurrent(103); + + blitBufferToBufferTrans(doneBox, + doneBoxWidth, + doneBoxHeight, + doneBoxWidth, + windowBuf + backgroundWidth * _doneY[dialogType] + _doneX[dialogType] + doneBoxWidth + 24, + backgroundWidth); + + fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + _doneX[dialogType] + doneBoxWidth + 59, + a8, backgroundWidth, backgroundWidth, _colorTable[18979]); + + int btn = buttonCreate(win, + doneBoxWidth + _doneX[dialogType] + 37, + _doneY[dialogType] + 4, + downButtonWidth, + downButtonHeight, + -1, -1, -1, 501, upButton, downButton, 0, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } else { + int doneBoxFid = buildFid(6, 209, 0, 0, 0); + unsigned char* doneBox = artLockFrameDataReturningSize(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight); + if (doneBox == NULL) { + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + int downButtonFid = buildFid(6, 9, 0, 0, 0); + unsigned char* downButton = artLockFrameDataReturningSize(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight); + if (downButton == NULL) { + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + int upButtonFid = buildFid(6, 8, 0, 0, 0); + unsigned char* upButton = artLockFrameData(upButtonFid, 0, 0, &upButtonHandle); + if (upButton == NULL) { + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + if (!messageListInit(&messageList)) { + artUnlock(upButtonHandle); + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "DBOX.MSG"); + + if (!messageListLoad(&messageList, path)) { + artUnlock(upButtonHandle); + artUnlock(downButtonHandle); + artUnlock(doneBoxHandle); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + windowDestroy(win); + return -1; + } + + blitBufferToBufferTrans(doneBox, + doneBoxWidth, + doneBoxHeight, + doneBoxWidth, + windowBuf + backgroundWidth * _doneY[dialogType] + _doneX[dialogType], + backgroundWidth); + + fontSetCurrent(103); + + fontDrawText(windowBuf + backgroundWidth * (_doneY[dialogType] + 3) + _doneX[dialogType] + 35, + a8, backgroundWidth, backgroundWidth, _colorTable[18979]); + + int btn = buttonCreate(win, + _doneX[dialogType] + 13, + _doneY[dialogType] + 4, + downButtonWidth, + downButtonHeight, + -1, + -1, + -1, + 501, + upButton, + downButton, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + v86 = true; + } + } + + fontSetCurrent(101); + + int v23 = _ytable[dialogType]; + + if ((flags & DIALOG_BOX_NO_VERTICAL_CENTERING) == 0) { + int v41 = _dblines[dialogType] * fontGetLineHeight() / 2 + v23; + v23 = v41 - ((bodyLength + 1) * fontGetLineHeight() / 2); + } + + if (hasTitle) { + if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) { + fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], title, backgroundWidth, backgroundWidth, titleColor); + } else { + int length = fontGetStringWidth(title); + fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, title, backgroundWidth, backgroundWidth, titleColor); + } + v23 += fontGetLineHeight(); + } + + for (int v94 = 0; v94 < bodyLength; v94++) { + int len = fontGetStringWidth(body[v94]); + if (len <= backgroundWidth - 26) { + if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) { + fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], body[v94], backgroundWidth, backgroundWidth, bodyColor); + } else { + int length = fontGetStringWidth(body[v94]); + fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, body[v94], backgroundWidth, backgroundWidth, bodyColor); + } + v23 += fontGetLineHeight(); + } else { + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(body[v94], backgroundWidth - 26, beginnings, &count) != 0) { + debugPrint("\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) { + fontDrawText(windowBuf + backgroundWidth * v23 + _xtable[dialogType], string, backgroundWidth, backgroundWidth, bodyColor); + } else { + int length = fontGetStringWidth(string); + fontDrawText(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, string, backgroundWidth, backgroundWidth, bodyColor); + } + v23 += fontGetLineHeight(); + } + } + } + + windowRefresh(win); + + int rc = -1; + while (rc == -1) { + int keyCode = _get_input(); + + if (keyCode == 500) { + rc = 1; + } else if (keyCode == KEY_RETURN) { + soundPlayFile("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; + } + } + + windowDestroy(win); + artUnlock(backgroundHandle); + fontSetCurrent(savedFont); + + if (v86) { + artUnlock(doneBoxHandle); + artUnlock(downButtonHandle); + artUnlock(upButtonHandle); + messageListFree(&messageList); + } + + return rc; +} + +// 0x41EA78 +int _save_file_dialog(char* a1, char** fileList, char* fileName, int fileListLength, int x, int y, int flags) +{ + int oldFont = fontGetCurrent(); + + unsigned char* frmBuffers[7]; + CacheEntry* frmHandles[7]; + Size frmSizes[7]; + + for (int index = 0; index < 7; index++) { + int fid = buildFid(6, _flgids2[index], 0, 0, 0); + frmBuffers[index] = artLockFrameDataReturningSize(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height)); + if (frmBuffers[index] == NULL) { + while (--index >= 0) { + artUnlock(frmHandles[index]); + } + return -1; + } + } + + int win = windowCreate(x, y, frmSizes[0].width, frmSizes[0].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + if (win == -1) { + for (int index = 0; index < 7; index++) { + artUnlock(frmHandles[index]); + } + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(win); + memcpy(windowBuffer, frmBuffers[0], frmSizes[0].width * frmSizes[0].height); + + MessageList messageList; + MessageListItem messageListItem; + + if (!messageListInit(&messageList)) { + windowDestroy(win); + + for (int index = 0; index < 7; index++) { + artUnlock(frmHandles[index]); + } + + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "DBOX.MSG"); + + if (!messageListLoad(&messageList, path)) { + windowDestroy(win); + + for (int index = 0; index < 7; index++) { + artUnlock(frmHandles[index]); + } + + return -1; + } + + fontSetCurrent(103); + + // DONE + const char* done = getmsg(&messageList, &messageListItem, 100); + fontDrawText(windowBuffer + frmSizes[0].width * 213 + 79, done, frmSizes[0].width, frmSizes[0].width, _colorTable[18979]); + + // CANCEL + const char* cancel = getmsg(&messageList, &messageListItem, 103); + fontDrawText(windowBuffer + frmSizes[0].width * 213 + 182, cancel, frmSizes[0].width, frmSizes[0].width, _colorTable[18979]); + + int doneBtn = buttonCreate(win, + 58, + 214, + frmSizes[2].width, + frmSizes[2].height, + -1, + -1, + -1, + 500, + frmBuffers[1], + frmBuffers[2], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (doneBtn != -1) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int cancelBtn = buttonCreate(win, + 163, + 214, + frmSizes[2].width, + frmSizes[2].height, + -1, + -1, + -1, + 501, + frmBuffers[1], + frmBuffers[2], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (cancelBtn != -1) { + buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int scrollUpBtn = buttonCreate(win, + 36, + 44, + frmSizes[6].width, + frmSizes[6].height, + -1, + 505, + 506, + 505, + frmBuffers[5], + frmBuffers[6], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (scrollUpBtn != -1) { + buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + int scrollDownButton = buttonCreate(win, + 36, + 44 + frmSizes[6].height, + frmSizes[4].width, + frmSizes[4].height, + -1, + 503, + 504, + 503, + frmBuffers[3], + frmBuffers[4], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (scrollUpBtn != -1) { + buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + buttonCreate( + win, + 55, + 49, + 190, + 124, + -1, + -1, + -1, + 502, + NULL, + NULL, + NULL, + 0); + + if (a1 != NULL) { + fontDrawText(windowBuffer + frmSizes[0].width * 16 + 49, a1, frmSizes[0].width, frmSizes[0].width, _colorTable[18979]); + } +} + +// 0x41FBDC +void _PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch) +{ + int lineHeight = fontGetLineHeight(); + int y = 49; + bufferFill(buffer + y * pitch + 55, 190, 124, pitch, 100); + if (fileListLength != 0) { + if (fileListLength - pageOffset > 12) { + fileListLength = 12; + } + + for (int index = 0; index < fileListLength; index++) { + int color = index == selectedIndex ? _colorTable[32747] : _colorTable[992]; + fontDrawText(buffer + y * index + 55, fileList[index], pitch, pitch, color); + y += lineHeight; + } + } +} diff --git a/src/dbox.h b/src/dbox.h new file mode 100644 index 0000000..50196e4 --- /dev/null +++ b/src/dbox.h @@ -0,0 +1,32 @@ +#ifndef DBOX_H +#define DBOX_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; + +extern const int gDialogBoxBackgroundFrmIds[DIALOG_TYPE_COUNT]; +extern const int _ytable[DIALOG_TYPE_COUNT]; +extern const int _xtable[DIALOG_TYPE_COUNT]; +extern const int _doneY[DIALOG_TYPE_COUNT]; +extern const int _doneX[DIALOG_TYPE_COUNT]; +extern const int _dblines[DIALOG_TYPE_COUNT]; +extern int _flgids[7]; +extern int _flgids2[7]; + +int showDialogBox(const char* title, const char** body, int bodyLength, int x, int y, int titleColor, const char* a8, int bodyColor, int flags); +int _save_file_dialog(char* a1, char** fileList, char* fileName, int fileListLength, int x, int y, int flags); +void _PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch); + +#endif /* DBOX_H */ diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..feda03d --- /dev/null +++ b/src/debug.c @@ -0,0 +1,225 @@ +#include "debug.h" + +#include "memory.h" +#include "window_manager_private.h" + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 0x51DEF8 +FILE* _fd = NULL; + +// 0x51DEFC +int _curx = 0; + +// 0x51DF00 +int _cury = 0; + +// 0x51DF04 +DebugPrintProc* gDebugPrintProc = NULL; + +// 0x4C6CD0 +void _GNW_debug_init() +{ + atexit(_debug_exit); +} + +// 0x4C6CDC +void _debug_register_mono() +{ + if (gDebugPrintProc != _debug_mono) { + if (_fd != NULL) { + fclose(_fd); + _fd = NULL; + } + + gDebugPrintProc = _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); + gDebugPrintProc = _debug_log; + } +} + +// 0x4C6D5C +void _debug_register_screen() +{ + if (gDebugPrintProc != _debug_screen) { + if (_fd != NULL) { + fclose(_fd); + _fd = NULL; + } + + gDebugPrintProc = _debug_screen; + } +} + +// 0x4C6D90 +void _debug_register_env() +{ + const char* type = getenv("DEBUGACTIVE"); + if (type == NULL) { + return; + } + + char* copy = internal_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 (gDebugPrintProc != _win_debug) { + if (_fd != NULL) { + fclose(_fd); + _fd = NULL; + } + + gDebugPrintProc = _win_debug; + } + } + + internal_free(copy); +} + +// 0x4C6F18 +void _debug_register_func(DebugPrintProc* proc) +{ + if (gDebugPrintProc != proc) { + if (_fd != NULL) { + fclose(_fd); + _fd = NULL; + } + + gDebugPrintProc = proc; + } +} + +// 0x4C6F48 +int debugPrint(const char* format, ...) +{ + va_list args; + va_start(args, format); + + int rc; + + if (gDebugPrintProc != NULL) { + char string[260]; + vsprintf(string, format, args); + + rc = gDebugPrintProc(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 (gDebugPrintProc != NULL) { + return gDebugPrintProc(string); + } + + return -1; +} + +// 0x4C6FAC +void _debug_clear() +{ + // TODO: Something with segments. +} + +// 0x4C7004 +int _debug_mono(char* string) +{ + if (gDebugPrintProc == _debug_mono) { + while (*string != '\0') { + char ch = *string++; + _debug_putc(ch); + } + } + return 0; +} + +// 0x4C7028 +int _debug_log(char* string) +{ + if (gDebugPrintProc == _debug_log) { + if (_fd == NULL) { + return -1; + } + + if (fprintf(_fd, string) < 0) { + return -1; + } + + if (fflush(_fd) == EOF) { + return -1; + } + } + + return 0; +} + +// 0x4C7068 +int _debug_screen(char* string) +{ + if (gDebugPrintProc == _debug_screen) { + printf(string); + } + + return 0; +} + +// 0x4C709C +void _debug_putc() +{ + // TODO: Something with segments. +} + +// 0x4C71AC +void _debug_scroll() +{ + // TODO: Something with segments. +} + +// 0x4C71E8 +void _debug_exit(void) +{ + if (_fd != NULL) { + fclose(_fd); + } +} diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..a1b59b7 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,28 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include + +typedef int(DebugPrintProc)(char* string); + +extern FILE* _fd; +extern int _curx; +extern int _cury; +extern DebugPrintProc* gDebugPrintProc; + +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 debugPrint(const char* format, ...); +int _debug_puts(char* string); +void _debug_clear(); +int _debug_mono(char* string); +int _debug_log(char* string); +int _debug_screen(char* string); +void _debug_putc(); +void _debug_exit(void); + +#endif /* DEBUG_H */ diff --git a/src/dfile.c b/src/dfile.c new file mode 100644 index 0000000..ccb49d2 --- /dev/null +++ b/src/dfile.c @@ -0,0 +1,828 @@ +#include "dfile.h" + +#include "fpattern.h" + +#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"); + +// Reads .DAT file contents. +// +// 0x4E4F58 +DBase* dbaseOpen(const char* filePath) +{ + assert(filePath); // "filename", "dfile.c", 74 + + FILE* stream = fopen(filePath, "rb"); + if (stream == NULL) { + return NULL; + } + + 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 = 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 = 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: + + dbaseClose(dbase); + + fclose(stream); + + return NULL; +} + +// Closes [dbase], all open file handles, frees all associated resources, +// including the [dbase] itself. +// +// 0x4E5270 +bool dbaseClose(DBase* dbase) +{ + assert(dbase); // "dbase", "dfile.c", 173 + + DFile* curr = dbase->dfileHead; + while (curr != NULL) { + DFile* next = curr->next; + dfileClose(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 dbaseFindFirstEntry(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 dbaseFindNextEntry(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 dbaseFindClose(DBase* dbase, DFileFindData* findFileData) +{ + return true; +} + +// [filelength]. +// +// 0x4E5424 +long dfileGetSize(DFile* stream) +{ + return stream->entry->uncompressedSize; +} + +// [fclose]. +// +// 0x4E542C +int dfileClose(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* dfileOpen(DBase* dbase, const char* filePath, const char* mode) +{ + assert(dbase); // dfile.c, 295 + assert(filePath); // dfile.c, 296 + assert(mode); // dfile.c, 297 + + return dfileOpenInternal(dbase, filePath, mode, 0); +} + +// [vfprintf]. +// +// 0x4E56C0 +int dfilePrintFormattedArgs(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 dfileReadChar(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 = dfileReadCharInternal(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* dfileReadString(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 = dfileReadCharInternal(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 dfileWriteChar(int ch, DFile* stream) +{ + assert(stream); // "stream", "dfile.c", 437 + + return -1; +} + +// [fputs]. +// +// 0x4E5854 +int dfileWriteString(const char* string, DFile* stream) +{ + assert(string); // "s", "dfile.c", 448 + assert(stream); // "stream", "dfile.c", 449 + + return -1; +} + +// [fread]. +// +// 0x4E58FC +size_t dfileRead(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 = ptr; + *byteBuffer++ = stream->ungotten & 0xFF; + ptr = byteBuffer; + + bytesToRead--; + + stream->flags &= ~DFILE_HAS_UNGETC; + extraBytesRead = 1; + } + + size_t bytesRead; + if (stream->entry->compressed == 1) { + if (!dfileReadCompressed(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 dfileWrite(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 dfileSeek(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. + dfileRewind(stream); + } + + // Consume characters one by one until we reach specified offset. + while (offsetFromBeginning > stream->position) { + if (dfileReadCharInternal(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 dfileTell(DFile* stream) +{ + assert(stream); // "stream", "dfile.c", 654 + + return stream->position; +} + +// [rewind]. +// +// 0x4E5CB0 +void dfileRewind(DFile* stream) +{ + assert(stream); // "stream", "dfile.c", 664 + + dfileSeek(stream, 0, SEEK_SET); + + stream->flags &= ~DFILE_ERROR; +} + +// [feof]. +// +// 0x4E5D10 +int dfileEof(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 +int dbaseFindEntryByFilePath(const void* a1, const void* a2) +{ + const char* filePath = (const char*)a1; + DBaseEntry* entry = (DBaseEntry*)a2; + + return stricmp(filePath, entry->path); +} + +// 0x4E5D9C +DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, DFile* dfile) +{ + DBaseEntry* entry = bsearch(filePath, dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dbaseFindEntryByFilePath); + if (entry == NULL) { + goto err; + } + + if (mode[0] != 'r') { + goto err; + } + + if (dfile == NULL) { + 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 = malloc(sizeof(*dfile->decompressionStream)); + if (dfile->decompressionStream == NULL) { + goto err; + } + + dfile->decompressionBuffer = 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) { + dfileClose(dfile); + } + + return NULL; +} + +// 0x4E5F9C +int dfileReadCharInternal(DFile* stream) +{ + if (stream->entry->compressed == 1) { + char ch; + if (!dfileReadCompressed(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 (dfileReadCompressed(stream, &nextCh, sizeof(nextCh))) { + if (nextCh == '\n') { + ch = nextCh; + } else { + // NOTE: Uninline. + dfileUngetCompressed(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 +bool dfileReadCompressed(DFile* stream, void* ptr, size_t size) +{ + if ((stream->flags & DFILE_HAS_COMPRESSED_UNGETC) != 0) { + unsigned char* byteBuffer = 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 = 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 +void dfileUngetCompressed(DFile* stream, int ch) +{ + stream->compressedUngotten = ch; + stream->flags |= DFILE_HAS_COMPRESSED_UNGETC; + stream->position--; +} diff --git a/src/dfile.h b/src/dfile.h new file mode 100644 index 0000000..c831d61 --- /dev/null +++ b/src/dfile.h @@ -0,0 +1,161 @@ +#ifndef DFILE_H +#define DFILE_H + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#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. +// +// [dfileRewind] 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 + // [dfileClose]. + 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 [dbaseFindFirstEntry] succeeds so + // that subsequent calls to [dbaseFindNextEntry] 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 [dbaseFindFirstEntry] and + // [dbaseFindNextEntry] succeed so that subsequent calls to [dbaseFindNextEntry] + // knows where to start search from. + int index; +} DFileFindData; + +DBase* dbaseOpen(const char* filename); +bool dbaseClose(DBase* dbase); +bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char* pattern); +bool dbaseFindNextEntry(DBase* dbase, DFileFindData* findFileData); +bool dbaseFindClose(DBase* dbase, DFileFindData* findFileData); +long dfileGetSize(DFile* stream); +int dfileClose(DFile* stream); +DFile* dfileOpen(DBase* dbase, const char* filename, const char* mode); +int dfilePrintFormattedArgs(DFile* stream, const char* format, va_list args); +int dfileReadChar(DFile* stream); +char* dfileReadString(char* str, int size, DFile* stream); +int dfileWriteChar(int ch, DFile* stream); +int dfileWriteString(const char* s, DFile* stream); +size_t dfileRead(void* ptr, size_t size, size_t count, DFile* stream); +size_t dfileWrite(const void* ptr, size_t size, size_t count, DFile* stream); +int dfileSeek(DFile* stream, long offset, int origin); +long dfileTell(DFile* stream); +void dfileRewind(DFile* stream); +int dfileEof(DFile* stream); +int dbaseFindEntryByFilePath(const void* a1, const void* a2); +DFile* dfileOpenInternal(DBase* dbase, const char* filename, const char* mode, DFile* a4); +int dfileReadCharInternal(DFile* stream); +bool dfileReadCompressed(DFile* stream, void* ptr, size_t size); +void dfileUngetCompressed(DFile* stream, int ch); + +#endif /* DFILE_H */ diff --git a/src/dialog.c b/src/dialog.c new file mode 100644 index 0000000..4473122 --- /dev/null +++ b/src/dialog.c @@ -0,0 +1,733 @@ +#include "dialog.h" + +#include "core.h" +#include "memory_manager.h" +#include "movie.h" +#include "text_font.h" +#include "widget.h" +#include "window_manager.h" + +#include + +// 0x501623 +const float flt_501623 = 31.0; + +// 0x501627 +const float flt_501627 = 31.0; + +// 0x5184B4 +int _tods = -1; + +// 0x5184B8 +int _topDialogLine = 0; + +// 0x5184BC +int _topDialogReply = 0; + +// 0x5184E4 +DialogFunc1* _replyWinDrawCallback = NULL; + +// 0x5184E8 +DialogFunc2* _optionsWinDrawCallback = NULL; + +// 0x5184EC +int gDialogBorderX = 7; + +// 0x5184F0 +int gDialogBorderY = 7; + +// 0x5184F4 +int gDialogOptionSpacing = 5; + +// 0x5184F8 +int _replyRGBset = 0; + +// 0x5184FC +int _optionRGBset = 0; + +// 0x518500 +int _exitDialog = 0; + +// 0x518504 +int _inDialog = 0; + +// 0x518508 +int _mediaFlag = 2; + +// 0x56DAE0 +STRUCT_56DAE0 _dialog[4]; + +// Reply flags. +// +// 0x56DB60 +short word_56DB60; + +// 0x56DB64 +int dword_56DB64; + +// 0x56DB68 +int dword_56DB68; + +// 0x56DB6C +int dword_56DB6C; + +// 0x56DB70 +int dword_56DB70; + +// 0x56DB74 +int _rand2plus; + +// 0x56DB7C +int dword_56DB7C; + +// 0x56DB80 +int dword_56DB80; + +// 0x56DB84 +int dword_56DB84; + +// 0x56DB88 +int dword_56DB88; + +// 0x56DB8C +int dword_56DB8C; + +// 0x56DB90 +int _replyPlaying; + +// 0x56DB94 +int _replyWin = -1; + +// 0x56DB98 +int gDialogReplyColorG; + +// 0x56DB9C +int gDialogReplyColorB; + +// 0x56DBA4 +int gDialogOptionColorG; + +// 0x56DBA8 +int gDialogReplyColorR; + +// 0x56DBAC +int gDialogOptionColorB; + +// 0x56DBB0 +int gDialogOptionColorR; + +// 0x56DBB4 +int _downButton; + +// 0x56DBB8 +int dword_56DBB8; + +// 0x56DBBC +int dword_56DBBC; + +// 0x56DBC0 +void* off_56DBC0; + +// 0x56DBC4 +void* off_56DBC4; + +// 0x56DBC8 +void* off_56DBC8; + +// 0x56DBCC +void* off_56DBCC; + +// 0x56DBD0 +char* gDialogReplyTitle; + +// 0x56DBD4 +int _upButton; + +// 0x56DBD8 +int dword_56DBD8; + +// 0x56DBDC +int dword_56DBDC; + +// 0x56DBE0 +void* off_56DBE0; + +// 0x56DBE4 +void* off_56DBE4; + +// 0x56DBE8 +void* off_56DBE8; + +// 0x56DBEC +void* off_56DBEC; + +// 0x42F434 +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 = internal_malloc_safe(sizeof(STRUCT_56DAE0_FIELD_4_FIELD_C), __FILE__, __LINE__); // "..\\int\\DIALOG.C", 789 + } else { + v0->field_14++; + v1 = internal_realloc_safe(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 +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].field_8 = 2; + + if (a1 != NULL) { + v14 = internal_malloc_safe(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 = internal_malloc_safe(strlen(a2) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 810 + strcpy(v15, a2); + v18->field_C[v17].field_4 = v15; + } else { + v18->field_C[v17].field_4 = NULL; + } + + v18->field_C[v17].field_18 = widgetGetFont(); + v18->field_C[v17].field_1A = word_56DB60; + v18->field_C[v17].field_14 = a3; +} + +// 0x42F624 +void _replyAddOptionProc(const char* a1, const char* a2, int a3) +{ + STRUCT_56DAE0_FIELD_4* v5; + int v13; + char* v11; + + v5 = _getReply(); + v13 = v5->field_14 - 1; + + v5->field_C[v13].field_8 = 1; + + if (a1 != NULL) { + v11 = internal_malloc_safe(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].field_4 = (char*)a2; + + v5->field_C[v13].field_18 = widgetGetFont(); + v5->field_C[v13].field_1A = word_56DB60; + v5->field_C[v13].field_14 = a3; +} + +// 0x42F714 +void _optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1) +{ + if (a1->field_0 != NULL) { + internal_free_safe(a1->field_0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 844 + } + + if (a1->field_8 == 2) { + if (a1->field_4 != NULL) { + internal_free_safe(a1->field_4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 846 + } + } +} + +// 0x42F754 +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])); + } + + internal_free_safe(v6->field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 857 + } + + if (v6->field_8 != NULL) { + internal_free_safe(v6->field_8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 860 + } + + if (v6->field_4 != NULL) { + internal_free_safe(v6->field_4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 862 + } + + if (v6->field_0 != NULL) { + internal_free_safe(v6->field_0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 864 + } + } + + if (ptr->field_4 != NULL) { + internal_free_safe(ptr->field_4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 867 + } +} + +// 0x42FB94 +int _endDialog() +{ + if (_tods == -1) { + return -1; + } + + _topDialogReply = _dialog[_tods].field_10; + _replyFree(); + + if (gDialogReplyTitle != NULL) { + internal_free_safe(gDialogReplyTitle, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 986 + gDialogReplyTitle = NULL; + } + + --_tods; + + return 0; +} + +// 0x42FC70 +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 * fontGetLineHeight(); + _windowPrintBuf(win, strings[i], strlen(strings[i]), a4, a5 + a7, a6, v11, a8, a9); + } +} + +// 0x42FCF0 +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 +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 (!(mouseGetEvent() & 0x10)) { + result = 0; + } else { + mouseGetPosition(&x, &y); + + if (windowGetAtPoint(x, y) != _replyWin) { + result = 0; + } + } + } + } + return result; +} + +// 0x430180 +void _endReply() +{ + if (_replyPlaying != 2) { + if (_replyPlaying == 1) { + if (!(_mediaFlag & 2) && _replyWin != -1) { + windowDestroy(_replyWin); + _replyWin = -1; + } + } else if (_replyPlaying != 3 && _replyWin != -1) { + windowDestroy(_replyWin); + _replyWin = -1; + } + } +} + +// 0x4301E8 +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 = widgetGetFont(); + widgetSetFont(font); + + _printStr(win, str, width, height, left, top, a8, a9, a10); + + rect.left = left; + rect.top = top; + rect.right = width + left; + rect.bottom = height + top; + windowRefreshRect(win, &rect); + widgetSetFont(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 dialogSetReplyTitle(const char* a1) +{ + if (gDialogReplyTitle != NULL) { + internal_free_safe(gDialogReplyTitle, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2561 + } + + if (a1 != NULL) { + gDialogReplyTitle = internal_malloc_safe(strlen(a1) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2564 + strcpy(gDialogReplyTitle, a1); + } else { + gDialogReplyTitle = 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, const char* a2) +{ + if (_dialog[_tods].field_C == -1) { + return 1; + } + + _replyAddOptionProc(a1, a2, 0); + + return 0; +} + +// 0x431184 +int _dialogGetExitPoint() +{ + return _topDialogLine + (_topDialogReply << 16); +} + +// 0x431198 +int _dialogQuit() +{ + if (_inDialog) { + _exitDialog = 1; + } else { + _endDialog(); + } + + return 0; +} + +// 0x4311B8 +int dialogSetOptionWindow(int a1, int a2, int a3, int a4, int a5) +{ + dword_56DB6C = a1; + dword_56DB70 = a2; + dword_56DB64 = a3; + dword_56DB68 = a4; + _rand2plus = a5; + return 0; +} + +// 0x4311E0 +int dialogSetReplyWindow(int a1, int a2, int a3, int a4, int a5) +{ + dword_56DB84 = a1; + dword_56DB88 = a2; + dword_56DB7C = a3; + dword_56DB80 = a4; + dword_56DB8C = a5; + + return 0; +} + +// 0x431208 +int dialogSetBorder(int a1, int a2) +{ + gDialogBorderX = a1; + gDialogBorderY = a2; + + return 0; +} + +// 0x431218 +int _dialogSetScrollUp(int a1, int a2, void* a3, void* a4, void* a5, void* a6, int a7) +{ + _upButton = a1; + dword_56DBD8 = a2; + + if (off_56DBE0 != NULL) { + internal_free_safe(off_56DBE0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2750 + } + off_56DBE0 = a3; + + if (off_56DBE4 != NULL) { + internal_free_safe(off_56DBE4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2752 + } + off_56DBE4 = a4; + + if (off_56DBE8 != NULL) { + internal_free_safe(off_56DBE8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2754 + } + off_56DBE8 = a5; + + if (off_56DBEC != NULL) { + internal_free_safe(off_56DBEC, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2756 + } + off_56DBEC = a5; + + dword_56DBDC = a7; + + return 0; +} + +// 0x4312C0 +int _dialogSetScrollDown(int a1, int a2, void* a3, void* a4, void* a5, void* a6, int a7) +{ + _downButton = a1; + dword_56DBB8 = a2; + + if (off_56DBC0 != NULL) { + internal_free_safe(off_56DBC0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2765 + } + off_56DBC0 = a3; + + if (off_56DBC4 != NULL) { + internal_free_safe(off_56DBC4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2767 + } + off_56DBC4 = a4; + + if (off_56DBC8 != NULL) { + internal_free_safe(off_56DBC8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2769 + } + off_56DBC8 = a5; + + if (off_56DBCC != NULL) { + internal_free_safe(off_56DBCC, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2771 + } + off_56DBCC = a6; + + dword_56DBBC = a7; + + return 0; +} + +// 0x431368 +int dialogSetOptionSpacing(int value) +{ + gDialogOptionSpacing = value; + + return 0; +} + +// 0x431370 +int dialogSetOptionColor(float a1, float a2, float a3) +{ + gDialogOptionColorR = (int)(a1 * flt_501623); + gDialogOptionColorG = (int)(a2 * flt_501623); + gDialogOptionColorB = (int)(a3 * flt_501623); + + _optionRGBset = 1; + + return 0; +} + +// 0x4313C8 +int dialogSetReplyColor(float a1, float a2, float a3) +{ + gDialogReplyColorR = (int)(a1 * flt_501627); + gDialogReplyColorG = (int)(a2 * flt_501627); + gDialogReplyColorB = (int)(a3 * flt_501627); + + _replyRGBset = 1; + + return 0; +} + +// 0x431420 +int _dialogSetOptionFlags(int flags) +{ + word_56DB60 = flags & 0xFFFF; + + return 1; +} + +// 0x431434 +void _dialogClose() +{ + if (off_56DBE0) { + internal_free_safe(off_56DBE0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2818 + } + + if (off_56DBE4) { + internal_free_safe(off_56DBE4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2819 + } + + if (off_56DBE8) { + internal_free_safe(off_56DBE8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2820 + } + + if (off_56DBEC) { + internal_free_safe(off_56DBEC, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2821 + } + + if (off_56DBC0) { + internal_free_safe(off_56DBC0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2823 + } + + if (off_56DBC4) { + internal_free_safe(off_56DBC4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2824 + } + + if (off_56DBC8) { + internal_free_safe(off_56DBC8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2825 + } + + if (off_56DBCC) { + internal_free_safe(off_56DBCC, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2826 + } +} + +// 0x431518 +int _dialogGetDialogDepth() +{ + return _tods; +} + +// 0x431520 +void _dialogRegisterWinDrawCallbacks(DialogFunc1* a1, DialogFunc2* a2) +{ + _replyWinDrawCallback = a1; + _optionsWinDrawCallback = a2; +} + +// 0x431530 +int _dialogToggleMediaFlag(int a1) +{ + if ((a1 & _mediaFlag) == a1) { + _mediaFlag &= ~a1; + } else { + _mediaFlag |= a1; + } + + return _mediaFlag; +} + +// 0x431554 +int _dialogGetMediaFlag() +{ + return _mediaFlag; +} diff --git a/src/dialog.h b/src/dialog.h new file mode 100644 index 0000000..0d2d367 --- /dev/null +++ b/src/dialog.h @@ -0,0 +1,128 @@ +#ifndef DIALOG_H +#define DIALOG_H + +#include "interpreter.h" + +typedef void DialogFunc1(int win); +typedef void DialogFunc2(int win); + +typedef struct STRUCT_56DAE0_FIELD_4_FIELD_C { + char* field_0; + char* field_4; + int field_8; + 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; + +extern const float flt_501623; +extern const float flt_501627; + +extern int _tods; +extern int _topDialogLine; +extern int _topDialogReply; +extern DialogFunc1* _replyWinDrawCallback; +extern DialogFunc2* _optionsWinDrawCallback; +extern int gDialogBorderX; +extern int gDialogBorderY; +extern int gDialogOptionSpacing; +extern int _replyRGBset; +extern int _optionRGBset; +extern int _exitDialog; +extern int _inDialog; +extern int _mediaFlag; + +extern STRUCT_56DAE0 _dialog[4]; +extern short word_56DB60; +extern int dword_56DB64; +extern int dword_56DB68; +extern int dword_56DB6C; +extern int dword_56DB70; +extern int _rand2plus; +extern int dword_56DB7C; +extern int dword_56DB80; +extern int dword_56DB84; +extern int dword_56DB88; +extern int dword_56DB8C; +extern int _replyPlaying; +extern int _replyWin; +extern int gDialogReplyColorG; +extern int gDialogReplyColorB; +extern int gDialogOptionColorG; +extern int gDialogReplyColorR; +extern int gDialogOptionColorB; +extern int gDialogOptionColorR; +extern int _downButton; +extern int dword_56DBB8; +extern int dword_56DBBC; +extern void* off_56DBC0; +extern void* off_56DBC4; +extern void* off_56DBC8; +extern void* off_56DBCC; +extern char* gDialogReplyTitle; +extern int _upButton; +extern int dword_56DBD8; +extern int dword_56DBDC; +extern void* off_56DBE0; +extern void* off_56DBE4; +extern void* off_56DBE8; +extern void* off_56DBEC; + +STRUCT_56DAE0_FIELD_4* _getReply(); +void _replyAddOption(const char* a1, const char* a2, int a3); +void _replyAddOptionProc(const char* a1, const char* a2, int a3); +void _optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1); +void _replyFree(); +int _endDialog(); +void _printLine(int win, char** strings, int strings_num, int a4, int a5, int a6, int a7, int a8, int a9); +void _printStr(int win, char* a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); +int _abortReply(int a1); +void _endReply(); +void _drawStr(int win, char* a2, int font, int width, int height, int left, int top, int a8, int a9, int a10); +int _dialogStart(Program* a1); +int _dialogRestart(); +int _dialogGotoReply(const char* a1); +int dialogSetReplyTitle(const char* a1); +int _dialogReply(const char* a1, const char* a2); +int _dialogOption(const char* a1, const char* a2); +int _dialogOptionProc(const char* a1, const char* a2); +int _dialogGetExitPoint(); +int _dialogQuit(); +int dialogSetOptionWindow(int a1, int a2, int a3, int a4, int a5); +int dialogSetReplyWindow(int a1, int a2, int a3, int a4, int a5); +int dialogSetBorder(int a1, int a2); +int _dialogSetScrollUp(int a1, int a2, void* a3, void* a4, void* a5, void* a6, int a7); +int _dialogSetScrollDown(int a1, int a2, void* a3, void* a4, void* a5, void* a6, int a7); +int dialogSetOptionSpacing(int value); +int dialogSetOptionColor(float a1, float a2, float a3); +int dialogSetReplyColor(float a1, float a2, float a3); +int _dialogSetOptionFlags(int flags); +void _dialogClose(); +int _dialogGetDialogDepth(); +void _dialogRegisterWinDrawCallbacks(DialogFunc1* a1, DialogFunc2* a2); +int _dialogToggleMediaFlag(int a1); +int _dialogGetMediaFlag(); + +#endif /* DIALOG_H */ diff --git a/src/dictionary.c b/src/dictionary.c new file mode 100644 index 0000000..06c363b --- /dev/null +++ b/src/dictionary.c @@ -0,0 +1,306 @@ +#include "dictionary.h" + +#include +#include +#include +#include + +// 0x51E408 +MallocProc* gDictionaryMallocProc = dictionaryMallocDefaultImpl; + +// 0x51E40C +ReallocProc* gDictionaryReallocProc = dictionaryReallocDefaultImpl; + +// 0x51E410 +FreeProc* gDictionaryFreeProc = dictionaryFreeDefaultImpl; + +// 0x4D9B90 +void* dictionaryMallocDefaultImpl(size_t size) +{ + return malloc(size); +} + +// 0x4D9B98 +void* dictionaryReallocDefaultImpl(void* ptr, size_t newSize) +{ + return realloc(ptr, newSize); +} + +// 0x4D9BA0 +void dictionaryFreeDefaultImpl(void* ptr) +{ + free(ptr); +} + +// 0x4D9BA8 +int dictionaryInit(Dictionary* dictionary, int initialCapacity, size_t valueSize, void* a4) +{ + dictionary->entriesCapacity = initialCapacity; + dictionary->valueSize = valueSize; + dictionary->entriesLength = 0; + + if (a4 != NULL) { + // NOTE: There is some structure pointed by [a4] with 5 fields. They are + // either memcopied or assigned one by one into field_10 - field_20 + // respectively. This parameter is always NULL, so I doubt it's possible + // to understand it's meaning. There are some hints in the unused + // functions though. + assert(false && "Not implemented"); + } else { + dictionary->field_10 = 0; + dictionary->field_14 = 0; + dictionary->field_18 = 0; + dictionary->field_1C = 0; + } + + int rc = 0; + + if (initialCapacity != 0) { + dictionary->entries = gDictionaryMallocProc(sizeof(*dictionary->entries) * initialCapacity); + if (dictionary->entries == NULL) { + rc = -1; + } + } else { + dictionary->entries = NULL; + } + + if (rc != -1) { + dictionary->marker = DICTIONARY_MARKER; + } + + return rc; +} + +// 0x4D9C0C +int dictionarySetCapacity(Dictionary* dictionary, int newCapacity) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + if (newCapacity < dictionary->entriesLength) { + return -1; + } + + DictionaryEntry* entries = gDictionaryReallocProc(dictionary->entries, sizeof(*dictionary->entries) * newCapacity); + if (entries == NULL) { + return -1; + } + + dictionary->entriesCapacity = newCapacity; + dictionary->entries = entries; + + return 0; +} + +// 0x4D9C48 +int dictionaryFree(Dictionary* dictionary) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + for (int index = 0; index < dictionary->entriesLength; index++) { + DictionaryEntry* entry = &(dictionary->entries[index]); + if (entry->key != NULL) { + gDictionaryFreeProc(entry->key); + } + + if (entry->value != NULL) { + gDictionaryFreeProc(entry->value); + } + } + + if (dictionary->entries != NULL) { + gDictionaryFreeProc(dictionary->entries); + } + + memset(dictionary, 0, sizeof(*dictionary)); + + 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 +int dictionaryFindIndexForKey(Dictionary* dictionary, const char* key, int* indexPtr) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + if (dictionary->entriesLength == 0) { + *indexPtr = 0; + return -1; + } + + int r = dictionary->entriesLength - 1; + int l = 0; + int mid = 0; + int cmp = 0; + while (r >= l) { + mid = (l + r) / 2; + + cmp = stricmp(key, dictionary->entries[mid].key); + if (cmp == 0) { + break; + } + + if (cmp > 0) { + l = l + 1; + } else { + r = r - 1; + } + } + + if (cmp == 0) { + *indexPtr = mid; + return 0; + } + + if (cmp < 0) { + *indexPtr = mid; + } else { + *indexPtr = 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 dictionaryGetIndexByKey(Dictionary* dictionary, const char* key) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + int index; + if (dictionaryFindIndexForKey(dictionary, key, &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 dictionaryAddValue(Dictionary* dictionary, const char* key, const void* value) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + int newElementIndex; + if (dictionaryFindIndexForKey(dictionary, key, &newElementIndex) == 0) { + // Element for this key is already exists. + return -1; + } + + if (dictionary->entriesLength == dictionary->entriesCapacity) { + // Dictionary reached it's capacity and needs to be enlarged. + if (dictionarySetCapacity(dictionary, 2 * (dictionary->entriesCapacity + 1)) == -1) { + return -1; + } + } + + // Make a copy of the key. + char* keyCopy = gDictionaryMallocProc(strlen(key) + 1); + if (keyCopy == NULL) { + return -1; + } + + strcpy(keyCopy, key); + + // Make a copy of the value. + void* valueCopy = NULL; + if (value != NULL && dictionary->valueSize != 0) { + valueCopy = gDictionaryMallocProc(dictionary->valueSize); + if (valueCopy == NULL) { + gDictionaryFreeProc(keyCopy); + return -1; + } + } + + if (valueCopy != NULL && dictionary->valueSize != 0) { + memcpy(valueCopy, value, dictionary->valueSize); + } + + // Starting at the end of entries array loop backwards and move entries down + // one by one until we reach insertion point. + for (int index = dictionary->entriesLength; index > newElementIndex; index--) { + DictionaryEntry* src = &(dictionary->entries[index - 1]); + DictionaryEntry* dest = &(dictionary->entries[index]); + memcpy(dest, src, sizeof(*dictionary->entries)); + } + + DictionaryEntry* entry = &(dictionary->entries[newElementIndex]); + entry->key = keyCopy; + entry->value = valueCopy; + + dictionary->entriesLength++; + + 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 dictionaryRemoveValue(Dictionary* dictionary, const char* key) +{ + if (dictionary->marker != DICTIONARY_MARKER) { + return -1; + } + + int indexToRemove; + if (dictionaryFindIndexForKey(dictionary, key, &indexToRemove) == -1) { + return -1; + } + + DictionaryEntry* entry = &(dictionary->entries[indexToRemove]); + + // Free key and value (which are copies). + gDictionaryFreeProc(entry->key); + if (entry->value != NULL) { + gDictionaryFreeProc(entry->value); + } + + dictionary->entriesLength--; + + // 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 < dictionary->entriesLength; index++) { + DictionaryEntry* src = &(dictionary->entries[index + 1]); + DictionaryEntry* dest = &(dictionary->entries[index]); + memcpy(dest, src, sizeof(*dictionary->entries)); + } + + return 0; +} + +// 0x4DA498 +void dictionarySetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) +{ + if (mallocProc != NULL && reallocProc != NULL && freeProc != NULL) { + gDictionaryMallocProc = mallocProc; + gDictionaryReallocProc = reallocProc; + gDictionaryFreeProc = freeProc; + } else { + gDictionaryMallocProc = dictionaryMallocDefaultImpl; + gDictionaryReallocProc = dictionaryReallocDefaultImpl; + gDictionaryFreeProc = dictionaryFreeDefaultImpl; + } +} diff --git a/src/dictionary.h b/src/dictionary.h new file mode 100644 index 0000000..28b5d2a --- /dev/null +++ b/src/dictionary.h @@ -0,0 +1,61 @@ +#ifndef DICTIONARY_H +#define DICTIONARY_H + +#include "memory_defs.h" + +// 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 + +// A tuple containing individual key-value pair of a dictionary. +typedef struct DictionaryEntry { + char* key; + void* value; +} DictionaryEntry; + +// A collection of key/value pairs. +// +// The keys in dictionary are always strings. Internally dictionary entries +// are kept sorted by the key. Both keys and values are copied when new entry +// is added to dictionary. For this reason the size of the value's type is +// provided during dictionary initialization. +typedef struct Dictionary { + int marker; + + // The number of key/value pairs in the dictionary. + int entriesLength; + + // The capacity of key/value pairs in [entries] array. + int entriesCapacity; + + // The size of the dictionary values in bytes. + size_t valueSize; + + int field_10; + int field_14; + int field_18; + int field_1C; + int field_20; + + // The array of key-value pairs. + DictionaryEntry* entries; +} Dictionary; + +extern MallocProc* gDictionaryMallocProc; +extern ReallocProc* gDictionaryReallocProc; +extern FreeProc* gDictionaryFreeProc; + +void* dictionaryMallocDefaultImpl(size_t size); +void* dictionaryReallocDefaultImpl(void* ptr, size_t newSize); +void dictionaryFreeDefaultImpl(void* ptr); +int dictionaryInit(Dictionary* dictionary, int initialCapacity, size_t valueSize, void* a4); +int dictionarySetCapacity(Dictionary* dictionary, int newCapacity); +int dictionaryFree(Dictionary* dictionary); +int dictionaryFindIndexForKey(Dictionary* dictionary, const char* key, int* index); +int dictionaryGetIndexByKey(Dictionary* dictionary, const char* key); +int dictionaryAddValue(Dictionary* dictionary, const char* key, const void* value); +int dictionaryRemoveValue(Dictionary* dictionary, const char* key); +void dictionarySetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); + +#endif /* DICTIONARY_H */ diff --git a/src/dinput.c b/src/dinput.c new file mode 100644 index 0000000..dea7d05 --- /dev/null +++ b/src/dinput.c @@ -0,0 +1,600 @@ +#include + +#include "dinput.h" + +// 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 + +// 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 +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 +LPDIRECTINPUTA gDirectInput = NULL; + +// 0x51E45C +LPDIRECTINPUTDEVICEA gMouseDevice = NULL; + +// 0x51E460 +LPDIRECTINPUTDEVICEA gKeyboardDevice = NULL; + +// 0x51E464 +int gKeyboardDeviceDataIndex = 0; + +// 0x51E468 +int gKeyboardDeviceDataLength = 0; + +// 0x6B2560 +DIDEVICEOBJECTDATA gKeyboardDeviceData[KEYBOARD_DEVICE_DATA_CAPACITY]; + +// 0x4E0400 +bool directInputInit() +{ + if (gDirectInput != NULL) { + return false; + } + + HRESULT hr = gDirectInputCreateAProc(gInstance, DIRECTINPUT_VERSION, &gDirectInput, NULL); + if (hr != DI_OK) { + goto err; + } + + if (!mouseDeviceInit()) { + goto err; + } + + if (!keyboardDeviceInit()) { + goto err; + } + + return true; + +err: + + keyboardDeviceFree(); + mouseDeviceFree(); + + if (gDirectInput != NULL) { + IDirectInput_Release(gDirectInput); + gDirectInput = NULL; + } + + return false; +} + +// 0x4E0478 +void directInputFree() +{ + // NOTE: Uninline. + keyboardDeviceFree(); + + // NOTE: Uninline. + mouseDeviceFree(); + + if (gDirectInput != NULL) { + IDirectInput_Release(gDirectInput); + gDirectInput = NULL; + } +} + +// 0x4E04E8 +bool mouseDeviceAcquire() +{ + if (gMouseDevice == NULL) { + return false; + } + + HRESULT hr = IDirectInputDevice_Acquire(gMouseDevice); + if (hr != DI_OK && hr != S_FALSE) { + return false; + } + + return true; +} + +// 0x4E0514 +bool mouseDeviceUnacquire() +{ + if (gMouseDevice == NULL) { + return false; + } + + HRESULT hr = IDirectInputDevice_Unacquire(gMouseDevice); + if (hr != DI_OK) { + return false; + } + + return true; +} + +// 0x4E053C +bool mouseDeviceGetData(MouseData* mouseState) +{ + if (gMouseDevice == NULL) { + return false; + } + + if (!mouseDeviceAcquire()) { + return false; + } + + DIMOUSESTATE dims; + HRESULT hr = IDirectInputDevice_GetDeviceState(gMouseDevice, sizeof(dims), &dims); + if (hr != DI_OK) { + return false; + } + + mouseState->x = dims.lX; + mouseState->y = dims.lY; + mouseState->buttons[0] = (dims.rgbButtons[0] & 0x80) != 0; + mouseState->buttons[1] = (dims.rgbButtons[1] & 0x80) != 0; + + return true; +} + +// 0x4E05A8 +bool keyboardDeviceAcquire() +{ + if (gKeyboardDevice == NULL) { + return false; + } + + HRESULT hr = IDirectInputDevice_Acquire(gKeyboardDevice); + if (hr != DI_OK && hr != S_FALSE) { + return false; + } + + return true; +} + +// 0x4E05D4 +bool keyboardDeviceUnacquire() +{ + if (gKeyboardDevice == NULL) { + return false; + } + + HRESULT hr = IDirectInputDevice_Unacquire(gKeyboardDevice); + if (hr != DI_OK) { + return false; + } + + return true; +} + +// 0x4E05FC +bool keyboardDeviceReset() +{ + if (gKeyboardDevice == NULL) { + return false; + } + + if (!keyboardDeviceAcquire()) { + return false; + } + + DWORD items = -1; + HRESULT hr = IDirectInputDevice_GetDeviceData(gKeyboardDevice, sizeof(DIDEVICEOBJECTDATA), NULL, &items, 0); + if (hr != DI_OK && hr != DI_BUFFEROVERFLOW) { + return false; + } + + return true; +} + +// 0x4E0650 +bool keyboardDeviceGetData(KeyboardData* keyboardData) +{ + if (gKeyboardDevice == NULL) { + return false; + } + + if (!keyboardDeviceAcquire()) { + return false; + } + + if (gKeyboardDeviceDataIndex >= gKeyboardDeviceDataLength) { + DWORD items = KEYBOARD_DEVICE_DATA_CAPACITY; + HRESULT hr = IDirectInputDevice_GetDeviceData(gKeyboardDevice, sizeof(DIDEVICEOBJECTDATA), gKeyboardDeviceData, &items, 0); + if (hr == DI_OK || hr == DI_BUFFEROVERFLOW) { + gKeyboardDeviceDataLength = items; + gKeyboardDeviceDataIndex = 0; + } + } + + if (gKeyboardDeviceDataIndex < gKeyboardDeviceDataLength) { + DIDEVICEOBJECTDATA* entry = &(gKeyboardDeviceData[gKeyboardDeviceDataIndex]); + keyboardData->key = entry->dwOfs & 0xFF; + keyboardData->down = (entry->dwData & 0x80) != 0; + gKeyboardDeviceDataIndex++; + + return true; + } + + return false; +} + +// 0x4E070C +bool mouseDeviceInit() +{ + HRESULT hr; + + hr = IDirectInput_CreateDevice(gDirectInput, &GUID_SysMouse, &gMouseDevice, NULL); + if (hr != DI_OK) { + goto err; + } + + hr = IDirectInputDevice_SetCooperativeLevel(gMouseDevice, gProgramWindow, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + if (hr != DI_OK) { + goto err; + } + + hr = IDirectInputDevice_SetDataFormat(gMouseDevice, &c_dfDIMouse); + if (hr != DI_OK) { + goto err; + } + + return true; + +err: + + // NOTE: Uninline. + mouseDeviceFree(); + + return false; +} + +// 0x4E078C +void mouseDeviceFree() +{ + if (gMouseDevice != NULL) { + IDirectInputDevice_Unacquire(gMouseDevice); + IDirectInputDevice_Release(gMouseDevice); + gMouseDevice = NULL; + } +} + +// 0x4E07B8 +bool keyboardDeviceInit() +{ + HRESULT hr; + + hr = IDirectInput_CreateDevice(gDirectInput, &GUID_SysKeyboard, &gKeyboardDevice, NULL); + if (hr != DI_OK) { + goto err; + } + + hr = IDirectInputDevice_SetCooperativeLevel(gKeyboardDevice, gProgramWindow, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); + if (hr != DI_OK) { + goto err; + } + + hr = IDirectInputDevice_SetDataFormat(gKeyboardDevice, &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(gKeyboardDevice, DIPROP_BUFFERSIZE, &(dipdw.diph)); + if (hr != DI_OK) { + goto err; + } + + return true; + +err: + + // NOTE: Uninline. + keyboardDeviceFree(); + + return false; +} + +// 0x4E0874 +void keyboardDeviceFree() +{ + if (gKeyboardDevice != NULL) { + IDirectInputDevice_Unacquire(gKeyboardDevice); + IDirectInputDevice_Release(gKeyboardDevice); + gKeyboardDevice = NULL; + } +} diff --git a/src/dinput.h b/src/dinput.h new file mode 100644 index 0000000..fa29b5a --- /dev/null +++ b/src/dinput.h @@ -0,0 +1,40 @@ +#ifndef DINPUT_H +#define DINPUT_H + +#include "win32.h" + +#define KEYBOARD_DEVICE_DATA_CAPACITY (32) + +typedef struct MouseData { + int x; + int y; + unsigned char buttons[2]; +} MouseData; + +typedef struct KeyboardData { + char key; + char down; +} KeyboardData; + +extern LPDIRECTINPUTA gDirectInput; +extern LPDIRECTINPUTDEVICEA gMouseDevice; +extern LPDIRECTINPUTDEVICEA gKeyboardDevice; +extern int gKeyboardDeviceDataIndex; +extern int gKeyboardDeviceDataLength; +extern DIDEVICEOBJECTDATA gKeyboardDeviceData[KEYBOARD_DEVICE_DATA_CAPACITY]; + +bool directInputInit(); +void directInputFree(); +bool mouseDeviceAcquire(); +bool mouseDeviceUnacquire(); +bool mouseDeviceGetData(MouseData* mouseData); +bool keyboardDeviceAcquire(); +bool keyboardDeviceUnacquire(); +bool keyboardDeviceReset(); +bool keyboardDeviceGetData(KeyboardData* keyboardData); +bool mouseDeviceInit(); +void mouseDeviceFree(); +bool keyboardDeviceInit(); +void keyboardDeviceFree(); + +#endif /* DINPUT_H */ diff --git a/src/display_monitor.c b/src/display_monitor.c new file mode 100644 index 0000000..0773c18 --- /dev/null +++ b/src/display_monitor.c @@ -0,0 +1,365 @@ +#include "display_monitor.h" + +#include "art.h" +#include "color.h" +#include "combat.h" +#include "core.h" +#include "draw.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "interface.h" +#include "memory.h" +#include "text_font.h" +#include "window_manager.h" + +#include + +// 0x51850C +bool gDisplayMonitorInitialized = false; + +// The rectangle that display monitor occupies in the main interface window. +// +// 0x518510 +const Rect gDisplayMonitorRect = { + DISPLAY_MONITOR_X, + DISPLAY_MONITOR_Y, + DISPLAY_MONITOR_X + DISPLAY_MONITOR_WIDTH - 1, + DISPLAY_MONITOR_Y + DISPLAY_MONITOR_HEIGHT - 1, +}; + +// 0x518520 +int gDisplayMonitorScrollDownButton = -1; + +// 0x518524 +int gDisplayMonitorScrollUpButton = -1; + +// 0x56DBFC +char gDisplayMonitorLines[DISPLAY_MONITOR_LINES_CAPACITY][DISPLAY_MONITOR_LINE_LENGTH]; + +// 0x56FB3C +unsigned char* gDisplayMonitorBackgroundFrmData; + +// 0x56FB40 +int _max_disp; + +// 0x56FB44 +bool gDisplayMonitorEnabled; + +// 0x56FB48 +int _disp_curr; + +// 0x56FB4C +int _intface_full_width; + +// 0x56FB50 +int gDisplayMonitorLinesCapacity; + +// 0x56FB54 +int _disp_start; + +// 0x56FB58 +unsigned int gDisplayMonitorLastBeepTimestamp; + +// 0x431610 +int displayMonitorInit() +{ + if (!gDisplayMonitorInitialized) { + int oldFont = fontGetCurrent(); + fontSetCurrent(DISPLAY_MONITOR_FONT); + + gDisplayMonitorLinesCapacity = DISPLAY_MONITOR_LINES_CAPACITY; + _max_disp = DISPLAY_MONITOR_HEIGHT / fontGetLineHeight(); + _disp_start = 0; + _disp_curr = 0; + fontSetCurrent(oldFont); + + gDisplayMonitorBackgroundFrmData = internal_malloc(DISPLAY_MONITOR_WIDTH * DISPLAY_MONITOR_HEIGHT); + if (gDisplayMonitorBackgroundFrmData == NULL) { + return -1; + } + + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 16, 0, 0, 0); + Art* backgroundFrm = artLock(backgroundFid, &backgroundFrmHandle); + if (backgroundFrm == NULL) { + internal_free(gDisplayMonitorBackgroundFrmData); + return -1; + } + + unsigned char* backgroundFrmData = artGetFrameData(backgroundFrm, 0, 0); + _intface_full_width = artGetWidth(backgroundFrm, 0, 0); + blitBufferToBuffer(backgroundFrmData + _intface_full_width * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X, + DISPLAY_MONITOR_WIDTH, + DISPLAY_MONITOR_HEIGHT, + _intface_full_width, + gDisplayMonitorBackgroundFrmData, + DISPLAY_MONITOR_WIDTH); + + artUnlock(backgroundFrmHandle); + + gDisplayMonitorScrollUpButton = buttonCreate(gInterfaceBarWindow, + DISPLAY_MONITOR_X, + DISPLAY_MONITOR_Y, + DISPLAY_MONITOR_WIDTH, + DISPLAY_MONITOR_HALF_HEIGHT, + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + 0); + if (gDisplayMonitorScrollUpButton != -1) { + buttonSetMouseCallbacks(gDisplayMonitorScrollUpButton, + displayMonitorScrollUpOnMouseEnter, + displayMonitorOnMouseExit, + displayMonitorScrollUpOnMouseDown, + NULL); + } + + gDisplayMonitorScrollDownButton = buttonCreate(gInterfaceBarWindow, + 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 (gDisplayMonitorScrollDownButton != -1) { + buttonSetMouseCallbacks(gDisplayMonitorScrollDownButton, + displayMonitorScrollDownOnMouseEnter, + displayMonitorOnMouseExit, + displayMonitorScrollDownOnMouseDown, + NULL); + } + + gDisplayMonitorEnabled = true; + gDisplayMonitorInitialized = true; + + for (int index = 0; index < gDisplayMonitorLinesCapacity; index++) { + gDisplayMonitorLines[index][0] = '\0'; + } + + _disp_start = 0; + _disp_curr = 0; + + displayMonitorRefresh(); + } + + return 0; +} + +// 0x431800 +int displayMonitorReset() +{ + if (gDisplayMonitorInitialized) { + for (int index = 0; index < gDisplayMonitorLinesCapacity; index++) { + gDisplayMonitorLines[index][0] = '\0'; + } + + _disp_start = 0; + _disp_curr = 0; + displayMonitorRefresh(); + } + return 0; +} + +// 0x43184C +void displayMonitorExit() +{ + if (gDisplayMonitorInitialized) { + internal_free(gDisplayMonitorBackgroundFrmData); + gDisplayMonitorInitialized = false; + } +} + +// 0x43186C +void displayMonitorAddMessage(char* str) +{ + if (!gDisplayMonitorInitialized) { + return; + } + + int oldFont = fontGetCurrent(); + fontSetCurrent(DISPLAY_MONITOR_FONT); + + char knob = '\x95'; + + char knobString[2]; + knobString[0] = knob; + knobString[1] = '\0'; + int knobWidth = fontGetStringWidth(knobString); + + if (!isInCombat()) { + unsigned int now = _get_bk_time(); + if (getTicksBetween(now, gDisplayMonitorLastBeepTimestamp) >= DISPLAY_MONITOR_BEEP_DELAY) { + gDisplayMonitorLastBeepTimestamp = now; + soundPlayFile("monitor"); + } + } + + // TODO: Refactor these two loops. + char* v1 = NULL; + while (true) { + while (fontGetStringWidth(str) < DISPLAY_MONITOR_WIDTH - _max_disp - knobWidth) { + char* temp = gDisplayMonitorLines[_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); + gDisplayMonitorLines[_disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\0'; + _disp_start = (_disp_start + 1) % gDisplayMonitorLinesCapacity; + + if (v1 == NULL) { + fontSetCurrent(oldFont); + _disp_curr = _disp_start; + displayMonitorRefresh(); + 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 = gDisplayMonitorLines[_disp_start]; + int length; + if (knob != '\0') { + temp++; + gDisplayMonitorLines[_disp_start][0] = knob; + length = DISPLAY_MONITOR_LINE_LENGTH - 2; + knob = '\0'; + } else { + length = DISPLAY_MONITOR_LINE_LENGTH - 1; + } + strncpy(temp, str, length); + + gDisplayMonitorLines[_disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\0'; + _disp_start = (_disp_start + 1) % gDisplayMonitorLinesCapacity; + + fontSetCurrent(oldFont); + _disp_curr = _disp_start; + displayMonitorRefresh(); +} + +// 0x431A78 +void displayMonitorRefresh() +{ + if (!gDisplayMonitorInitialized) { + return; + } + + unsigned char* buf = windowGetBuffer(gInterfaceBarWindow); + if (buf == NULL) { + return; + } + + buf += _intface_full_width * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X; + blitBufferToBuffer(gDisplayMonitorBackgroundFrmData, + DISPLAY_MONITOR_WIDTH, + DISPLAY_MONITOR_HEIGHT, + DISPLAY_MONITOR_WIDTH, + buf, + _intface_full_width); + + int oldFont = fontGetCurrent(); + fontSetCurrent(DISPLAY_MONITOR_FONT); + + for (int index = 0; index < _max_disp; index++) { + int stringIndex = (_disp_curr + gDisplayMonitorLinesCapacity + index - _max_disp) % gDisplayMonitorLinesCapacity; + fontDrawText(buf + index * _intface_full_width * fontGetLineHeight(), gDisplayMonitorLines[stringIndex], DISPLAY_MONITOR_WIDTH, _intface_full_width, _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++; + } + + windowRefreshRect(gInterfaceBarWindow, &gDisplayMonitorRect); + fontSetCurrent(oldFont); +} + +// 0x431B70 +void displayMonitorScrollUpOnMouseDown(int btn, int keyCode) +{ + if ((gDisplayMonitorLinesCapacity + _disp_curr - 1) % gDisplayMonitorLinesCapacity != _disp_start) { + _disp_curr = (gDisplayMonitorLinesCapacity + _disp_curr - 1) % gDisplayMonitorLinesCapacity; + displayMonitorRefresh(); + } +} + +// 0x431B9C +void displayMonitorScrollDownOnMouseDown(int btn, int keyCode) +{ + if (_disp_curr != _disp_start) { + _disp_curr = (_disp_curr + 1) % gDisplayMonitorLinesCapacity; + displayMonitorRefresh(); + } +} + +// 0x431BC8 +void displayMonitorScrollUpOnMouseEnter(int btn, int keyCode) +{ + gameMouseSetCursor(MOUSE_CURSOR_SMALL_ARROW_UP); +} + +// 0x431BD4 +void displayMonitorScrollDownOnMouseEnter(int btn, int keyCode) +{ + gameMouseSetCursor(MOUSE_CURSOR_SMALL_ARROW_DOWN); +} + +// 0x431BE0 +void displayMonitorOnMouseExit(int btn, int keyCode) +{ + gameMouseSetCursor(MOUSE_CURSOR_ARROW); +} + +// 0x431BEC +void displayMonitorDisable() +{ + if (gDisplayMonitorEnabled) { + buttonDisable(gDisplayMonitorScrollDownButton); + buttonDisable(gDisplayMonitorScrollUpButton); + gDisplayMonitorEnabled = false; + } +} + +// 0x431C14 +void displayMonitorEnable() +{ + if (!gDisplayMonitorEnabled) { + buttonEnable(gDisplayMonitorScrollDownButton); + buttonEnable(gDisplayMonitorScrollUpButton); + gDisplayMonitorEnabled = true; + } +} diff --git a/src/display_monitor.h b/src/display_monitor.h new file mode 100644 index 0000000..038bc75 --- /dev/null +++ b/src/display_monitor.h @@ -0,0 +1,54 @@ +#ifndef DISPLAY_MONITOR_H +#define DISPLAY_MONITOR_H + +#include "geometry.h" + +#include + +// 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) + +extern bool gDisplayMonitorInitialized; +extern const Rect gDisplayMonitorRect; +extern int gDisplayMonitorScrollDownButton; +extern int gDisplayMonitorScrollUpButton; + +extern char gDisplayMonitorLines[DISPLAY_MONITOR_LINES_CAPACITY][DISPLAY_MONITOR_LINE_LENGTH]; +extern unsigned char* gDisplayMonitorBackgroundFrmData; +extern int _max_disp; +extern bool gDisplayMonitorEnabled; +extern int _disp_curr; +extern int _intface_full_width; +extern int gDisplayMonitorLinesCapacity; +extern int _disp_start; +extern unsigned int gDisplayMonitorLastBeepTimestamp; + +int displayMonitorInit(); +int displayMonitorReset(); +void displayMonitorExit(); +void displayMonitorAddMessage(char* string); +void displayMonitorRefresh(); +void displayMonitorScrollUpOnMouseDown(int btn, int keyCode); +void displayMonitorScrollDownOnMouseDown(int btn, int keyCode); +void displayMonitorScrollUpOnMouseEnter(int btn, int keyCode); +void displayMonitorScrollDownOnMouseEnter(int btn, int keyCode); +void displayMonitorOnMouseExit(int btn, int keyCode); +void displayMonitorDisable(); +void displayMonitorEnable(); + +#endif /* DISPLAY_MONITOR_H */ diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..fb856d8 --- /dev/null +++ b/src/draw.c @@ -0,0 +1,328 @@ +#include "draw.h" + +#include "color.h" +#include "core.h" +#include "mmx.h" + +#include + +// 0x4D2FC0 +void bufferDrawLine(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 bufferDrawRect(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color) +{ + bufferDrawLine(buf, pitch, left, top, right, top, color); + bufferDrawLine(buf, pitch, left, bottom, right, bottom, color); + bufferDrawLine(buf, pitch, left, top, left, bottom, color); + bufferDrawLine(buf, pitch, right, top, right, bottom, color); +} + +// 0x4D322C +void bufferDrawRectShadowed(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int ltColor, int rbColor) +{ + bufferDrawLine(buf, pitch, left, top, right, top, ltColor); + bufferDrawLine(buf, pitch, left, bottom, right, bottom, rbColor); + bufferDrawLine(buf, pitch, left, top, left, bottom, ltColor); + bufferDrawLine(buf, pitch, right, top, right, bottom, rbColor); +} + +// 0x4D33F0 +void blitBufferToBufferStretch(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 blitBufferToBufferStretchTrans(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 blitBufferToBuffer(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch) +{ + mmxBlit(dest, destPitch, src, srcPitch, width, height); +} + +// 0x4D3704 +void blitBufferToBufferTrans(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch) +{ + mmxBlitTrans(dest, destPitch, src, srcPitch, width, height); +} + +// 0x4D387C +void bufferFill(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 << 8) + 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 bufferOutline(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; + } + } +} diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..7adcac0 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,17 @@ +#ifndef DRAW_H +#define DRAW_H + +void bufferDrawLine(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color); +void bufferDrawRect(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7); +void bufferDrawRectShadowed(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8); +void blitBufferToBufferStretch(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch); +void blitBufferToBufferStretchTrans(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch); +void blitBufferToBuffer(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); +void blitBufferToBufferTrans(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); +void bufferFill(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 bufferOutline(unsigned char* buf, int width, int height, int pitch, int a5); + +#endif /* DRAW_H */ diff --git a/src/electronic_registration.c b/src/electronic_registration.c new file mode 100644 index 0000000..b81f311 --- /dev/null +++ b/src/electronic_registration.c @@ -0,0 +1,42 @@ +#include "electronic_registration.h" + +#include "game_config.h" + +#define WIN32_LEAN_AND_MEAN +#include + +// 0x440DD0 +void runElectronicRegistration() +{ + int timesRun = 0; + configGetInt(&gGameConfig, 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); + } + } + + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, timesRun + 1); + } else { + if (timesRun == 0) { + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, 1); + } + } +} diff --git a/src/electronic_registration.h b/src/electronic_registration.h new file mode 100644 index 0000000..84aa9b4 --- /dev/null +++ b/src/electronic_registration.h @@ -0,0 +1,6 @@ +#ifndef ELECTRONIC_REGISTRATION_H +#define ELECTRONIC_REGISTRATION_H + +void runElectronicRegistration(); + +#endif /* ELECTRONIC_REGISTRATION_H */ diff --git a/src/elevator.c b/src/elevator.c new file mode 100644 index 0000000..f5b94fd --- /dev/null +++ b/src/elevator.c @@ -0,0 +1,647 @@ +#include "elevator.h" + +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "draw.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "map.h" +#include "pipboy.h" +#include "scripts.h" +#include "window_manager.h" + +#include +#include + +// 0x43E950 +const int gElevatorFrmIds[ELEVATOR_FRM_COUNT] = { + 141, // ebut_in.frm - map elevator screen + 142, // ebut_out.frm - map elevator screen + 149, // gaj000.frm - map elevator screen +}; + +// 0x43E95C +const ElevatorBackground gElevatorBackgrounds[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 +const int gElevatorLevels[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 +const ElevatorDescription gElevatorDescriptions[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 +const char gElevatorLevelLabels[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 +const char* gElevatorSoundEffects[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 +Size gElevatorFrmSizes[ELEVATOR_FRM_COUNT]; + +// 0x570A44 +int gElevatorBackgroundFrmWidth; + +// 0x570A48 +int gElevatorBackgroundFrmHeight; + +// 0x570A4C +int gElevatorPanelFrmWidth; + +// 0x570A50 +int gElevatorPanelFrmHeight; + +// 0x570A54 +int gElevatorWindow; + +// 0x570A58 +CacheEntry* gElevatorFrmHandles[ELEVATOR_FRM_COUNT]; + +// 0x570A64 +CacheEntry* gElevatorBackgroundFrmHandle; + +// 0x570A68 +CacheEntry* gElevatorPanelFrmHandle; + +// 0x570A6C +unsigned char* gElevatorWindowBuffer; + +// 0x570A70 +bool gElevatorWindowIsoWasEnabled; + +// 0x570A74 +unsigned char* gElevatorFrmData[ELEVATOR_FRM_COUNT]; + +// 0x570A80 +unsigned char* gElevatorBackgroundFrmData; + +// 0x570A84 +unsigned char* gElevatorPanelFrmData; + +// Presents elevator dialog for player to pick a desired level. +// +// 0x43EF5C +int elevatorSelectLevel(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr) +{ + if (elevator < 0 || elevator >= ELEVATOR_COUNT) { + return -1; + } + + if (elevatorWindowInit(elevator) == -1) { + return -1; + } + + const ElevatorDescription* elevatorDescription = gElevatorDescriptions[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; + } + + debugPrint("\n the start elev level %d\n", *elevationPtr); + + int v18 = (gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].width * gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].height) / 13; + float v42 = 12.0f / (float)(gElevatorLevels[elevator] - 1); + blitBufferToBuffer( + gElevatorFrmData[ELEVATOR_FRM_GAUGE] + v18 * (int)((float)(*elevationPtr) * v42), + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].width, + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].height / 13, + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].width, + gElevatorWindowBuffer + gElevatorBackgroundFrmWidth * 41 + 121, + gElevatorBackgroundFrmWidth); + windowRefresh(gElevatorWindow); + + 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 = elevatorGetLevelFromKeyCode(elevator, keyCode); + if (level != 0) { + keyCode = 500 + level - 1; + done = true; + } + } + } + + if (keyCode != KEY_ESCAPE) { + keyCode -= 500; + + if (*elevationPtr != keyCode) { + float v43 = (float)(gElevatorLevels[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; + } + + soundPlayFile(gElevatorSoundEffects[gElevatorLevels[elevator] - 2][numberOfLevelsTravelled]); + + float v41 = (float)keyCode * v42; + float v44 = (float)(*elevationPtr) * v42; + do { + unsigned int tick = _get_time(); + v44 += v43; + blitBufferToBuffer( + gElevatorFrmData[ELEVATOR_FRM_GAUGE] + v18 * (int)v44, + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].width, + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].height / 13, + gElevatorFrmSizes[ELEVATOR_FRM_GAUGE].width, + gElevatorWindowBuffer + gElevatorBackgroundFrmWidth * 41 + 121, + gElevatorBackgroundFrmWidth); + + windowRefresh(gElevatorWindow); + + while (getTicksSince(tick) < delay) { + } + } while ((v43 <= 0.0 || v44 < v41) && (v43 > 0.0 || v44 > v41)); + + coreDelayProcessingEvents(200); + } + } + + elevatorWindowFree(); + + if (keyCode != KEY_ESCAPE) { + const ElevatorDescription* description = &(elevatorDescription[keyCode]); + *mapPtr = description->map; + *elevationPtr = description->elevation; + *tilePtr = description->tile; + } + + return 0; +} + +// 0x43F324 +int elevatorWindowInit(int elevator) +{ + gElevatorWindowIsoWasEnabled = isoDisable(); + colorCycleDisable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + gameMouseObjectsHide(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + scriptsDisable(); + + int index; + for (index = 0; index < ELEVATOR_FRM_COUNT; index++) { + int fid = buildFid(6, gElevatorFrmIds[index], 0, 0, 0); + gElevatorFrmData[index] = artLockFrameDataReturningSize(fid, &(gElevatorFrmHandles[index]), &(gElevatorFrmSizes[index].width), &(gElevatorFrmSizes[index].height)); + if (gElevatorFrmData[index] == NULL) { + break; + } + } + + if (index != ELEVATOR_FRM_COUNT) { + for (int reversedIndex = index - 1; reversedIndex >= 0; reversedIndex--) { + artUnlock(gElevatorFrmHandles[reversedIndex]); + } + + if (gElevatorWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + + gElevatorPanelFrmData = ELEVATOR_BACKGROUND_NULL; + gElevatorBackgroundFrmData = ELEVATOR_BACKGROUND_NULL; + + const ElevatorBackground* elevatorBackground = &(gElevatorBackgrounds[elevator]); + bool backgroundsLoaded = true; + + int backgroundFid = buildFid(6, elevatorBackground->backgroundFrmId, 0, 0, 0); + gElevatorBackgroundFrmData = artLockFrameDataReturningSize(backgroundFid, &gElevatorBackgroundFrmHandle, &gElevatorBackgroundFrmWidth, &gElevatorBackgroundFrmHeight); + if (gElevatorBackgroundFrmData != NULL) { + if (elevatorBackground->panelFrmId != -1) { + int panelFid = buildFid(6, elevatorBackground->panelFrmId, 0, 0, 0); + gElevatorPanelFrmData = artLockFrameDataReturningSize(panelFid, &gElevatorPanelFrmHandle, &gElevatorPanelFrmWidth, &gElevatorPanelFrmHeight); + if (gElevatorPanelFrmData == NULL) { + gElevatorPanelFrmData = ELEVATOR_BACKGROUND_NULL; + backgroundsLoaded = false; + } + } + } else { + gElevatorBackgroundFrmData = ELEVATOR_BACKGROUND_NULL; + backgroundsLoaded = false; + } + + if (!backgroundsLoaded) { + if (gElevatorBackgroundFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorBackgroundFrmHandle); + } + + if (gElevatorPanelFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorPanelFrmHandle); + } + + for (int index = 0; index < ELEVATOR_FRM_COUNT; index++) { + artUnlock(gElevatorFrmHandles[index]); + } + + if (gElevatorWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + + gElevatorWindow = windowCreate( + (640 - gElevatorBackgroundFrmWidth) / 2, + (379 - gElevatorBackgroundFrmHeight) / 2, + gElevatorBackgroundFrmWidth, + gElevatorBackgroundFrmHeight, + 256, + WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (gElevatorWindow == -1) { + if (gElevatorBackgroundFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorBackgroundFrmHandle); + } + + if (gElevatorPanelFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorPanelFrmHandle); + } + + for (int index = 0; index < ELEVATOR_FRM_COUNT; index++) { + artUnlock(gElevatorFrmHandles[index]); + } + + if (gElevatorWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + + gElevatorWindowBuffer = windowGetBuffer(gElevatorWindow); + memcpy(gElevatorWindowBuffer, (unsigned char*)gElevatorBackgroundFrmData, gElevatorBackgroundFrmWidth * gElevatorBackgroundFrmHeight); + + if (gElevatorPanelFrmData != ELEVATOR_BACKGROUND_NULL) { + blitBufferToBuffer((unsigned char*)gElevatorPanelFrmData, + gElevatorPanelFrmWidth, + gElevatorPanelFrmHeight, + gElevatorPanelFrmWidth, + gElevatorWindowBuffer + gElevatorBackgroundFrmWidth * (gElevatorBackgroundFrmHeight - gElevatorPanelFrmHeight), + gElevatorBackgroundFrmWidth); + } + + int y = 40; + for (int level = 0; level < gElevatorLevels[elevator]; level++) { + int btn = buttonCreate(gElevatorWindow, + 13, + y, + gElevatorFrmSizes[ELEVATOR_FRM_BUTTON_DOWN].width, + gElevatorFrmSizes[ELEVATOR_FRM_BUTTON_DOWN].height, + -1, + -1, + -1, + 500 + level, + gElevatorFrmData[ELEVATOR_FRM_BUTTON_UP], + gElevatorFrmData[ELEVATOR_FRM_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, NULL); + } + y += 60; + } + + return 0; +} + +// 0x43F6D0 +void elevatorWindowFree() +{ + windowDestroy(gElevatorWindow); + + if (gElevatorBackgroundFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorBackgroundFrmHandle); + } + + if (gElevatorPanelFrmData != ELEVATOR_BACKGROUND_NULL) { + artUnlock(gElevatorPanelFrmHandle); + } + + for (int index = 0; index < ELEVATOR_FRM_COUNT; index++) { + artUnlock(gElevatorFrmHandles[index]); + } + + scriptsEnable(); + + if (gElevatorWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); +} + +// 0x43F73C +int elevatorGetLevelFromKeyCode(int elevator, int keyCode) +{ + // TODO: Check if result is really unused? + toupper(keyCode); + + for (int index = 0; index < ELEVATOR_LEVEL_MAX; index++) { + char c = gElevatorLevelLabels[elevator][index]; + if (c == '\0') { + break; + } + + if (c == (char)(keyCode & 0xFF)) { + return index + 1; + } + } + return 0; +} diff --git a/src/elevator.h b/src/elevator.h new file mode 100644 index 0000000..4b5d467 --- /dev/null +++ b/src/elevator.h @@ -0,0 +1,79 @@ +#ifndef ELEVATOR_H +#define ELEVATOR_H + +#include "art.h" +#include "geometry.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 - [gElevatorBackgroundFrmData] and [gElevatorPanelFrmData]. +// 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)) + +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_COUNT = 24, +} Elevator; + +typedef enum ElevatorFrm { + ELEVATOR_FRM_BUTTON_DOWN, + ELEVATOR_FRM_BUTTON_UP, + ELEVATOR_FRM_GAUGE, + ELEVATOR_FRM_COUNT, +} ElevatorFrm; + +typedef struct ElevatorBackground { + int backgroundFrmId; + int panelFrmId; +} ElevatorBackground; + +typedef struct ElevatorDescription { + int map; + int elevation; + int tile; +} ElevatorDescription; + +extern const int gElevatorFrmIds[ELEVATOR_FRM_COUNT]; +extern const ElevatorBackground gElevatorBackgrounds[ELEVATOR_COUNT]; +extern const int gElevatorLevels[ELEVATOR_COUNT]; +extern const ElevatorDescription gElevatorDescriptions[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX]; +extern const char gElevatorLevelLabels[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX]; + +extern const char* gElevatorSoundEffects[ELEVATOR_LEVEL_MAX - 1][ELEVATOR_LEVEL_MAX]; + +extern Size gElevatorFrmSizes[ELEVATOR_FRM_COUNT]; +extern int gElevatorBackgroundFrmWidth; +extern int gElevatorBackgroundFrmHeight; +extern int gElevatorPanelFrmWidth; +extern int gElevatorPanelFrmHeight; +extern int gElevatorWindow; +extern CacheEntry* gElevatorFrmHandles[ELEVATOR_FRM_COUNT]; +extern CacheEntry* gElevatorBackgroundFrmHandle; +extern CacheEntry* gElevatorPanelFrmHandle; +extern unsigned char* gElevatorWindowBuffer; +extern bool gElevatorWindowIsoWasEnabled; +extern unsigned char* gElevatorFrmData[ELEVATOR_FRM_COUNT]; +extern unsigned char* gElevatorBackgroundFrmData; +extern unsigned char* gElevatorPanelFrmData; + +int elevatorSelectLevel(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr); +int elevatorWindowInit(int elevator); +void elevatorWindowFree(); +int elevatorGetLevelFromKeyCode(int elevator, int keyCode); + +#endif /* ELEVATOR_H */ diff --git a/src/endgame.c b/src/endgame.c new file mode 100644 index 0000000..b8a378c --- /dev/null +++ b/src/endgame.c @@ -0,0 +1,1153 @@ +#include "endgame.h" + +#include "color.h" +#include "core.h" +#include "credits.h" +#include "cycle.h" +#include "db.h" +#include "dbox.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "palette.h" +#include "pipboy.h" +#include "random.h" +#include "stat.h" +#include "text_font.h" +#include "window_manager.h" +#include "word_wrap.h" +#include "world_map.h" + +#include +#include +#include +#include + +// The maximum number of subtitle lines per slide. +#define ENDGAME_ENDING_MAX_SUBTITLES (50) + +// 0x50B00C +char _aEnglish_2[] = ENGLISH; + +// The number of lines in current subtitles file. +// +// It's used as a length for two arrays: +// - [gEndgameEndingSubtitles] +// - [gEndgameEndingSubtitlesTimings] +// +// This value does not exceed [ENDGAME_ENDING_SUBTITLES_CAPACITY]. +// +// 0x518668 +int gEndgameEndingSubtitlesLength = 0; + +// The number of characters in current subtitles file. +// +// This value is used to determine +// +// 0x51866C +int gEndgameEndingSubtitlesCharactersCount = 0; + +// 0x518670 +int gEndgameEndingSubtitlesCurrentLine = 0; + +// 0x518674 +int _endgame_maybe_done = 0; + +// enddeath.txt +// +// 0x518678 +EndgameDeathEnding* gEndgameDeathEndings = NULL; + +// The number of death endings in [gEndgameDeathEndings] array. +// +// 0x51867C +int gEndgameDeathEndingsLength = 0; + +// Base file name for death ending. +// +// This value does not include extension. +// +// 0x570A90 +char gEndgameDeathEndingFileName[40]; + +// This flag denotes whether speech sound was successfully loaded for +// the current slide. +// +// 0x570AB8 +bool gEndgameEndingVoiceOverSpeechLoaded; + +// 0x570ABC +char gEndgameEndingSubtitlesLocalizedPath[MAX_PATH]; + +// The flag used to denote voice over speech for current slide has ended. +// +// 0x570BC0 +bool gEndgameEndingSpeechEnded; + +// endgame.txt +// +// 0x570BC4 +EndgameEnding* gEndgameEndings; + +// The array of text lines in current subtitles file. +// +// The length is specified in [gEndgameEndingSubtitlesLength]. It's capacity +// is [ENDGAME_ENDING_SUBTITLES_CAPACITY]. +// +// 0x570BC8 +char** gEndgameEndingSubtitles; + +// 0x570BCC +bool gEndgameEndingSubtitlesEnabled; + +// The flag used to denote voice over subtitles for current slide has ended. +// +// 0x570BD0 +bool gEndgameEndingSubtitlesEnded; + +// 0x570BD4 +bool _endgame_map_enabled; + +// 0x570BD8 +bool _endgame_mouse_state; + +// The number of endings in [gEndgameEndings] array. +// +// 0x570BDC +int gEndgameEndingsLength = 0; + +// This flag denotes whether subtitles was successfully loaded for +// the current slide. +// +// 0x570BE0 +bool gEndgameEndingVoiceOverSubtitlesLoaded; + +// Reference time is a timestamp when subtitle is first displayed. +// +// This value is used together with [gEndgameEndingSubtitlesTimings] array to +// determine when next line needs to be displayed. +// +// 0x570BE4 +unsigned int gEndgameEndingSubtitlesReferenceTime; + +// The array of timings for each line in current subtitles file. +// +// The length is specified in [gEndgameEndingSubtitlesLength]. It's capacity +// is [ENDGAME_ENDING_SUBTITLES_CAPACITY]. +// +// 0x570BE8 +unsigned int* gEndgameEndingSubtitlesTimings; + +// Font that was current before endgame slideshow window was created. +// +// 0x570BEC +int gEndgameEndingSlideshowOldFont; + +// 0x570BF0 +unsigned char* gEndgameEndingSlideshowWindowBuffer; + +// 0x570BF4 +int gEndgameEndingSlideshowWindow; + +// 0x43F788 +void endgamePlaySlideshow() +{ + if (endgameEndingSlideshowWindowInit() == -1) { + return; + } + + for (int index = 0; index < gEndgameEndingsLength; index++) { + EndgameEnding* ending = &(gEndgameEndings[index]); + int value = gameGetGlobalVar(ending->gvar); + if (value == ending->value) { + if (ending->art_num == 327) { + endgameEndingRenderPanningScene(ending->direction, ending->voiceOverBaseName); + } else { + int fid = buildFid(6, ending->art_num, 0, 0, 0); + endgameEndingRenderStaticScene(fid, ending->voiceOverBaseName); + } + } + } + + endgameEndingSlideshowWindowFree(); +} + +// 0x43F810 +void endgamePlayMovie() +{ + backgroundSoundDelete(); + isoDisable(); + paletteFadeTo(gPaletteBlack); + _endgame_maybe_done = 0; + tickersAdd(_endgame_movie_bk_process); + backgroundSoundSetEndCallback(_endgame_movie_callback); + backgroundSoundLoad("akiss", 12, 14, 15); + coreDelayProcessingEvents(3000); + + // NOTE: Result is ignored. I guess there was some kind of switch for male + // vs. female ending, but it was not implemented. + critterGetStat(gDude, STAT_GENDER); + + creditsOpen("credits.txt", -1, false); + backgroundSoundDelete(); + backgroundSoundSetEndCallback(NULL); + tickersRemove(_endgame_movie_bk_process); + backgroundSoundDelete(); + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + isoEnable(); + endgameEndingHandleContinuePlaying(); +} + +// 0x43F8C4 +int endgameEndingHandleContinuePlaying() +{ + bool isoWasEnabled = isoDisable(); + + bool gameMouseWasVisible; + if (isoWasEnabled) { + gameMouseWasVisible = gameMouseObjectsIsVisible(); + } else { + gameMouseWasVisible = false; + } + + if (gameMouseWasVisible) { + gameMouseObjectsHide(); + } + + bool oldCursorIsHidden = cursorIsHidden(); + if (oldCursorIsHidden) { + mouseShowCursor(); + } + + int oldCursor = gameMouseGetCursor(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + int rc; + + MessageListItem messageListItem; + messageListItem.num = 30; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + rc = showDialogBox(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; + } + + gameMouseSetCursor(oldCursor); + if (oldCursorIsHidden) { + mouseHideCursor(); + } + + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + if (isoWasEnabled) { + isoEnable(); + } + + return rc; +} + +// 0x43FBDC +void endgameEndingRenderPanningScene(int direction, const char* narratorFileName) +{ + int fid = buildFid(6, 327, 0, 0, 0); + + CacheEntry* backgroundHandle; + Art* background = artLock(fid, &backgroundHandle); + if (background != NULL) { + int width = artGetWidth(background, 0, 0); + int height = artGetHeight(background, 0, 0); + unsigned char* backgroundData = artGetFrameData(background, 0, 0); + bufferFill(gEndgameEndingSlideshowWindowBuffer, 640, 480, 640, _colorTable[0]); + endgameEndingLoadPalette(6, 327); + + unsigned char palette[768]; + memcpy(palette, _cmap, 768); + + paletteSetEntries(gPaletteBlack); + endgameEndingVoiceOverInit(narratorFileName); + + // TODO: Unclear math. + int v8 = width - 640; + int v32 = v8 / 4; + unsigned int v9 = 16 * v8 / v8; + unsigned int v9_ = 16 * v8; + + if (gEndgameEndingVoiceOverSpeechLoaded) { + unsigned int v10 = 1000 * speechGetDuration(); + 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; + } + + tickersDisable(); + + bool subtitlesLoaded = false; + + unsigned int since = 0; + while (start != end) { + int v12 = 640 - v32; + + // TODO: Complex math, setup scene in debugger. + if (getTicksSince(since) >= v9) { + blitBufferToBuffer(backgroundData + start, 640, 480, width, gEndgameEndingSlideshowWindowBuffer, 640); + + if (subtitlesLoaded) { + endgameEndingRefreshSubtitles(); + } + + windowRefresh(gEndgameEndingSlideshowWindow); + + 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); + } + paletteSetEntries(darkenedPalette); + } + + start += direction; + + if (direction == 1 && (start == v32)) { + // NOTE: Uninline. + endgameEndingVoiceOverReset(); + subtitlesLoaded = true; + } else if (direction == -1 && (start == v12)) { + // NOTE: Uninline. + endgameEndingVoiceOverReset(); + subtitlesLoaded = true; + } + } + + soundContinueAll(); + + if (_get_input() != -1) { + // NOTE: Uninline. + endgameEndingVoiceOverFree(); + break; + } + } + + tickersEnable(); + artUnlock(backgroundHandle); + + paletteFadeTo(gPaletteBlack); + bufferFill(gEndgameEndingSlideshowWindowBuffer, 640, 480, 640, _colorTable[0]); + windowRefresh(gEndgameEndingSlideshowWindow); + } + + while (mouseGetEvent() != 0) { + _get_input(); + } +} + +// 0x440004 +void endgameEndingRenderStaticScene(int fid, const char* narratorFileName) +{ + CacheEntry* backgroundHandle; + Art* background = artLock(fid, &backgroundHandle); + if (background == NULL) { + return; + } + + unsigned char* backgroundData = artGetFrameData(background, 0, 0); + if (backgroundData != NULL) { + blitBufferToBuffer(backgroundData, 640, 480, 640, gEndgameEndingSlideshowWindowBuffer, 640); + windowRefresh(gEndgameEndingSlideshowWindow); + + endgameEndingLoadPalette((fid & 0xF000000) >> 24, fid & 0xFFF); + + endgameEndingVoiceOverInit(narratorFileName); + + unsigned int delay; + if (gEndgameEndingVoiceOverSubtitlesLoaded || gEndgameEndingVoiceOverSpeechLoaded) { + delay = UINT_MAX; + } else { + delay = 3000; + } + + paletteFadeTo(_cmap); + + coreDelayProcessingEvents(500); + + // NOTE: Uninline. + endgameEndingVoiceOverReset(); + + unsigned int referenceTime = _get_time(); + tickersDisable(); + + int keyCode; + while (true) { + keyCode = _get_input(); + if (keyCode != -1) { + break; + } + + if (gEndgameEndingSpeechEnded) { + break; + } + + if (gEndgameEndingSubtitlesEnded) { + break; + } + + if (getTicksSince(referenceTime) > delay) { + break; + } + + blitBufferToBuffer(backgroundData, 640, 480, 640, gEndgameEndingSlideshowWindowBuffer, 640); + endgameEndingRefreshSubtitles(); + windowRefresh(gEndgameEndingSlideshowWindow); + soundContinueAll(); + } + + tickersEnable(); + speechDelete(); + endgameEndingSubtitlesFree(); + + gEndgameEndingVoiceOverSpeechLoaded = false; + gEndgameEndingVoiceOverSubtitlesLoaded = false; + + if (keyCode == -1) { + coreDelayProcessingEvents(500); + } + + paletteFadeTo(gPaletteBlack); + + while (mouseGetEvent() != 0) { + _get_input(); + } + } + + artUnlock(backgroundHandle); +} + +// 0x43F99C +int endgameEndingSlideshowWindowInit() +{ + if (endgameEndingInit() != 0) { + return -1; + } + + backgroundSoundDelete(); + + _endgame_map_enabled = isoDisable(); + + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + bool oldCursorIsHidden = cursorIsHidden(); + _endgame_mouse_state = oldCursorIsHidden == 0; + + if (oldCursorIsHidden) { + mouseShowCursor(); + } + + gEndgameEndingSlideshowOldFont = fontGetCurrent(); + fontSetCurrent(101); + + paletteFadeTo(gPaletteBlack); + + gEndgameEndingSlideshowWindow = windowCreate(0, 0, 640, 480, _colorTable[0], 4); + if (gEndgameEndingSlideshowWindow == -1) { + return -1; + } + + gEndgameEndingSlideshowWindowBuffer = windowGetBuffer(gEndgameEndingSlideshowWindow); + if (gEndgameEndingSlideshowWindowBuffer == NULL) { + return -1; + } + + colorCycleDisable(); + + speechSetEndCallback(_endgame_voiceover_callback); + + gEndgameEndingSubtitlesEnabled = false; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &gEndgameEndingSubtitlesEnabled); + if (!gEndgameEndingSubtitlesEnabled) { + return 0; + } + + char* language; + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { + gEndgameEndingSubtitlesEnabled = false; + return 0; + } + + sprintf(gEndgameEndingSubtitlesLocalizedPath, "text\\%s\\cuts\\", language); + + gEndgameEndingSubtitles = internal_malloc(sizeof(*gEndgameEndingSubtitles) * ENDGAME_ENDING_MAX_SUBTITLES); + if (gEndgameEndingSubtitles == NULL) { + gEndgameEndingSubtitlesEnabled = false; + return 0; + } + + for (int index = 0; index < ENDGAME_ENDING_MAX_SUBTITLES; index++) { + gEndgameEndingSubtitles[index] = NULL; + } + + gEndgameEndingSubtitlesTimings = internal_malloc(sizeof(*gEndgameEndingSubtitlesTimings) * ENDGAME_ENDING_MAX_SUBTITLES); + if (gEndgameEndingSubtitlesTimings == NULL) { + internal_free(gEndgameEndingSubtitles); + gEndgameEndingSubtitlesEnabled = false; + return 0; + } + + return 0; +} + +// 0x43FB28 +void endgameEndingSlideshowWindowFree() +{ + if (gEndgameEndingSubtitlesEnabled) { + endgameEndingSubtitlesFree(); + + internal_free(gEndgameEndingSubtitlesTimings); + internal_free(gEndgameEndingSubtitles); + + gEndgameEndingSubtitles = NULL; + gEndgameEndingSubtitlesEnabled = false; + } + + // NOTE: Uninline. + endgameEndingFree(); + + fontSetCurrent(gEndgameEndingSlideshowOldFont); + + speechSetEndCallback(NULL); + windowDestroy(gEndgameEndingSlideshowWindow); + + if (!_endgame_mouse_state) { + mouseHideCursor(); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + + colorCycleEnable(); + + if (_endgame_map_enabled) { + isoEnable(); + } +} + +// 0x4401A0 +void endgameEndingVoiceOverInit(const char* fileBaseName) +{ + char path[MAX_PATH]; + + // NOTE: Uninline. + endgameEndingVoiceOverFree(); + + gEndgameEndingVoiceOverSpeechLoaded = false; + gEndgameEndingVoiceOverSubtitlesLoaded = false; + + // Build speech file path. + sprintf(path, "%s%s", "narrator\\", fileBaseName); + + if (speechLoad(path, 10, 14, 15) != -1) { + gEndgameEndingVoiceOverSpeechLoaded = true; + } + + if (gEndgameEndingSubtitlesEnabled) { + // Build subtitles file path. + sprintf(path, "%s%s.txt", gEndgameEndingSubtitlesLocalizedPath, fileBaseName); + + if (endgameEndingSubtitlesLoad(path) != 0) { + return; + } + + double durationPerCharacter; + if (gEndgameEndingVoiceOverSpeechLoaded) { + durationPerCharacter = (double)speechGetDuration() / (double)gEndgameEndingSubtitlesCharactersCount; + } else { + durationPerCharacter = 0.08; + } + + unsigned int timing = 0; + for (int index = 0; index < gEndgameEndingSubtitlesLength; index++) { + double charactersCount = strlen(gEndgameEndingSubtitles[index]); + // NOTE: There is floating point math at 0x4402E6 used to add + // timing. + timing += (unsigned int)trunc(charactersCount * durationPerCharacter * 1000.0); + gEndgameEndingSubtitlesTimings[index] = timing; + } + + gEndgameEndingVoiceOverSubtitlesLoaded = true; + } +} + +// NOTE: This function was inlined at every call site. +// +// 0x440324 +void endgameEndingVoiceOverReset() +{ + gEndgameEndingSubtitlesEnded = false; + gEndgameEndingSpeechEnded = false; + + if (gEndgameEndingVoiceOverSpeechLoaded) { + _gsound_speech_play_preloaded(); + } + + if (gEndgameEndingVoiceOverSubtitlesLoaded) { + gEndgameEndingSubtitlesReferenceTime = _get_time(); + } +} + +// NOTE: This function was inlined at every call site. +// +// 0x44035C +void endgameEndingVoiceOverFree() +{ + speechDelete(); + endgameEndingSubtitlesFree(); + gEndgameEndingVoiceOverSpeechLoaded = false; + gEndgameEndingVoiceOverSubtitlesLoaded = false; +} + +// 0x440378 +void endgameEndingLoadPalette(int type, int id) +{ + char fileName[13]; + if (artCopyFileName(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); + colorPaletteLoad(path); + } +} + +// 0x4403F0 +void _endgame_voiceover_callback() +{ + gEndgameEndingSpeechEnded = true; +} + +// Loads subtitles file. +// +// 0x4403FC +int endgameEndingSubtitlesLoad(const char* filePath) +{ + endgameEndingSubtitlesFree(); + + File* stream = fileOpen(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 (fileReadString(string, sizeof(string), stream)) { + char* pch; + + // Find and clamp string at EOL. + pch = string; + while (*pch != '\0' && *pch != '\n') { + pch++; + } + + if (*pch != '\0') { + *pch = '\0'; + } + + // Find separator. The value before separator is ignored (as opposed to + // movie subtitles, where the value before separator is a timing). + pch = string; + while (*pch != '\0' && *pch != ':') { + pch++; + } + + if (*pch != '\0') { + if (gEndgameEndingSubtitlesLength < ENDGAME_ENDING_MAX_SUBTITLES) { + gEndgameEndingSubtitles[gEndgameEndingSubtitlesLength] = internal_strdup(pch + 1); + gEndgameEndingSubtitlesLength++; + gEndgameEndingSubtitlesCharactersCount += strlen(pch + 1); + } + } + } + + fileClose(stream); + + return 0; +} + +// Refreshes subtitles. +// +// 0x4404EC +void endgameEndingRefreshSubtitles() +{ + if (gEndgameEndingSubtitlesLength <= gEndgameEndingSubtitlesCurrentLine) { + if (gEndgameEndingVoiceOverSubtitlesLoaded) { + gEndgameEndingSubtitlesEnded = true; + } + return; + } + + if (getTicksSince(gEndgameEndingSubtitlesReferenceTime) > gEndgameEndingSubtitlesTimings[gEndgameEndingSubtitlesCurrentLine]) { + gEndgameEndingSubtitlesCurrentLine++; + return; + } + + char* text = gEndgameEndingSubtitles[gEndgameEndingSubtitlesCurrentLine]; + if (text == NULL) { + return; + } + + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(text, 540, beginnings, &count) != 0) { + return; + } + + int height = fontGetLineHeight(); + 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 = fontGetStringWidth(beginning); + int x = (640 - width) / 2; + bufferFill(gEndgameEndingSlideshowWindowBuffer + 640 * y + x, width, height, 640, _colorTable[0]); + fontDrawText(gEndgameEndingSlideshowWindowBuffer + 640 * y + x, beginning, width, 640, _colorTable[32767]); + + *ending = c; + + y += height; + } +} + +// 0x4406CC +void endgameEndingSubtitlesFree() +{ + for (int index = 0; index < gEndgameEndingSubtitlesLength; index++) { + if (gEndgameEndingSubtitles[index] != NULL) { + internal_free(gEndgameEndingSubtitles[index]); + gEndgameEndingSubtitles[index] = NULL; + } + } + + gEndgameEndingSubtitlesCurrentLine = 0; + gEndgameEndingSubtitlesCharactersCount = 0; + gEndgameEndingSubtitlesLength = 0; +} + +// 0x440728 +void _endgame_movie_callback() +{ + _endgame_maybe_done = 1; +} + +// 0x440734 +void _endgame_movie_bk_process() +{ + if (_endgame_maybe_done) { + backgroundSoundLoad("10labone", 11, 14, 16); + backgroundSoundSetEndCallback(NULL); + tickersRemove(_endgame_movie_bk_process); + } +} + +// 0x440770 +int endgameEndingInit() +{ + File* stream; + char str[256]; + char *ch, *tok; + const char* delim = " \t,"; + EndgameEnding entry; + EndgameEnding* entries; + int narrator_file_len; + + if (gEndgameEndings != NULL) { + internal_free(gEndgameEndings); + gEndgameEndings = NULL; + } + + gEndgameEndingsLength = 0; + + stream = fileOpen("data\\endgame.txt", "rt"); + if (stream == NULL) { + return -1; + } + + while (fileReadString(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 = internal_realloc(gEndgameEndings, sizeof(*entries) * (gEndgameEndingsLength + 1)); + if (entries == NULL) { + goto err; + } + + memcpy(&(entries[gEndgameEndingsLength]), &entry, sizeof(entry)); + + gEndgameEndings = entries; + gEndgameEndingsLength++; + } + + fileClose(stream); + + return 0; + +err: + + fileClose(stream); + + return -1; +} + +// NOTE: There are no references to this function. It was inlined. +// +// 0x44095C +void endgameEndingFree() +{ + if (gEndgameEndings != NULL) { + internal_free(gEndgameEndings); + gEndgameEndings = NULL; + } + + gEndgameEndingsLength = 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(gEndgameDeathEndingFileName, "narrator\\nar_5"); + + stream = fileOpen("data\\enddeath.txt", "rt"); + if (stream == NULL) { + return -1; + } + + while (fileReadString(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 = internal_realloc(gEndgameDeathEndings, sizeof(*entries) * (gEndgameDeathEndingsLength + 1)); + if (entries == NULL) { + goto err; + } + + memcpy(&(entries[gEndgameDeathEndingsLength]), &entry, sizeof(entry)); + + gEndgameDeathEndings = entries; + gEndgameDeathEndingsLength++; + } + + fileClose(stream); + + return 0; + +err: + + fileClose(stream); + + return -1; +} + +// 0x440BA8 +int endgameDeathEndingExit() +{ + if (gEndgameDeathEndings != NULL) { + internal_free(gEndgameDeathEndings); + gEndgameDeathEndings = NULL; + + gEndgameDeathEndingsLength = 0; + } + + return 0; +} + +// endgameSetupDeathEnding +// 0x440BD0 +void endgameSetupDeathEnding(int reason) +{ + if (!gEndgameDeathEndingsLength) { + debugPrint("\nError: endgameSetupDeathEnding: No endgame death info!"); + return; + } + + // Build death ending file path. + strcpy(gEndgameDeathEndingFileName, "narrator\\"); + + int percentage = 0; + endgameDeathEndingValidate(&percentage); + + int selectedEnding = 0; + bool specialEndingSelected = false; + + switch (reason) { + case ENDGAME_DEATH_ENDING_REASON_DEATH: + if (gameGetGlobalVar(GVAR_MODOC_SHITTY_DEATH) != 0) { + selectedEnding = 12; + specialEndingSelected = true; + } + break; + case ENDGAME_DEATH_ENDING_REASON_TIMEOUT: + gameMoviePlay(MOVIE_TIMEOUT, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC); + break; + } + + if (!specialEndingSelected) { + int chance = randomBetween(0, percentage); + + int accum = 0; + for (int index = 0; index < gEndgameDeathEndingsLength; index++) { + EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[index]); + + if (deathEnding->enabled) { + accum += deathEnding->percentage; + if (accum >= chance) { + break; + } + selectedEnding++; + } + } + } + + EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[selectedEnding]); + + strcat(gEndgameDeathEndingFileName, deathEnding->voiceOverBaseName); + + debugPrint("\nendgameSetupDeathEnding: Death Filename Picked: %s", gEndgameDeathEndingFileName); +} + +// Validates conditions imposed by death endings. +// +// Upon return [percentage] is set as a sum of all valid endings' percentages. +// Always returns 0. +// +// 0x440CF4 +int endgameDeathEndingValidate(int* percentage) +{ + *percentage = 0; + + for (int index = 0; index < gEndgameDeathEndingsLength; index++) { + EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[index]); + + deathEnding->enabled = false; + + if (deathEnding->gvar != -1) { + if (gameGetGlobalVar(deathEnding->gvar) >= deathEnding->value) { + continue; + } + } + + if (deathEnding->worldAreaKnown != -1) { + if (!_wmAreaIsKnown(deathEnding->worldAreaKnown)) { + continue; + } + } + + if (deathEnding->worldAreaNotKnown != -1) { + if (_wmAreaIsKnown(deathEnding->worldAreaNotKnown)) { + continue; + } + } + + if (pcGetStat(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* endgameDeathEndingGetFileName() +{ + if (gEndgameDeathEndingsLength == 0) { + debugPrint("\nError: endgameSetupDeathEnding: No endgame death info!"); + strcpy(gEndgameDeathEndingFileName, "narrator\\nar_4"); + } + + debugPrint("\nendgameSetupDeathEnding: Death Filename: %s", gEndgameDeathEndingFileName); + + return gEndgameDeathEndingFileName; +} diff --git a/src/endgame.h b/src/endgame.h new file mode 100644 index 0000000..eb841cd --- /dev/null +++ b/src/endgame.h @@ -0,0 +1,92 @@ +#ifndef ENDGAME_H +#define ENDGAME_H + +// Provides [MAX_PATH]. +#define WIN32_LEAN_AND_MEAN +#include + +#include + +typedef enum EndgameDeathEndingReason { + // Dude died. + ENDGAME_DEATH_ENDING_REASON_DEATH = 0, + + // 13 years passed. + ENDGAME_DEATH_ENDING_REASON_TIMEOUT = 2, +} EndgameDeathEndingReason; + +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; + +extern char _aEnglish_2[]; + +extern int gEndgameEndingSubtitlesLength; +extern int gEndgameEndingSubtitlesCharactersCount; +extern int gEndgameEndingSubtitlesCurrentLine; +extern int _endgame_maybe_done; +extern EndgameDeathEnding* gEndgameDeathEndings; +extern int gEndgameDeathEndingsLength; + +extern char gEndgameDeathEndingFileName[40]; +extern bool gEndgameEndingVoiceOverSpeechLoaded; +extern char gEndgameEndingSubtitlesLocalizedPath[MAX_PATH]; +extern bool gEndgameEndingSpeechEnded; +extern EndgameEnding* gEndgameEndings; +extern char** gEndgameEndingSubtitles; +extern bool gEndgameEndingSubtitlesEnabled; +extern bool gEndgameEndingSubtitlesEnded; +extern bool _endgame_map_enabled; +extern bool _endgame_mouse_state; +extern int gEndgameEndingsLength; +extern bool gEndgameEndingVoiceOverSubtitlesLoaded; +extern unsigned int gEndgameEndingSubtitlesReferenceTime; +extern unsigned int* gEndgameEndingSubtitlesTimings; +extern int gEndgameEndingSlideshowOldFont; +extern unsigned char* gEndgameEndingSlideshowWindowBuffer; +extern int gEndgameEndingSlideshowWindow; + +void endgamePlaySlideshow(); +void endgamePlayMovie(); +void endgameEndingRenderPanningScene(int direction, const char* narratorFileName); +void endgameEndingRenderStaticScene(int fid, const char* narratorFileName); +int endgameEndingHandleContinuePlaying(); +int endgameEndingSlideshowWindowInit(); +void endgameEndingSlideshowWindowFree(); +void endgameEndingVoiceOverInit(const char* fname); +void endgameEndingVoiceOverReset(); +void endgameEndingVoiceOverFree(); +void endgameEndingLoadPalette(int type, int id); +void _endgame_voiceover_callback(); +int endgameEndingSubtitlesLoad(const char* filePath); +void endgameEndingRefreshSubtitles(); +void endgameEndingSubtitlesFree(); +void _endgame_movie_callback(); +void _endgame_movie_bk_process(); +int endgameEndingInit(); +void endgameEndingFree(); +int endgameDeathEndingInit(); +int endgameDeathEndingExit(); +void endgameSetupDeathEnding(int reason); +int endgameDeathEndingValidate(int* percentage); +char* endgameDeathEndingGetFileName(); + +#endif /* ENDGAME_H */ diff --git a/src/export.c b/src/export.c new file mode 100644 index 0000000..517daec --- /dev/null +++ b/src/export.c @@ -0,0 +1,325 @@ +#include "export.h" + +#include "interpreter_lib.h" +#include "memory_manager.h" + +#include +#include + +// 0x570C00 +ExternalProcedure gExternalProcedures[1013]; + +// 0x57BA1C +ExternalVariable gExternalVariables[1013]; + +// NOTE: Inlined. +// +// 0x440F10 +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 +ExternalProcedure* externalProcedureFind(const char* identifier) +{ + // NOTE: Uninline. + unsigned int v1 = _hashName(identifier); + unsigned int v2 = v1; + + ExternalProcedure* externalProcedure = &(gExternalProcedures[v1]); + if (externalProcedure->program != NULL) { + if (stricmp(externalProcedure->name, identifier) == 0) { + return externalProcedure; + } + } + + do { + v1 += 7; + if (v1 >= 1013) { + v1 -= 1013; + } + + externalProcedure = &(gExternalProcedures[v1]); + if (externalProcedure->program != NULL) { + if (stricmp(externalProcedure->name, identifier) == 0) { + return externalProcedure; + } + } + } while (v1 != v2); + + return NULL; +} + +// 0x441018 +ExternalProcedure* externalProcedureAdd(const char* identifier) +{ + // NOTE: Uninline. + unsigned int v1 = _hashName(identifier); + unsigned int a2 = v1; + + ExternalProcedure* externalProcedure = &(gExternalProcedures[v1]); + if (externalProcedure->name[0] == '\0') { + return externalProcedure; + } + + do { + v1 += 7; + if (v1 >= 1013) { + v1 -= 1013; + } + + externalProcedure = &(gExternalProcedures[v1]); + if (externalProcedure->name[0] == '\0') { + return externalProcedure; + } + } while (v1 != a2); + + return NULL; +} + +// 0x4410AC +ExternalVariable* externalVariableFind(const char* identifier) +{ + // NOTE: Uninline. + unsigned int v1 = _hashName(identifier); + unsigned int v2 = v1; + + ExternalVariable* exportedVariable = &(gExternalVariables[v1]); + if (stricmp(exportedVariable->name, identifier) == 0) { + return exportedVariable; + } + + do { + exportedVariable = &(gExternalVariables[v1]); + if (exportedVariable->name[0] == '\0') { + break; + } + + v1 += 7; + if (v1 >= 1013) { + v1 -= 1013; + } + + exportedVariable = &(gExternalVariables[v1]); + if (stricmp(exportedVariable->name, identifier) == 0) { + return exportedVariable; + } + } while (v1 != v2); + + return NULL; +} + +// 0x44118C +ExternalVariable* externalVariableAdd(const char* identifier) +{ + // NOTE: Uninline. + unsigned int v1 = _hashName(identifier); + unsigned int v2 = v1; + + ExternalVariable* exportedVariable = &(gExternalVariables[v1]); + if (exportedVariable->name[0] == '\0') { + return exportedVariable; + } + + do { + v1 += 7; + if (v1 >= 1013) { + v1 -= 1013; + } + + exportedVariable = &(gExternalVariables[v1]); + if (exportedVariable->name[0] == '\0') { + return exportedVariable; + } + } while (v1 != v2); + + return NULL; +} + +// 0x44127C +int externalVariableSetValue(Program* program, const char* name, opcode_t opcode, int data) +{ + ExternalVariable* exportedVariable = externalVariableFind(name); + if (exportedVariable == NULL) { + return 1; + } + + if ((exportedVariable->type & 0xF7FF) == VALUE_TYPE_STRING) { + internal_free_safe(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 169 + } + + if ((opcode & 0xF7FF) == VALUE_TYPE_STRING) { + if (program != NULL) { + const char* stringValue = programGetString(program, opcode, data); + exportedVariable->type = VALUE_TYPE_DYNAMIC_STRING; + + exportedVariable->stringValue = internal_malloc_safe(strlen(stringValue) + 1, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 175 + strcpy(exportedVariable->stringValue, stringValue); + } + } else { + exportedVariable->value = data; + exportedVariable->type = opcode; + } + + return 0; +} + +// 0x4413D4 +int externalVariableGetValue(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr) +{ + ExternalVariable* exportedVariable = externalVariableFind(name); + if (exportedVariable == NULL) { + return 1; + } + + *opcodePtr = exportedVariable->type; + + if ((exportedVariable->type & 0xF7FF) == VALUE_TYPE_STRING) { + *dataPtr = programPushString(program, exportedVariable->stringValue); + } else { + *dataPtr = exportedVariable->value; + } + + return 0; +} + +// 0x4414B8 +int externalVariableCreate(Program* program, const char* identifier) +{ + const char* programName = program->name; + ExternalVariable* exportedVariable = externalVariableFind(identifier); + + if (exportedVariable != NULL) { + if (stricmp(exportedVariable->programName, programName) != 0) { + return 1; + } + + if ((exportedVariable->type & 0xF7FF) == VALUE_TYPE_STRING) { + internal_free_safe(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 234 + } + } else { + exportedVariable = externalVariableAdd(identifier); + if (exportedVariable == NULL) { + return 1; + } + + strncpy(exportedVariable->name, identifier, 31); + + exportedVariable->programName = internal_malloc_safe(strlen(programName) + 1, __FILE__, __LINE__); // // "..\\int\\EXPORT.C", 243 + strcpy(exportedVariable->programName, programName); + } + + exportedVariable->type = VALUE_TYPE_INT; + exportedVariable->value = 0; + + return 0; +} + +// 0x4414FC +void _removeProgramReferences(Program* program) +{ + for (int index = 0; index < 1013; index++) { + ExternalProcedure* externalProcedure = &(gExternalProcedures[index]); + if (externalProcedure->program == program) { + externalProcedure->name[0] = '\0'; + externalProcedure->program = NULL; + } + } +} + +// 0x44152C +void _initExport() +{ + _interpretRegisterProgramDeleteCallback(_removeProgramReferences); +} + +// 0x441538 +void externalVariablesClear() +{ + for (int index = 0; index < 1013; index++) { + ExternalVariable* exportedVariable = &(gExternalVariables[index]); + + if (exportedVariable->name[0] != '\0') { + internal_free_safe(exportedVariable->programName, __FILE__, __LINE__); // ..\\int\\EXPORT.C, 274 + } + + if (exportedVariable->type == VALUE_TYPE_DYNAMIC_STRING) { + internal_free_safe(exportedVariable->stringValue, __FILE__, __LINE__); // ..\\int\\EXPORT.C, 276 + } + } +} + +// 0x44158C +Program* externalProcedureGetProgram(const char* identifier, int* addressPtr, int* argumentCountPtr) +{ + ExternalProcedure* externalProcedure = externalProcedureFind(identifier); + if (externalProcedure == NULL) { + return NULL; + } + + if (externalProcedure->program == NULL) { + return NULL; + } + + *addressPtr = externalProcedure->address; + *argumentCountPtr = externalProcedure->argumentCount; + + return externalProcedure->program; +} + +// 0x4415B0 +int externalProcedureCreate(Program* program, const char* identifier, int address, int argumentCount) +{ + ExternalProcedure* externalProcedure = externalProcedureFind(identifier); + if (externalProcedure != NULL) { + if (program != externalProcedure->program) { + return 1; + } + } else { + externalProcedure = externalProcedureAdd(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 = &(gExternalVariables[index]); + if (exportedVariable->name[0] != '\0') { + if ((exportedVariable->type & 0xF7FF) == VALUE_TYPE_STRING) { + if (exportedVariable->stringValue != NULL) { + internal_free_safe(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 387 + } + } + + if (exportedVariable->programName != NULL) { + internal_free_safe(exportedVariable->programName, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 393 + exportedVariable->programName = NULL; + } + + exportedVariable->name[0] = '\0'; + exportedVariable->type = 0; + } + } +} diff --git a/src/export.h b/src/export.h new file mode 100644 index 0000000..b8466c6 --- /dev/null +++ b/src/export.h @@ -0,0 +1,40 @@ +#ifndef EXPORT_H +#define EXPORT_H + +#include "interpreter.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; + +extern ExternalProcedure gExternalProcedures[1013]; +extern ExternalVariable gExternalVariables[1013]; + +ExternalProcedure* externalProcedureFind(const char* identifier); +ExternalProcedure* externalProcedureAdd(const char* identifier); +ExternalVariable* externalVariableFind(const char* identifier); +ExternalVariable* externalVariableAdd(const char* identifier); +int externalVariableSetValue(Program* program, const char* identifier, opcode_t opcode, int data); +int externalVariableGetValue(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr); +int externalVariableCreate(Program* program, const char* identifier); +void _removeProgramReferences(Program* program); +void _initExport(); +void externalVariablesClear(); +Program* externalProcedureGetProgram(const char* identifier, int* addressPtr, int* argumentCountPtr); +int externalProcedureCreate(Program* program, const char* identifier, int address, int argumentCount); +void _exportClearAllVariables(); + +#endif /* EXPORT_H */ diff --git a/src/file_find.c b/src/file_find.c new file mode 100644 index 0000000..517c340 --- /dev/null +++ b/src/file_find.c @@ -0,0 +1,63 @@ +#include "file_find.h" + +// 0x4E6380 +bool fileFindFirst(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 fileFindNext(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 findFindClose(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; +} diff --git a/src/file_find.h b/src/file_find.h new file mode 100644 index 0000000..10490d9 --- /dev/null +++ b/src/file_find.h @@ -0,0 +1,44 @@ +#ifndef FILE_FIND_H +#define FILE_FIND_H + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 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(_MSC_VER) + HANDLE hFind; + WIN32_FIND_DATAA ffd; +#elif defined(__WATCOMC__) + DIR* dir; + struct dirent* entry; +#else +#error Not implemented +#endif +} DirectoryFileFindData; + +bool fileFindFirst(const char* path, DirectoryFileFindData* findData); +bool fileFindNext(DirectoryFileFindData* findData); +bool findFindClose(DirectoryFileFindData* findData); + +#endif /* FILE_FIND_H */ diff --git a/src/file_utils.c b/src/file_utils.c new file mode 100644 index 0000000..52bde9f --- /dev/null +++ b/src/file_utils.c @@ -0,0 +1,144 @@ +// NOTE: For unknown reason functions in this module use __stdcall instead +// of regular __usercall. + +#include "file_utils.h" + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 0x452740 +int fileCopyDecompressed(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 fileCopyCompressed(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. +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; +} diff --git a/src/file_utils.h b/src/file_utils.h new file mode 100644 index 0000000..10f626a --- /dev/null +++ b/src/file_utils.h @@ -0,0 +1,8 @@ +#ifndef FILE_UTILS_H +#define FILE_UTILS_H + +int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath); +int fileCopyCompressed(const char* existingFilePath, const char* newFilePath); +int _gzdecompress_file(const char* existingFilePath, const char* newFilePath); + +#endif /* FILE_UTILS_H */ diff --git a/src/font_manager.c b/src/font_manager.c new file mode 100644 index 0000000..31029a8 --- /dev/null +++ b/src/font_manager.c @@ -0,0 +1,337 @@ +#include "font_manager.h" + +#include "color.h" +#include "db.h" +#include "memory_manager.h" + +#include +#include + +// 0x518680 +bool gInterfaceFontsInitialized = false; + +// 0x518684 +int gInterfaceFontsLength = 0; + +// 0x518688 +FontManager gModernFontManager = { + 100, + 110, + interfaceFontSetCurrentImpl, + interfaceFontDrawImpl, + interfaceFontGetLineHeightImpl, + interfaceFontGetStringWidthImpl, + interfaceFontGetCharacterWidthImpl, + interfaceFontGetMonospacedStringWidthImpl, + interfaceFontGetLetterSpacingImpl, + interfaceFontGetBufferSizeImpl, + interfaceFontGetMonospacedCharacterWidthImpl, +}; + +// 0x586838 +InterfaceFontDescriptor gInterfaceFontDescriptors[INTERFACE_FONT_MAX]; + +// 0x58E938 +int gCurrentInterfaceFont; + +// 0x58E93C +InterfaceFontDescriptor* gCurrentInterfaceFontDescriptor; + +// 0x441C80 +int interfaceFontsInit() +{ + int currentFont = -1; + + for (int font = 0; font < INTERFACE_FONT_MAX; font++) { + if (interfaceFontLoad(font) == -1) { + gInterfaceFontDescriptors[font].field_0 = 0; + gInterfaceFontDescriptors[font].data = NULL; + } else { + ++gInterfaceFontsLength; + + if (currentFont == -1) { + currentFont = font; + } + } + } + + if (currentFont == -1) { + return -1; + } + + gInterfaceFontsInitialized = true; + + interfaceFontSetCurrentImpl(currentFont + 100); + + return 0; +} + +// 0x441CEC +void interfaceFontsExit() +{ + for (int font = 0; font < INTERFACE_FONT_MAX; font++) { + if (gInterfaceFontDescriptors[font].data != NULL) { + internal_free_safe(gInterfaceFontDescriptors[font].data, __FILE__, __LINE__); // FONTMGR.C, 124 + } + } +} + +// 0x441D20 +int interfaceFontLoad(int font_index) +{ + InterfaceFontDescriptor* fontDescriptor = &(gInterfaceFontDescriptors[font_index]); + + char path[56]; + sprintf(path, "font%d.aaf", font_index); + + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + return false; + } + + int fileSize = fileGetSize(stream); + + int sig; + if (fileRead(&sig, 4, 1, stream) != 1) goto err; + + sig = _byteswap_ulong(sig); + if (sig != 0x41414646) goto err; + + if (fileRead(&(fontDescriptor->field_0), 2, 1, stream) != 1) goto err; + fontDescriptor->field_0 = _byteswap_ushort(fontDescriptor->field_0); + + if (fileRead(&(fontDescriptor->letterSpacing), 2, 1, stream) != 1) goto err; + fontDescriptor->letterSpacing = _byteswap_ushort(fontDescriptor->letterSpacing); + + if (fileRead(&(fontDescriptor->wordSpacing), 2, 1, stream) != 1) goto err; + fontDescriptor->wordSpacing = _byteswap_ushort(fontDescriptor->wordSpacing); + + if (fileRead(&(fontDescriptor->field_6), 2, 1, stream) != 1) goto err; + fontDescriptor->field_6 = _byteswap_ushort(fontDescriptor->field_6); + + for (int index = 0; index < 256; index++) { + InterfaceFontGlyph* glyph = &(fontDescriptor->glyphs[index]); + + if (fileRead(&(glyph->width), 2, 1, stream) != 1) goto err; + glyph->width = _byteswap_ushort(glyph->width); + + if (fileRead(&(glyph->field_2), 2, 1, stream) != 1) goto err; + glyph->field_2 = _byteswap_ushort(glyph->field_2); + + if (fileRead(&(glyph->field_4), 4, 1, stream) != 1) goto err; + glyph->field_4 = _byteswap_ulong(glyph->field_4); + } + + fileSize -= sizeof(InterfaceFontDescriptor); + + fontDescriptor->data = internal_malloc_safe(fileSize, __FILE__, __LINE__); // FONTMGR.C, 259 + if (fontDescriptor->data == NULL) goto err; + + if (fileRead(fontDescriptor->data, fileSize, 1, stream) != 1) { + internal_free_safe(fontDescriptor->data, __FILE__, __LINE__); // FONTMGR.C, 268 + goto err; + } + + fileClose(stream); + + return 0; + +err: + fileClose(stream); + + return -1; +} + +// 0x442120 +void interfaceFontSetCurrentImpl(int font) +{ + if (!gInterfaceFontsInitialized) { + return; + } + + font -= 100; + + if (gInterfaceFontDescriptors[font].data != NULL) { + gCurrentInterfaceFont = font; + gCurrentInterfaceFontDescriptor = &(gInterfaceFontDescriptors[font]); + } +} + +// 0x442168 +int interfaceFontGetLineHeightImpl() +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + return gCurrentInterfaceFontDescriptor->field_6 + gCurrentInterfaceFontDescriptor->field_0; +} + +// 0x442188 +int interfaceFontGetStringWidthImpl(const char* string) +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + const char* pch = string; + int width = 0; + + while (*pch != '\0') { + int v3; + int v4; + + if (*pch == ' ') { + v3 = gCurrentInterfaceFontDescriptor->letterSpacing; + v4 = gCurrentInterfaceFontDescriptor->wordSpacing; + } else { + v3 = gCurrentInterfaceFontDescriptor->glyphs[*pch & 0xFF].width; + v4 = gCurrentInterfaceFontDescriptor->letterSpacing; + } + width += v3 + v4; + + pch++; + } + + return width; +} + +// 0x4421DC +int interfaceFontGetCharacterWidthImpl(int ch) +{ + int width; + + if (!gInterfaceFontsInitialized) { + return 0; + } + + if (ch == ' ') { + width = gCurrentInterfaceFontDescriptor->wordSpacing; + } else { + width = gCurrentInterfaceFontDescriptor->glyphs[ch].width; + } + + return width; +} + +// 0x442210 +int interfaceFontGetMonospacedStringWidthImpl(const char* str) +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + return interfaceFontGetMonospacedCharacterWidthImpl() * strlen(str); +} + +// 0x442240 +int interfaceFontGetLetterSpacingImpl() +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + return gCurrentInterfaceFontDescriptor->letterSpacing; +} + +// 0x442258 +int interfaceFontGetBufferSizeImpl(const char* str) +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + return interfaceFontGetStringWidthImpl(str) * interfaceFontGetLineHeightImpl(); +} + +// 0x442278 +int interfaceFontGetMonospacedCharacterWidthImpl() +{ + if (!gInterfaceFontsInitialized) { + return 0; + } + + int v1; + if (gCurrentInterfaceFontDescriptor->wordSpacing <= gCurrentInterfaceFontDescriptor->field_8) { + v1 = gCurrentInterfaceFontDescriptor->field_6; + } else { + v1 = gCurrentInterfaceFontDescriptor->letterSpacing; + } + + return v1 + gCurrentInterfaceFontDescriptor->field_0; +} + +// 0x4422B4 +void interfaceFontDrawImpl(unsigned char* buf, const char* string, int length, int pitch, int color) +{ + if (!gInterfaceFontsInitialized) { + return; + } + + if ((color & FONT_SHADOW) != 0) { + color &= ~FONT_SHADOW; + // NOTE: Other font options preserved. This is different from text font + // shadows. + interfaceFontDrawImpl(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 = interfaceFontGetMonospacedCharacterWidthImpl(); + } + + unsigned char* ptr = buf; + while (*string != '\0') { + char ch = *string++; + + int characterWidth; + if (ch == ' ') { + characterWidth = gCurrentInterfaceFontDescriptor->wordSpacing; + } else { + characterWidth = gCurrentInterfaceFontDescriptor->glyphs[ch & 0xFF].width; + } + + unsigned char* end; + if ((color & FONT_MONO) != 0) { + end = ptr + monospacedCharacterWidth; + ptr += (monospacedCharacterWidth - characterWidth - gCurrentInterfaceFontDescriptor->letterSpacing) / 2; + } else { + end = ptr + characterWidth + gCurrentInterfaceFontDescriptor->letterSpacing; + } + + if (end - buf > length) { + break; + } + + InterfaceFontGlyph* glyph = &(gCurrentInterfaceFontDescriptor->glyphs[ch & 0xFF]); + unsigned char* glyphDataPtr = gCurrentInterfaceFontDescriptor->data + glyph->field_4; + + // Skip blank pixels (difference between font's line height and glyph height). + ptr += (gCurrentInterfaceFontDescriptor->field_0 - glyph->field_2) * pitch; + + for (int y = 0; y < glyph->field_2; 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 * (gCurrentInterfaceFontDescriptor->field_0 - 1); + for (int index = 0; index < length; index++) { + *underlinePtr++ = color & 0xFF; + } + } + + _freeColorBlendTable(color & 0xFF); +} diff --git a/src/font_manager.h b/src/font_manager.h new file mode 100644 index 0000000..c6117dd --- /dev/null +++ b/src/font_manager.h @@ -0,0 +1,49 @@ +#ifndef FONT_MANAGER_H +#define FONT_MANAGER_H + +#include "text_font.h" + +#include + +// The maximum number of interface fonts. +#define INTERFACE_FONT_MAX (16) + +typedef struct InterfaceFontGlyph { + short width; + short field_2; + int field_4; +} InterfaceFontGlyph; + +typedef struct InterfaceFontDescriptor { + short field_0; + short letterSpacing; + short wordSpacing; + short field_6; + short field_8; + short field_A; + InterfaceFontGlyph glyphs[256]; + unsigned char* data; +} InterfaceFontDescriptor; + +extern bool gInterfaceFontsInitialized; +extern int gInterfaceFontsLength; +extern FontManager gModernFontManager; + +extern InterfaceFontDescriptor gInterfaceFontDescriptors[INTERFACE_FONT_MAX]; +extern int gCurrentInterfaceFont; +extern InterfaceFontDescriptor* gCurrentInterfaceFontDescriptor; + +int interfaceFontsInit(); +void interfaceFontsExit(); +int interfaceFontLoad(int font); +void interfaceFontSetCurrentImpl(int font); +int interfaceFontGetLineHeightImpl(); +int interfaceFontGetStringWidthImpl(const char* string); +int interfaceFontGetCharacterWidthImpl(int ch); +int interfaceFontGetMonospacedStringWidthImpl(const char* string); +int interfaceFontGetLetterSpacingImpl(); +int interfaceFontGetBufferSizeImpl(const char* string); +int interfaceFontGetMonospacedCharacterWidthImpl(); +void interfaceFontDrawImpl(unsigned char* buf, const char* string, int length, int pitch, int color); + +#endif /* FONT_MANAGER_H */ diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..fdd946d --- /dev/null +++ b/src/game.c @@ -0,0 +1,1268 @@ +#include "game.h" + +#include "actions.h" +#include "animation.h" +#include "automap.h" +#include "character_editor.h" +#include "character_selector.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "db.h" +#include "dbox.h" +#include "debug.h" +#include "display_monitor.h" +#include "draw.h" +#include "electronic_registration.h" +#include "endgame.h" +#include "font_manager.h" +#include "game_config.h" +#include "game_dialog.h" +#include "game_memory.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "inventory.h" +#include "item.h" +#include "loadsave.h" +#include "map.h" +#include "memory.h" +#include "movie.h" +#include "movie_effect.h" +#include "object.h" +#include "options.h" +#include "palette.h" +#include "party_member.h" +#include "perk.h" +#include "pipboy.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "skilldex.h" +#include "stat.h" +#include "text_font.h" +#include "tile.h" +#include "trait.h" +#include "trap.h" +#include "version.h" +#include "window_manager.h" +#include "world_map.h" + +#include +#include + +#define HELP_SCREEN_WIDTH (640) +#define HELP_SCREEN_HEIGHT (480) + +#define SPLASH_WIDTH (640) +#define SPLASH_HEIGHT (480) +#define SPLASH_COUNT (10) + +// 0x501C9C +char _aGame_0[] = "game\\"; + +// 0x5020B8 +char _aDec11199816543[] = VERSION_BUILD_TIME; + +// 0x5186B4 +bool gGameUiDisabled = false; + +// 0x5186B8 +int _game_state_cur = 0; + +// 0x5186BC +bool gIsMapper = false; + +// 0x5186C0 +int* gGameGlobalVars = NULL; + +// 0x5186C4 +int gGameGlobalVarsLength = 0; + +// 0x5186C8 +const char* asc_5186C8 = _aGame_0; + +// 0x5186CC +int _game_user_wants_to_quit = 0; + +// misc.msg +// +// 0x58E940 +MessageList gMiscMessageList; + +// master.dat loading result +// +// 0x58E948 +int _master_db_handle; + +// critter.dat loading result +// +// 0x58E94C +int _critter_db_handle; + +// 0x442580 +int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4, int argc, char** argv) +{ + char path[MAX_PATH]; + + if (gameMemoryInit() == -1) { + return -1; + } + + gameConfigInit(isMapper, argc, argv); + + gIsMapper = isMapper; + + if (gameDbInit() == -1) { + gameConfigExit(false); + return -1; + } + + runElectronicRegistration(); + programWindowSetTitle(windowTitle); + _initWindow(1, a4); + paletteInit(); + + char* language; + if (configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { + if (stricmp(language, FRENCH) == 0) { + keyboardSetLayout(KEYBOARD_LAYOUT_FRENCH); + } else if (stricmp(language, GERMAN) == 0) { + keyboardSetLayout(KEYBOARD_LAYOUT_GERMAN); + } else if (stricmp(language, ITALIAN) == 0) { + keyboardSetLayout(KEYBOARD_LAYOUT_ITALIAN); + } else if (stricmp(language, SPANISH) == 0) { + keyboardSetLayout(KEYBOARD_LAYOUT_SPANISH); + } + } + + if (!gIsMapper) { + showSplash(); + } + + _trap_init(); + + interfaceFontsInit(); + fontManagerAdd(&gModernFontManager); + fontSetCurrent(font); + + screenshotHandlerConfigure(KEY_F12, gameTakeScreenshot); + pauseHandlerConfigure(-1, NULL); + + tileDisable(); + + randomInit(); + badwordsInit(); + skillsInit(); + statsInit(); + + if (partyMembersInit() != 0) { + debugPrint("Failed on partyMember_init\n"); + return -1; + } + + perksInit(); + traitsInit(); + itemsInit(); + queueInit(); + critterInit(); + aiInit(); + _inven_reset_dude(); + + if (gameSoundInit() != 0) { + debugPrint("Sound initialization failed.\n"); + } + + debugPrint(">gsound_init\t"); + + movieInit(); + debugPrint(">initMovie\t\t"); + + if (gameMoviesInit() != 0) { + debugPrint("Failed on gmovie_init\n"); + return -1; + } + + debugPrint(">gmovie_init\t"); + + if (movieEffectsInit() != 0) { + debugPrint("Failed on moviefx_init\n"); + return -1; + } + + debugPrint(">moviefx_init\t"); + + if (isoInit() != 0) { + debugPrint("Failed on iso_init\n"); + return -1; + } + + debugPrint(">iso_init\t"); + + if (gameMouseInit() != 0) { + debugPrint("Failed on gmouse_init\n"); + return -1; + } + + debugPrint(">gmouse_init\t"); + + if (protoInit() != 0) { + debugPrint("Failed on proto_init\n"); + return -1; + } + + debugPrint(">proto_init\t"); + + animationInit(); + debugPrint(">anim_init\t"); + + if (scriptsInit() != 0) { + debugPrint("Failed on scr_init\n"); + return -1; + } + + debugPrint(">scr_init\t"); + + if (gameLoadGlobalVars() != 0) { + debugPrint("Failed on game_load_info\n"); + return -1; + } + + debugPrint(">game_load_info\t"); + + if (_scr_game_init() != 0) { + debugPrint("Failed on scr_game_init\n"); + return -1; + } + + debugPrint(">scr_game_init\t"); + + if (worldmapInit() != 0) { + debugPrint("Failed on wmWorldMap_init\n"); + return -1; + } + + debugPrint(">wmWorldMap_init\t"); + + _CharEditInit(); + debugPrint(">CharEditInit\t"); + + pipboyInit(); + debugPrint(">pip_init\t\t"); + + _InitLoadSave(); + lsgInit(); + debugPrint(">InitLoadSave\t"); + + if (gameDialogInit() != 0) { + debugPrint("Failed on gdialog_init\n"); + return -1; + } + + debugPrint(">gdialog_init\t"); + + if (combatInit() != 0) { + debugPrint("Failed on combat_init\n"); + return -1; + } + + debugPrint(">combat_init\t"); + + if (automapInit() != 0) { + debugPrint("Failed on automap_init\n"); + return -1; + } + + debugPrint(">automap_init\t"); + + if (!messageListInit(&gMiscMessageList)) { + debugPrint("Failed on message_init\n"); + return -1; + } + + debugPrint(">message_init\t"); + + sprintf(path, "%s%s", asc_5186C8, "misc.msg"); + + if (!messageListLoad(&gMiscMessageList, path)) { + debugPrint("Failed on message_load\n"); + return -1; + } + + debugPrint(">message_load\t"); + + if (scriptsDisable() != 0) { + debugPrint("Failed on scr_disable\n"); + return -1; + } + + debugPrint(">scr_disable\t"); + + if (_init_options_menu() != 0) { + debugPrint("Failed on init_options_menu\n"); + return -1; + } + + debugPrint(">init_options_menu\n"); + + if (endgameDeathEndingInit() != 0) { + debugPrint("Failed on endgameDeathEndingInit"); + return -1; + } + + debugPrint(">endgameDeathEndingInit\n"); + + return 0; +} + +// 0x442B84 +void gameReset() +{ + tileDisable(); + paletteReset(); + randomReset(); + skillsReset(); + statsReset(); + perksReset(); + traitsReset(); + itemsReset(); + queueExit(); + animationReset(); + lsgInit(); + critterReset(); + aiReset(); + _inven_reset_dude(); + gameSoundReset(); + _movieStop(); + movieEffectsReset(); + gameMoviesReset(); + isoReset(); + gameMouseReset(); + protoReset(); + _scr_reset(); + gameLoadGlobalVars(); + scriptsReset(); + worldmapReset(); + partyMembersReset(); + _CharEditInit(); + pipboyReset(); + _ResetLoadSave(); + gameDialogReset(); + combatReset(); + _game_user_wants_to_quit = 0; + automapReset(); + _init_options_menu(); +} + +// 0x442C34 +void gameExit() +{ + debugPrint("\nGame Exit\n"); + + tileDisable(); + messageListFree(&gMiscMessageList); + combatExit(); + gameDialogExit(); + _scr_game_exit(); + + // NOTE: Uninline. + gameFreeGlobalVars(); + + scriptsExit(); + animationExit(); + protoExit(); + gameMouseExit(); + isoExit(); + movieEffectsExit(); + movieExit(); + gameSoundExit(); + aiExit(); + critterExit(); + itemsExit(); + queueExit(); + perksExit(); + statsExit(); + skillsExit(); + traitsExit(); + randomExit(); + badwordsExit(); + automapExit(); + paletteExit(); + worldmapExit(); + partyMembersExit(); + endgameDeathEndingExit(); + interfaceFontsExit(); + _trap_init(); + _windowClose(); + dbExit(); + gameConfigExit(true); +} + +// 0x442D44 +int gameHandleKey(int eventCode, bool isInCombatMode) +{ + if (_game_state_cur == 5) { + _gdialogSystemEnter(); + } + + if (eventCode == -1) { + return 0; + } + + if (eventCode == -2) { + int mouseState = mouseGetEvent(); + int mouseX; + int mouseY; + mouseGetPosition(&mouseX, &mouseY); + + if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) { + if (mouseX == _scr_size.left || mouseX == _scr_size.right + || mouseY == _scr_size.top || mouseY == _scr_size.bottom) { + _gmouse_clicked_on_edge = true; + } + } + } 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 (interfaceBarEnabled()) { + _intface_use_item(); + } + break; + case KEY_CTRL_Q: + case KEY_CTRL_X: + case KEY_F10: + soundPlayFile("ib1p1xx1"); + showQuitConfirmationDialog(); + break; + case KEY_TAB: + if (interfaceBarEnabled() + && gPressedPhysicalKeys[DIK_LALT] == 0 + && gPressedPhysicalKeys[DIK_RALT] == 0) { + soundPlayFile("ib1p1xx1"); + automapShow(true, false); + } + break; + case KEY_CTRL_P: + soundPlayFile("ib1p1xx1"); + showPause(false); + break; + case KEY_UPPERCASE_A: + case KEY_LOWERCASE_A: + if (interfaceBarEnabled()) { + if (!isInCombatMode) { + _combat(NULL); + } + } + break; + case KEY_UPPERCASE_N: + case KEY_LOWERCASE_N: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + interfaceCycleItemAction(); + } + break; + case KEY_UPPERCASE_M: + case KEY_LOWERCASE_M: + gameMouseCycleMode(); + break; + case KEY_UPPERCASE_B: + case KEY_LOWERCASE_B: + // change active hand + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + interfaceBarSwapHands(true); + } + break; + case KEY_UPPERCASE_C: + case KEY_LOWERCASE_C: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + bool isoWasEnabled = isoDisable(); + _editor_design(false); + if (isoWasEnabled) { + isoEnable(); + } + } + break; + case KEY_UPPERCASE_I: + case KEY_LOWERCASE_I: + // open inventory + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + inventoryOpen(); + } + break; + case KEY_ESCAPE: + case KEY_UPPERCASE_O: + case KEY_LOWERCASE_O: + // options + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + showOptions(); + } + break; + case KEY_UPPERCASE_P: + case KEY_LOWERCASE_P: + // pipboy + if (interfaceBarEnabled()) { + if (isInCombatMode) { + soundPlayFile("iisxxxx1"); + + // Pipboy not available in combat! + MessageListItem messageListItem; + char title[128]; + strcpy(title, getmsg(&gMiscMessageList, &messageListItem, 7)); + showDialogBox(title, NULL, 0, 192, 116, _colorTable[32328], NULL, _colorTable[32328], 0); + } else { + soundPlayFile("ib1p1xx1"); + pipboyOpen(false); + } + } + break; + case KEY_UPPERCASE_S: + case KEY_LOWERCASE_S: + // skilldex + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + + int mode = -1; + + // NOTE: There is an `inc` for this value to build jump table which + // is not needed. + int rc = skilldexOpen(); + + // Remap Skilldex result code to action. + switch (rc) { + case SKILLDEX_RC_ERROR: + debugPrint("\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) { + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(mode); + } + } + break; + case KEY_UPPERCASE_Z: + case KEY_LOWERCASE_Z: + if (interfaceBarEnabled()) { + if (isInCombatMode) { + soundPlayFile("iisxxxx1"); + + // Pipboy not available in combat! + MessageListItem messageListItem; + char title[128]; + strcpy(title, getmsg(&gMiscMessageList, &messageListItem, 7)); + showDialogBox(title, NULL, 0, 192, 116, _colorTable[32328], NULL, _colorTable[32328], 0); + } else { + soundPlayFile("ib1p1xx1"); + pipboyOpen(true); + } + } + break; + case KEY_HOME: + if (gDude->elevation != gElevation) { + mapSetElevation(gDude->elevation); + } + + if (gIsMapper) { + tileSetCenter(gDude->tile, TILE_SET_CENTER_FLAG_0x01); + } else { + _tile_scroll_to(gDude->tile, 2); + } + + break; + case KEY_1: + case KEY_EXCLAMATION: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + _action_skill_use(SKILL_SNEAK); + } + break; + case KEY_2: + case KEY_AT: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_LOCKPICK); + } + break; + case KEY_3: + case KEY_NUMBER_SIGN: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_STEAL); + } + break; + case KEY_4: + case KEY_DOLLAR: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_TRAPS); + } + break; + case KEY_5: + case KEY_PERCENT: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_FIRST_AID); + } + break; + case KEY_6: + case KEY_CARET: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_DOCTOR); + } + break; + case KEY_7: + case KEY_AMPERSAND: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_SCIENCE); + } + break; + case KEY_8: + case KEY_ASTERISK: + if (interfaceBarEnabled()) { + soundPlayFile("ib1p1xx1"); + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_REPAIR); + } + break; + case KEY_MINUS: + case KEY_UNDERSCORE: + brightnessDecrease(); + break; + case KEY_EQUAL: + case KEY_PLUS: + brightnessIncrease(); + break; + case KEY_COMMA: + case KEY_LESS: + if (reg_anim_begin(0) == 0) { + reg_anim_rotate_counter_clockwise(gDude); + reg_anim_end(); + } + break; + case KEY_DOT: + case KEY_GREATER: + if (reg_anim_begin(0) == 0) { + reg_anim_rotate_clockwise(gDude); + reg_anim_end(); + } + break; + case KEY_SLASH: + case KEY_QUESTION: + if (1) { + soundPlayFile("ib1p1xx1"); + + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + MessageList messageList; + if (messageListInit(&messageList)) { + char path[FILENAME_MAX]; + sprintf(path, "%s%s", asc_5186C8, "editor.msg"); + + if (messageListLoad(&messageList, path)) { + MessageListItem messageListItem; + messageListItem.num = 500 + month - 1; + if (messageListGetItem(&messageList, &messageListItem)) { + char* time = gameTimeGetTimeString(); + + char date[128]; + sprintf(date, "%s: %d/%d %s", messageListItem.text, day, year, time); + + displayMonitorAddMessage(date); + } + } + + messageListFree(&messageList); + } + } + break; + case KEY_F1: + soundPlayFile("ib1p1xx1"); + showHelp(); + break; + case KEY_F2: + gameSoundSetMasterVolume(gameSoundGetMasterVolume() - 2047); + break; + case KEY_F3: + gameSoundSetMasterVolume(gameSoundGetMasterVolume() + 2047); + break; + case KEY_CTRL_S: + case KEY_F4: + soundPlayFile("ib1p1xx1"); + if (lsgSaveGame(1) == -1) { + debugPrint("\n ** Error calling SaveGame()! **\n"); + } + break; + case KEY_CTRL_L: + case KEY_F5: + soundPlayFile("ib1p1xx1"); + if (lsgLoadGame(LOAD_SAVE_MODE_NORMAL) == -1) { + debugPrint("\n ** Error calling LoadGame()! **\n"); + } + break; + case KEY_F6: + if (1) { + soundPlayFile("ib1p1xx1"); + + int rc = lsgSaveGame(LOAD_SAVE_MODE_QUICK); + if (rc == -1) { + debugPrint("\n ** Error calling SaveGame()! **\n"); + } else if (rc == 1) { + MessageListItem messageListItem; + // Quick save game successfully saved. + char* msg = getmsg(&gMiscMessageList, &messageListItem, 5); + displayMonitorAddMessage(msg); + } + } + break; + case KEY_F7: + if (1) { + soundPlayFile("ib1p1xx1"); + + int rc = lsgLoadGame(LOAD_SAVE_MODE_QUICK); + if (rc == -1) { + debugPrint("\n ** Error calling LoadGame()! **\n"); + } else if (rc == 1) { + MessageListItem messageListItem; + // Quick load game successfully loaded. + char* msg = getmsg(&gMiscMessageList, &messageListItem, 4); + displayMonitorAddMessage(msg); + } + } + break; + case KEY_CTRL_V: + if (1) { + soundPlayFile("ib1p1xx1"); + + char version[VERSION_MAX]; + versionGetVersion(version); + displayMonitorAddMessage(version); + displayMonitorAddMessage(_aDec11199816543); + } + break; + case KEY_ARROW_LEFT: + mapScroll(-1, 0); + break; + case KEY_ARROW_RIGHT: + mapScroll(1, 0); + break; + case KEY_ARROW_UP: + mapScroll(0, -1); + break; + case KEY_ARROW_DOWN: + mapScroll(0, 1); + break; + } + + // TODO: Incomplete. + + return 0; +} + +// game_ui_disable +// 0x443BFC +void gameUiDisable(int a1) +{ + if (!gGameUiDisabled) { + gameMouseObjectsHide(); + _gmouse_disable(a1); + keyboardDisable(); + interfaceBarDisable(); + gGameUiDisabled = true; + } +} + +// game_ui_enable +// 0x443C30 +void gameUiEnable() +{ + if (gGameUiDisabled) { + interfaceBarEnable(); + keyboardEnable(); + keyboardReset(); + _gmouse_enable(); + gameMouseObjectsShow(); + gGameUiDisabled = false; + } +} + +// game_ui_is_disabled +// 0x443C60 +bool gameUiIsDisabled() +{ + return gGameUiDisabled; +} + +// 0x443C68 +int gameGetGlobalVar(int var) +{ + if (var < 0 || var >= gGameGlobalVarsLength) { + debugPrint("ERROR: attempt to reference global var out of range: %d", var); + return 0; + } + + return gGameGlobalVars[var]; +} + +// 0x443C98 +int gameSetGlobalVar(int var, int value) +{ + if (var < 0 || var >= gGameGlobalVarsLength) { + debugPrint("ERROR: attempt to reference global var out of range: %d", var); + return -1; + } + + gGameGlobalVars[var] = value; + + return 0; +} + +// game_load_info +// 0x443CC8 +int gameLoadGlobalVars() +{ + return globalVarsRead("data\\vault13.gam", "GAME_GLOBAL_VARS:", &gGameGlobalVarsLength, &gGameGlobalVars); +} + +// 0x443CE8 +int globalVarsRead(const char* path, const char* section, int* out_vars_num, int** out_vars) +{ + File* stream; + char str[258]; + char* ch; + + _inven_reset_dude(); + + stream = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + if (*out_vars_num != 0) { + internal_free(*out_vars); + *out_vars = NULL; + *out_vars_num = 0; + } + + if (section != NULL) { + while (fileReadString(str, sizeof(str), stream)) { + if (strncmp(str, section, 16) == 0) { + break; + } + } + } + + while (fileReadString(str, sizeof(str), stream)) { + if (str[0] == '\n') { + continue; + } + + if (str[0] == '/' && str[1] == '/') { + continue; + } + + ch = str; + + while (*ch != '\0' && *ch != ';') { + ch++; + } + + if (*ch != '\0') { + *ch = '\0'; + } + + *out_vars_num = *out_vars_num + 1; + *out_vars = internal_realloc(*out_vars, sizeof(int) * *out_vars_num); + + if (*out_vars == NULL) { + exit(1); + } + + ch = str; + while (*ch != '\0' && *ch != '=') { + ch++; + } + + if (*ch != '\0') { + sscanf(ch + 1, "%d", *out_vars + *out_vars_num - 1); + } else { + *out_vars[*out_vars_num - 1] = 0; + } + } + + fileClose(stream); + + return 0; +} + +// 0x443E2C +int _game_state() +{ + return _game_state_cur; +} + +// 0x443E34 +int _game_state_request(int a1) +{ + if (a1 == 0) { + a1 = 1; + } else if (a1 == 2) { + a1 = 3; + } else if (a1 == 4) { + a1 = 5; + } + + if (_game_state_cur != 4 || a1 != 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 1: + v0 = 0; + break; + case 3: + v0 = 2; + break; + case 5: + v0 = 4; + } + + _game_state_cur = v0; +} + +// 0x443EF0 +int gameTakeScreenshot(int width, int height, unsigned char* buffer, unsigned char* palette) +{ + MessageListItem messageListItem; + + if (screenshotHandlerDefaultImpl(width, height, buffer, palette) != 0) { + // Error saving screenshot. + messageListItem.num = 8; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + } + + // Saved screenshot. + messageListItem.num = 3; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return 0; +} + +// NOTE: Inlined. +// +// 0x443F50 +void gameFreeGlobalVars() +{ + gGameGlobalVarsLength = 0; + if (gGameGlobalVars != NULL) { + internal_free(gGameGlobalVars); + gGameGlobalVars = NULL; + } +} + +// 0x443F74 +void showHelp() +{ + bool isoWasEnabled = isoDisable(); + gameMouseObjectsHide(); + + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + bool colorCycleWasEnabled = colorCycleEnabled(); + colorCycleDisable(); + + int win = windowCreate(0, 0, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, 0, WINDOW_HIDDEN | WINDOW_FLAG_0x04); + if (win != -1) { + unsigned char* windowBuffer = windowGetBuffer(win); + if (windowBuffer != NULL) { + int backgroundFid = buildFid(6, 297, 0, 0, 0); + CacheEntry* backgroundHandle; + unsigned char* backgroundData = artLockFrameData(backgroundFid, 0, 0, &backgroundHandle); + if (backgroundData != NULL) { + paletteSetEntries(gPaletteBlack); + blitBufferToBuffer(backgroundData, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, HELP_SCREEN_WIDTH, windowBuffer, HELP_SCREEN_WIDTH); + artUnlock(backgroundHandle); + windowUnhide(win); + colorPaletteLoad("art\\intrface\\helpscrn.pal"); + paletteSetEntries(_cmap); + + while (_get_input() == -1 && _game_user_wants_to_quit == 0) { + } + + while (mouseGetEvent() != 0) { + _get_input(); + } + + paletteSetEntries(gPaletteBlack); + } + } + + windowDestroy(win); + colorPaletteLoad("color.pal"); + paletteSetEntries(_cmap); + } + + if (colorCycleWasEnabled) { + colorCycleEnable(); + } + + gameMouseObjectsShow(); + + if (isoWasEnabled) { + isoEnable(); + } +} + +// 0x4440B8 +int showQuitConfirmationDialog() +{ + bool isoWasEnabled = isoDisable(); + + bool gameMouseWasVisible; + if (isoWasEnabled) { + gameMouseWasVisible = gameMouseObjectsIsVisible(); + } else { + gameMouseWasVisible = false; + } + + if (gameMouseWasVisible) { + gameMouseObjectsHide(); + } + + bool cursorWasHidden = cursorIsHidden(); + if (cursorWasHidden) { + mouseShowCursor(); + } + + int oldCursor = gameMouseGetCursor(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + int rc; + + // Are you sure you want to quit? + MessageListItem messageListItem; + messageListItem.num = 0; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + rc = showDialogBox(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; + } + + gameMouseSetCursor(oldCursor); + + if (cursorWasHidden) { + mouseHideCursor(); + } + + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + if (isoWasEnabled) { + isoEnable(); + } + + return rc; +} + +// 0x44418C +int gameDbInit() +{ + 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 (configGetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, &hashing)) { + _db_enable_hash_table_(); + } + + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, &main_file_name); + if (*main_file_name == '\0') { + main_file_name = NULL; + } + + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patch_file_name); + if (*patch_file_name == '\0') { + patch_file_name = NULL; + } + + _master_db_handle = dbOpen(main_file_name, 0, patch_file_name, 1); + if (_master_db_handle == -1) { + showMesageBox("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; + } + + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, &main_file_name); + if (*main_file_name == '\0') { + main_file_name = NULL; + } + + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, &patch_file_name); + if (*patch_file_name == '\0') { + patch_file_name = NULL; + } + + _critter_db_handle = dbOpen(main_file_name, 0, patch_file_name, 1); + if (_critter_db_handle == -1) { + showMesageBox("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) { + dbOpen(filename, 0, NULL, 1); + } + } + + return 0; +} + +// 0x444384 +void showSplash() +{ + int splash; + configGetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, &splash); + + char path[64]; + char* language; + if (configGetString(&gGameConfig, 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 = fileOpen(filePath, "rb"); + if (stream != NULL) { + break; + } + + splash++; + + if (splash >= SPLASH_COUNT) { + splash = 0; + } + } + + if (stream == NULL) { + return; + } + + unsigned char* palette = internal_malloc(768); + if (palette == NULL) { + fileClose(stream); + return; + } + + unsigned char* data = internal_malloc(SPLASH_WIDTH * SPLASH_HEIGHT); + if (data == NULL) { + internal_free(palette); + fileClose(stream); + return; + } + + paletteSetEntries(gPaletteBlack); + fileSeek(stream, 10, SEEK_SET); + fileRead(palette, 1, 768, stream); + fileRead(data, 1, SPLASH_WIDTH * SPLASH_HEIGHT, stream); + fileClose(stream); + + _scr_blit(data, SPLASH_WIDTH, SPLASH_HEIGHT, 0, 0, SPLASH_WIDTH, SPLASH_HEIGHT, 0, 0); + paletteFadeTo(palette); + + internal_free(data); + internal_free(palette); + + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, splash + 1); +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..0959aa6 --- /dev/null +++ b/src/game.h @@ -0,0 +1,44 @@ +#ifndef GAME_H +#define GAME_H + +#include "game_vars.h" +#include "message.h" + +#include + +extern char _aGame_0[]; + +extern bool gGameUiDisabled; +extern int _game_state_cur; +extern bool gIsMapper; +extern int* gGameGlobalVars; +extern int gGameGlobalVarsLength; +extern const char* asc_5186C8; +extern int _game_user_wants_to_quit; + +extern MessageList gMiscMessageList; +extern int _master_db_handle; +extern int _critter_db_handle; + +int gameInitWithOptions(const char* windowTitle, bool isMapper, int a3, int a4, int argc, char** argv); +void gameReset(); +void gameExit(); +int gameHandleKey(int eventCode, bool isInCombatMode); +void gameUiDisable(int a1); +void gameUiEnable(); +bool gameUiIsDisabled(); +int gameGetGlobalVar(int var); +int gameSetGlobalVar(int var, int value); +int gameLoadGlobalVars(); +int globalVarsRead(const char* path, const char* section, int* out_vars_num, int** out_vars); +int _game_state(); +int _game_state_request(int a1); +void _game_state_update(); +int gameTakeScreenshot(int width, int height, unsigned char* buffer, unsigned char* palette); +void gameFreeGlobalVars(); +void showHelp(); +int showQuitConfirmationDialog(); +int gameDbInit(); +void showSplash(); + +#endif /* GAME_H */ diff --git a/src/game_config.c b/src/game_config.c new file mode 100644 index 0000000..d4cdd0e --- /dev/null +++ b/src/game_config.c @@ -0,0 +1,180 @@ +#include "game_config.h" + +#include +#include + +// A flag indicating if [gGameConfig] was initialized. +// +// 0x5186D0 +bool gGameConfigInitialized = false; + +// fallout2.cfg +// +// 0x58E950 +Config gGameConfig; + +// NOTE: There are additional 4 bytes following this array at 0x58EA7C, which +// probably means it's size is 264 bytes. +// +// 0x58E978 +char gGameConfigFilePath[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 +// [configParseCommandLineArguments] for expected format. +// +// 0x444570 +bool gameConfigInit(bool isMapper, int argc, char** argv) +{ + if (gGameConfigInitialized) { + return false; + } + + if (!configInit(&gGameConfig)) { + return false; + } + + // Initialize defaults. + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "game"); + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, "master.dat"); + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, "data"); + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, "critter.dat"); + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, "data"); + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, ENGLISH); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SCROLL_LOCK_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, 8); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_FREE_SPACE_KEY, 20480); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 3); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 2); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEED_KEY, 0); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 3.5); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 1.399994); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, 1.0); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEVICE_KEY, -1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_PORT_KEY, -1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_IRQ_KEY, -1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DMA_KEY, -1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, 1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, 22281); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, 22281); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, 22281); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, 22281); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, 448); + configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY, "sound\\music\\"); + configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY, "sound\\music\\"); + configSetString(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_MODE_KEY, "environment"); + configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_TILE_NUM_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_LOAD_INFO_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY, 0); + + if (isMapper) { + configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "mapper"); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_LIBRARIAN_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_USE_ART_NOT_PROTOS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_REBUILD_PROTOS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_OBJECTS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SHOW_PID_NUMBERS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SAVE_TEXT_MAPS_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY, 0); + configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY, 1); + configSetInt(&gGameConfig, 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(gGameConfigFilePath, "%s\\%s", executable, GAME_CONFIG_FILE_NAME); + *ch = '\\'; + } else { + strcpy(gGameConfigFilePath, GAME_CONFIG_FILE_NAME); + } + + // Read contents of `fallout2.cfg` into config. The values from the file + // will override the defaults above. + configRead(&gGameConfig, gGameConfigFilePath, false); + + // Add key-values from command line, which overrides both defaults and + // whatever was loaded from `fallout2.cfg`. + configParseCommandLineArguments(&gGameConfig, argc, argv); + + gGameConfigInitialized = true; + + return true; +} + +// Saves game config into `fallout2.cfg`. +// +// 0x444C14 +bool gameConfigSave() +{ + if (!gGameConfigInitialized) { + return false; + } + + if (!configWrite(&gGameConfig, gGameConfigFilePath, false)) { + return false; + } + + return true; +} + +// Frees game config, optionally saving it. +// +// 0x444C3C +bool gameConfigExit(bool shouldSave) +{ + if (!gGameConfigInitialized) { + return false; + } + + bool result = true; + + if (shouldSave) { + if (!configWrite(&gGameConfig, gGameConfigFilePath, false)) { + result = false; + } + } + + configFree(&gGameConfig); + + gGameConfigInitialized = false; + + return result; +} diff --git a/src/game_config.h b/src/game_config.h new file mode 100644 index 0000000..784d339 --- /dev/null +++ b/src/game_config.h @@ -0,0 +1,125 @@ +#ifndef GAME_CONFIG_H +#define GAME_CONFIG_H + +#include "config.h" + +#include + +// 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_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 bool gGameConfigInitialized; +extern Config gGameConfig; +extern char gGameConfigFilePath[]; + +bool gameConfigInit(bool isMapper, int argc, char** argv); +bool gameConfigSave(); +bool gameConfigExit(bool shouldSave); + +#endif /* GAME_CONFIG_H */ diff --git a/src/game_dialog.c b/src/game_dialog.c new file mode 100644 index 0000000..61ea837 --- /dev/null +++ b/src/game_dialog.c @@ -0,0 +1,4388 @@ +#include "game_dialog.h" + +#include "actions.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "debug.h" +#include "dialog.h" +#include "display_monitor.h" +#include "draw.h" +#include "game.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "lips.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "text_object.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include + +// 0x444D10 +int _Dogs[3] = { + 0x1000088, + 0x1000156, + 0x1000180, +}; + +// 0x5186D4 +int _dialog_state_fix = 0; + +// 0x5186D8 +int gGameDialogOptionEntriesLength = 0; + +// 0x5186DC +int gGameDialogReviewEntriesLength = 0; + +// 0x5186E0 +unsigned char* gGameDialogDisplayBuffer = NULL; + +// 0x5186E4 +int gGameDialogReplyWindow = -1; + +// 0x5186E8 +int gGameDialogOptionsWindow = -1; + +// 0x5186EC +bool _gdialog_window_created = false; + +// 0x5186F0 +int _boxesWereDisabled = 0; + +// 0x5186F4 +int gGameDialogFidgetFid = 0; + +// 0x5186F8 +CacheEntry* gGameDialogFidgetFrmHandle = NULL; + +// 0x5186FC +Art* gGameDialogFidgetFrm = NULL; + +// 0x518700 +int gGameDialogBackground = 2; + +// 0x518704 +int _lipsFID = 0; + +// 0x518708 +CacheEntry* _lipsKey = NULL; + +// 0x51870C +Art* _lipsFp = NULL; + +// 0x518710 +bool gGameDialogLipSyncStarted = false; + +// 0x518714 +int _dialogue_state = 0; + +// 0x518718 +int _dialogue_switch_mode = 0; + +// 0x51871C +int _gdialog_state = -1; + +// 0x518720 +bool _gdDialogWentOff = false; + +// 0x518724 +bool _gdDialogTurnMouseOff = false; + +// 0x518728 +int _gdReenterLevel = 0; + +// 0x51872C +bool _gdReplyTooBig = false; + +// 0x518730 +Object* _peon_table_obj = NULL; + +// 0x518734 +Object* _barterer_table_obj = NULL; + +// 0x518738 +Object* _barterer_temp_obj = NULL; + +// 0x51873C +int gGameDialogBarterModifier = 0; + +// dialogueBackWindow +// 0x518740 +int gGameDialogBackgroundWindow = -1; + +// 0x518744 +int gGameDialogWindow = -1; + +// 0x518748 +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 +int _talk_need_to_center = 1; + +// 0x5187CC +bool _can_start_new_fidget = false; + +// 0x5187D0 +int _gd_replyWin = -1; + +// 0x5187D4 +int _gd_optionsWin = -1; + +// 0x5187D8 +int gGameDialogOldMusicVolume = -1; + +// 0x5187DC +int gGameDialogOldCenterTile = -1; + +// 0x5187E0 +int gGameDialogOldDudeTile = -1; + +// 0x5187E4 +unsigned char* _light_BlendTable = NULL; + +// 0x5187E8 +unsigned char* _dark_BlendTable = NULL; + +// 0x5187EC +int _dialogue_just_started = 0; + +// 0x5187F0 +int _dialogue_seconds_since_last_input = 0; + +// 0x5187F4 +CacheEntry* gGameDialogReviewWindowButtonFrmHandles[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 +CacheEntry* _reviewBackKey = INVALID_CACHE_ENTRY; + +// 0x518810 +CacheEntry* gGameDialogReviewWindowBackgroundFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518814 +unsigned char* gGameDialogReviewWindowBackgroundFrmData = NULL; + +// 0x518818 +const int gGameDialogReviewWindowButtonWidths[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = { + 35, + 35, + 82, +}; + +// 0x518824 +const int gGameDialogReviewWindowButtonHeights[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = { + 35, + 37, + 46, +}; + +// 0x518830 +int gGameDialogReviewWindowButtonFrmIds[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* gGameDialogSpeaker = NULL; + +// 0x51884C +bool gGameDialogSpeakerIsPartyMember = false; + +// 0x518850 +int gGameDialogHeadFid = 0; + +// 0x518854 +int gGameDialogSid = -1; + +// Maps phoneme to talking head frame. +// +// 0x518858 +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, +}; + +// 0x518900 +int _phone_anim = 0; + +// 0x518904 +int _loop_cnt = -1; + +// 0x518908 +unsigned int _tocksWaiting = 10000; + +// 0x51890C +const char* _react_strs[3] = { + "Said Good", + "Said Neutral", + "Said Bad", +}; + +// 0x518918 +int _dialogue_subwin_len = 0; + +// 0x51891C +GameDialogButtonData gGameDialogDispositionButtonsData[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 +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 +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 }, +}; + +// 0x518BF4 +int _totalHotx = 0; + +// 0x58EA80 +int _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT]; + +// custom.msg +// +// 0x58EA98 +MessageList gCustomMessageList; + +// 0x58EAA0 +unsigned char _light_GrayTable[256]; + +// 0x58EBA0 +unsigned char _dark_GrayTable[256]; + +// 0x58ECA0 +unsigned char* _backgrndBufs[8]; + +// 0x58ECC0 +Rect _optionRect; + +// 0x58ECD0 +Rect _replyRect; + +// 0x58ECE0 +GameDialogReviewEntry gDialogReviewEntries[DIALOG_REVIEW_ENTRIES_CAPACITY]; + +// 0x58F460 +int _custom_buttons_start; + +// 0x58F464 +int _control_buttons_start; + +// 0x58F468 +int gGameDialogReviewWindowOldFont; + +// 0x58F46C +CacheEntry* gGameDialogRedButtonUpFrmHandle; + +// 0x58F470 +int _gdialog_buttons[9]; + +// 0x58F494 +CacheEntry* gGameDialogUpperHighlightFrmHandle; + +// 0x58F498 +CacheEntry* gGameDialogReviewButtonUpFrmHandle; + +// 0x58F49C +int gGameDialogLowerHighlightFrmHeight; + +// 0x58F4A0 +CacheEntry* gGameDialogReviewButtonDownFrmHandle; + +// 0x58F4A4 +unsigned char* gGameDialogRedButtonDownFrmData; + +// 0x58F4A8 +int gGameDialogLowerHighlightFrmWidth; + +// 0x58F4AC +unsigned char* gGameDialogRedButtonUpFrmData; + +// 0x58F4B0 +int gGameDialogUpperHighlightFrmWidth; + +// Yellow highlight blick effect. +// +// 0x58F4B4 +Art* gGameDialogLowerHighlightFrm; + +// 0x58F4B8 +int gGameDialogUpperHighlightFrmHeight; + +// 0x58F4BC +CacheEntry* gGameDialogRedButtonDownFrmHandle; + +// 0x58F4C0 +CacheEntry* gGameDialogLowerHighlightFrmHandle; + +// 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 +Art* gGameDialogUpperHighlightFrm; + +// 0x58F4C8 +int _oldFont; + +// 0x58F4CC +unsigned int gGameDialogFidgetLastUpdateTimestamp; + +// 0x58F4D0 +int gGameDialogFidgetReaction; + +// 0x58F4D4 +Program* gDialogReplyProgram; + +// 0x58F4D8 +int gDialogReplyMessageListId; + +// 0x58F4DC +int gDialogReplyMessageId; + +// 0x58F4E0 +int dword_58F4E0; + +// NOTE: The is something odd about this variable. 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. +// +// 0x58F4E4 +char gDialogReplyText[900]; + +// 0x58FF70 +GameDialogOptionEntry gDialogOptionEntries[DIALOG_OPTION_ENTRIES_CAPACITY]; + +// 0x596C30 +int _talkOldFont; + +// 0x596C34 +unsigned int gGameDialogFidgetUpdateDelay; + +// 0x596C38 +int gGameDialogFidgetFrmCurrentFrame; + +// gdialog_init +// 0x444D1C +int gameDialogInit() +{ + return 0; +} + +// 0x444D20 +int _gdialogReset() +{ + gameDialogEndLips(); + return 0; +} + +// NOTE: Uncollapsed 0x444D20. +int gameDialogReset() +{ + return _gdialogReset(); +} + +// NOTE: Uncollapsed 0x444D20. +int gameDialogExit() +{ + return _gdialogReset(); +} + +// 0x444D2C +bool _gdialogActive() +{ + return _dialog_state_fix != 0; +} + +// gdialogEnter +// 0x444D3C +void gameDialogEnter(Object* a1, int a2) +{ + if (a1 == NULL) { + debugPrint("\nError: gdialogEnter: target was NULL!"); + return; + } + + _gdDialogWentOff = false; + + if (isInCombat()) { + return; + } + + if (a1->sid == -1) { + return; + } + + if ((a1->pid >> 24) != OBJ_TYPE_ITEM && (a1->pid >> 24) != OBJ_TYPE_CRITTER) { + MessageListItem messageListItem; + + int rc = _action_can_talk_to(gDude, a1); + if (rc == -1) { + // You can't see there. + messageListItem.num = 660; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + if (a2) { + displayMonitorAddMessage(messageListItem.text); + } else { + debugPrint(messageListItem.text); + } + } else { + debugPrint("\nError: gdialog: Can't find message!"); + } + return; + } + + if (rc == -2) { + // Too far away. + messageListItem.num = 661; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + if (a2) { + displayMonitorAddMessage(messageListItem.text); + } else { + debugPrint(messageListItem.text); + } + } else { + debugPrint("\nError: gdialog: Can't find message!"); + } + return; + } + } + + gGameDialogOldCenterTile = gCenterTile; + gGameDialogBarterModifier = 0; + gGameDialogOldDudeTile = gDude->tile; + isoDisable(); + + _dialog_state_fix = 1; + gGameDialogSpeaker = a1; + gGameDialogSpeakerIsPartyMember = objectIsPartyMember(a1); + + _dialogue_just_started = 1; + + if (a1->sid != -1) { + scriptExecProc(a1->sid, SCRIPT_PROC_TALK); + } + + Script* script; + if (scriptGetScript(a1->sid, &script) == -1) { + gameMouseObjectsShow(); + isoEnable(); + scriptsExecMapUpdateProc(); + _dialog_state_fix = 0; + return; + } + + if (script->scriptOverrides || _dialogue_state != 4) { + _dialogue_just_started = 0; + isoEnable(); + scriptsExecMapUpdateProc(); + _dialog_state_fix = 0; + return; + } + + gameDialogEndLips(); + + 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 = gDude->tile; + if (gGameDialogOldDudeTile != tile) { + gGameDialogOldCenterTile = tile; + } + + if (_gdDialogWentOff) { + _tile_scroll_to(gGameDialogOldCenterTile, 2); + } + + isoEnable(); + scriptsExecMapUpdateProc(); + + _dialog_state_fix = 0; +} + +// 0x444FE4 +void _gdialogSystemEnter() +{ + _game_state_update(); + + _gdDialogTurnMouseOff = true; + + soundContinueAll(); + gameDialogEnter(gGameDialogSpeaker, 0); + soundContinueAll(); + + if (gGameDialogOldDudeTile != gDude->tile) { + gGameDialogOldCenterTile = gDude->tile; + } + + if (_gdDialogWentOff) { + _tile_scroll_to(gGameDialogOldCenterTile, 2); + } + + _game_state_request(2); + + _game_state_update(); +} + +// 0x445050 +void gameDialogStartLips(const char* audioFileName) +{ + if (audioFileName == NULL) { + debugPrint("\nGDialog: Bleep!"); + soundPlayFile("censor"); + return; + } + + char name[16]; + if (artCopyFileName(OBJ_TYPE_HEAD, gGameDialogHeadFid & 0xFFF, name) == -1) { + return; + } + + if (lipsLoad(audioFileName, name) == -1) { + return; + } + + gGameDialogLipSyncStarted = true; + + lipsStart(); + + debugPrint("Starting lipsynch speech"); +} + +// 0x4450C4 +void gameDialogEndLips() +{ + if (gGameDialogLipSyncStarted) { + debugPrint("Ending lipsynch system"); + gGameDialogLipSyncStarted = false; + + lipsFree(); + } +} + +// 0x4450EC +int gameDialogEnable() +{ + tickersAdd(gameDialogTicker); + return 0; +} + +// 0x4450FC +int gameDialogDisable() +{ + tickersRemove(gameDialogTicker); + return 0; +} + +// 0x44510C +int _gdialogInitFromScript(int headFid, int reaction) +{ + if (_dialogue_state == 1) { + return -1; + } + + if (_gdialog_state == 1) { + return 0; + } + + _anim_stop(); + + _boxesWereDisabled = indicatorBarHide(); + gGameDialogSpeakerIsPartyMember = objectIsPartyMember(gGameDialogSpeaker); + _oldFont = fontGetCurrent(); + fontSetCurrent(101); + dialogSetReplyWindow(135, 225, 379, 58, 0); + dialogSetReplyColor(0.3f, 0.3f, 0.3f); + dialogSetOptionWindow(127, 335, 393, 117, 0); + dialogSetOptionColor(0.2f, 0.2f, 0.2f); + dialogSetReplyTitle(NULL); + _dialogRegisterWinDrawCallbacks(_demo_copy_title, _demo_copy_options); + gameDialogPrepareHighlights(); + colorCycleDisable(); + if (_gdDialogTurnMouseOff) { + _gmouse_disable(0); + } + gameMouseObjectsHide(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + textObjectsReset(); + + if ((gGameDialogSpeaker->pid >> 24) != OBJ_TYPE_ITEM) { + _tile_scroll_to(gGameDialogSpeaker->tile, 2); + } + + _talk_need_to_center = 1; + + _gdCreateHeadWindow(); + tickersAdd(gameDialogTicker); + _gdSetupFidget(headFid, reaction); + _gdialog_state = 1; + _gmouse_disable_scrolling(); + + if (headFid == -1) { + gGameDialogOldMusicVolume = _gsound_background_volume_get_set(gGameDialogOldMusicVolume / 2); + } else { + gGameDialogOldMusicVolume = -1; + backgroundSoundDelete(); + } + + _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; + } + + gameDialogEndLips(); + dialogReviewEntriesClear(); + tickersRemove(gameDialogTicker); + + if (gGameDialogSpeaker->pid >> 24 != OBJ_TYPE_ITEM) { + if (gGameDialogOldDudeTile != gDude->tile) { + gGameDialogOldCenterTile = gDude->tile; + } + _tile_scroll_to(gGameDialogOldCenterTile, 2); + } + + _gdDestroyHeadWindow(); + + fontSetCurrent(_oldFont); + + if (gGameDialogFidgetFrm != NULL) { + artUnlock(gGameDialogFidgetFrmHandle); + gGameDialogFidgetFrm = NULL; + } + + if (_lipsKey != NULL) { + if (artUnlock(_lipsKey) == -1) { + debugPrint("Failure unlocking lips frame!\n"); + } + _lipsKey = NULL; + _lipsFp = NULL; + _lipsFID = 0; + } + + _freeColorBlendTable(_colorTable[17969]); + _freeColorBlendTable(_colorTable[22187]); + + artUnlock(gGameDialogUpperHighlightFrmHandle); + artUnlock(gGameDialogLowerHighlightFrmHandle); + + _gdialog_state = 0; + _dialogue_state = 0; + + colorCycleEnable(); + + if (!gameUiIsDisabled()) { + _gmouse_enable_scrolling(); + } + + if (gGameDialogOldMusicVolume == -1) { + backgroundSoundRestart(11); + } else { + backgroundSoundSetVolume(gGameDialogOldMusicVolume); + } + + if (_boxesWereDisabled) { + indicatorBarShow(); + } + + _boxesWereDisabled = 0; + + if (_gdDialogTurnMouseOff) { + if (!gameUiIsDisabled()) { + _gmouse_enable(); + } + + _gdDialogTurnMouseOff = 0; + } + + if (!gameUiIsDisabled()) { + gameMouseObjectsShow(); + } + + _gdDialogWentOff = true; + + return 0; +} + +// 0x445438 +void gameDialogSetBackground(int a1) +{ + if (a1 != -1) { + gGameDialogBackground = a1; + } +} + +// Renders supplementary message in reply area of the dialog. +// +// 0x445448 +void gameDialogRenderSupplementaryMessage(char* msg) +{ + if (_gd_replyWin == -1) { + debugPrint("\nError: Reply window doesn't exist!"); + return; + } + + _replyRect.left = 5; + _replyRect.top = 10; + _replyRect.right = 374; + _replyRect.bottom = 58; + _demo_copy_title(gGameDialogReplyWindow); + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogReplyWindow); + int lineHeight = fontGetLineHeight(); + + int a4 = 0; + gameDialogDrawText(windowBuffer, &_replyRect, msg, &a4, lineHeight, 379, _colorTable[992] | 0x2000000, 1); + + windowUnhide(_gd_replyWin); + windowRefresh(gGameDialogReplyWindow); +} + +// 0x4454FC +int _gdialogStart() +{ + gGameDialogReviewEntriesLength = 0; + gGameDialogOptionEntriesLength = 0; + return 0; +} + +// 0x445510 +int _gdialogSayMessage() +{ + mouseShowCursor(); + _gdialogGo(); + + gGameDialogOptionEntriesLength = 0; + gDialogReplyMessageListId = -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 gameDialogAddMessageOptionWithProcIdentifier(int messageListId, int messageId, const char* proc, int reaction) +{ + gDialogOptionEntries[gGameDialogOptionEntriesLength].proc = 0; + + return gameDialogAddMessageOption(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 gameDialogAddTextOptionWithProcIdentifier(int messageListId, const char* text, const char* proc, int reaction) +{ + gDialogOptionEntries[gGameDialogOptionEntriesLength].proc = 0; + + return gameDialogAddTextOption(messageListId, text, reaction); +} + +// 0x4455B8 +int gameDialogAddMessageOptionWithProc(int messageListId, int messageId, int proc, int reaction) +{ + gDialogOptionEntries[gGameDialogOptionEntriesLength].proc = proc; + + return gameDialogAddMessageOption(messageListId, messageId, reaction); +} + +// 0x4455FC +int gameDialogAddTextOptionWithProc(int messageListId, const char* text, int proc, int reaction) +{ + gDialogOptionEntries[gGameDialogOptionEntriesLength].proc = proc; + + return gameDialogAddTextOption(messageListId, text, reaction); +} + +// 0x445640 +int gameDialogSetMessageReply(Program* program, int messageListId, int messageId) +{ + gameDialogAddReviewMessage(messageListId, messageId); + + gDialogReplyProgram = program; + gDialogReplyMessageListId = messageListId; + gDialogReplyMessageId = messageId; + dword_58F4E0 = 0; + gDialogReplyText[0] = '\0'; + gGameDialogOptionEntriesLength = 0; + + return 0; +} + +// 0x44567C +int gameDialogSetTextReply(Program* program, int messageListId, const char* text) +{ + gameDialogAddReviewText(text); + + gDialogReplyProgram = program; + dword_58F4E0 = 0; + gDialogReplyMessageListId = -4; + gDialogReplyMessageId = -4; + + strcpy(gDialogReplyText, text); + + gGameDialogOptionEntriesLength = 0; + + return 0; +} + +// 0x4456D8 +int _gdialogGo() +{ + if (gDialogReplyMessageListId == -1) { + return 0; + } + + int rc = 0; + + if (gGameDialogOptionEntriesLength < 1) { + gDialogOptionEntries[gGameDialogOptionEntriesLength].proc = 0; + + if (gameDialogAddMessageOption(-1, -1, 50) == -1) { + programFatalError("Error setting option."); + rc = -1; + } + } + + if (rc != -1) { + rc = _gdProcess(); + } + + gGameDialogOptionEntriesLength = 0; + + return rc; +} + +// 0x445764 +void _gdialogUpdatePartyStatus() +{ + if (_dialogue_state != 1) { + return; + } + + bool isPartyMember = objectIsPartyMember(gGameDialogSpeaker); + if (isPartyMember == gGameDialogSpeakerIsPartyMember) { + return; + } + + if (_gd_replyWin != -1) { + windowHide(_gd_replyWin); + } + + if (_gd_optionsWin != -1) { + windowHide(_gd_optionsWin); + } + + _gdialog_window_destroy(); + + gGameDialogSpeakerIsPartyMember = isPartyMember; + + _gdialog_window_create(); + + if (_gd_replyWin != -1) { + windowUnhide(_gd_replyWin); + } + + if (_gd_optionsWin != -1) { + windowUnhide(_gd_optionsWin); + } +} + +// 0x44585C +int gameDialogAddMessageOption(int messageListId, int messageId, int reaction) +{ + if (gGameDialogOptionEntriesLength >= DIALOG_OPTION_ENTRIES_CAPACITY) { + debugPrint("\nError: dialog: Ran out of options!"); + return -1; + } + + GameDialogOptionEntry* optionEntry = &(gDialogOptionEntries[gGameDialogOptionEntriesLength]); + optionEntry->messageListId = messageListId; + optionEntry->messageId = messageId; + optionEntry->reaction = reaction; + optionEntry->btn = -1; + optionEntry->text[0] = '\0'; + + gGameDialogOptionEntriesLength++; + + return 0; +} + +// 0x4458BC +int gameDialogAddTextOption(int messageListId, const char* text, int reaction) +{ + if (gGameDialogOptionEntriesLength >= DIALOG_OPTION_ENTRIES_CAPACITY) { + debugPrint("\nError: dialog: Ran out of options!"); + return -1; + } + + GameDialogOptionEntry* optionEntry = &(gDialogOptionEntries[gGameDialogOptionEntriesLength]); + optionEntry->messageListId = -4; + optionEntry->messageId = -4; + optionEntry->reaction = reaction; + optionEntry->btn = -1; + sprintf(optionEntry->text, "%c %s", '\x95', text); + + gGameDialogOptionEntriesLength++; + + return 0; +} + +// 0x445938 +int gameDialogReviewWindowInit(int* win) +{ + if (gGameDialogLipSyncStarted) { + if (soundIsPlaying(gLipsData.sound)) { + gameDialogEndLips(); + } + } + + gGameDialogReviewWindowOldFont = fontGetCurrent(); + + if (win == NULL) { + return -1; + } + + *win = windowCreate(0, 0, _scr_size.right - _scr_size.left + 1, 480, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + if (*win == -1) { + return -1; + } + + int fid = buildFid(6, 102, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &_reviewBackKey); + if (backgroundFrmData == NULL) { + windowDestroy(*win); + *win = -1; + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(*win); + blitBufferToBuffer(backgroundFrmData, + _scr_size.right - _scr_size.left + 1, + 480, + _scr_size.right - _scr_size.left + 1, + windowBuffer, + _scr_size.right - _scr_size.left + 1); + + artUnlock(_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 = buildFid(6, gGameDialogReviewWindowButtonFrmIds[index], 0, 0, 0); + buttonFrmData[index] = artLockFrameData(fid, 0, 0, &(gGameDialogReviewWindowButtonFrmHandles[index])); + if (buttonFrmData[index] == NULL) { + break; + } + } + + if (index != GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT) { + gameDialogReviewWindowFree(win); + return -1; + } + + int upBtn = buttonCreate(*win, + 475, + 152, + gGameDialogReviewWindowButtonWidths[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP], + gGameDialogReviewWindowButtonHeights[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) { + gameDialogReviewWindowFree(win); + return -1; + } + + buttonSetCallbacks(upBtn, _gsound_med_butt_press, _gsound_med_butt_release); + + int downBtn = buttonCreate(*win, + 475, + 191, + gGameDialogReviewWindowButtonWidths[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN], + gGameDialogReviewWindowButtonHeights[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) { + gameDialogReviewWindowFree(win); + return -1; + } + + buttonSetCallbacks(downBtn, _gsound_med_butt_press, _gsound_med_butt_release); + + int doneBtn = buttonCreate(*win, + 499, + 398, + gGameDialogReviewWindowButtonWidths[GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE], + gGameDialogReviewWindowButtonHeights[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) { + gameDialogReviewWindowFree(win); + return -1; + } + + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + + fontSetCurrent(101); + + windowRefresh(*win); + + tickersRemove(gameDialogTicker); + + int backgroundFid = buildFid(6, 102, 0, 0, 0); + gGameDialogReviewWindowBackgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &gGameDialogReviewWindowBackgroundFrmHandle); + if (gGameDialogReviewWindowBackgroundFrmData == NULL) { + gameDialogReviewWindowFree(win); + return -1; + } + + return 0; +} + +// 0x445C18 +int gameDialogReviewWindowFree(int* win) +{ + tickersAdd(gameDialogTicker); + + for (int index = 0; index < GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT; index++) { + if (gGameDialogReviewWindowButtonFrmHandles[index] != INVALID_CACHE_ENTRY) { + artUnlock(gGameDialogReviewWindowButtonFrmHandles[index]); + gGameDialogReviewWindowButtonFrmHandles[index] = INVALID_CACHE_ENTRY; + } + } + + if (gGameDialogReviewWindowBackgroundFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameDialogReviewWindowBackgroundFrmHandle); + gGameDialogReviewWindowBackgroundFrmHandle = INVALID_CACHE_ENTRY; + gGameDialogReviewWindowBackgroundFrmData = NULL; + } + + fontSetCurrent(gGameDialogReviewWindowOldFont); + + if (win == NULL) { + return -1; + } + + windowDestroy(*win); + *win = -1; + + return 0; +} + +// 0x445CA0 +int gameDialogShowReview() +{ + int win; + + if (gameDialogReviewWindowInit(&win) == -1) { + debugPrint("\nError initializing review window!"); + return -1; + } + + // probably current top line or something like this, which is used to scroll + int v1 = 0; + gameDialogReviewWindowUpdate(win, v1); + + while (true) { + int keyCode = _get_input(); + if (keyCode == 17 || keyCode == 24 || keyCode == 324) { + showQuitConfirmationDialog(); + } + + if (_game_user_wants_to_quit != 0 || keyCode == KEY_ESCAPE) { + break; + } + + // likely scrolling + if (keyCode == 328) { + v1 -= 1; + if (v1 >= 0) { + gameDialogReviewWindowUpdate(win, v1); + } else { + v1 = 0; + } + } else if (keyCode == 336) { + v1 += 1; + if (v1 <= gGameDialogReviewEntriesLength - 1) { + gameDialogReviewWindowUpdate(win, v1); + } else { + v1 = gGameDialogReviewEntriesLength - 1; + } + } + } + + if (gameDialogReviewWindowFree(&win) == -1) { + return -1; + } + + return 0; +} + +// NOTE: Uncollapsed 0x445CA0 with different signature. +void gameDialogReviewButtonOnMouseUp(int btn, int keyCode) +{ + gameDialogShowReview(); +} + +// 0x445D44 +void gameDialogReviewWindowUpdate(int win, int origin) +{ + Rect entriesRect; + entriesRect.left = 113; + entriesRect.top = 76; + entriesRect.right = 422; + entriesRect.bottom = 418; + + int v20 = fontGetLineHeight() + 2; + unsigned char* windowBuffer = windowGetBuffer(win); + if (windowBuffer == NULL) { + debugPrint("\nError: gdialog: review: can't find buffer!"); + return; + } + + int width = _scr_size.right - _scr_size.left + 1; + blitBufferToBuffer( + gGameDialogReviewWindowBackgroundFrmData + width * entriesRect.top + entriesRect.left, + width, + entriesRect.bottom - entriesRect.top + 15, + width, + windowBuffer + width * entriesRect.top + entriesRect.left, + width); + + int y = 76; + for (int index = origin; index < gGameDialogReviewEntriesLength; index++) { + GameDialogReviewEntry* dialogReviewEntry = &(gDialogReviewEntries[index]); + + char name[60]; + sprintf(name, "%s:", objectGetName(gGameDialogSpeaker)); + windowDrawText(win, name, 180, 88, y, _colorTable[992] | 0x2000000); + entriesRect.top += v20; + + char* replyText; + if (dialogReviewEntry->replyMessageListId <= -3) { + replyText = dialogReviewEntry->replyText; + } else { + replyText = _scr_get_msg_str(dialogReviewEntry->replyMessageListId, dialogReviewEntry->replyMessageId); + } + + if (replyText == NULL) { + showMesageBox("\nGDialog::Error Grabbing text message!"); + exit(1); + } + + y = gameDialogDrawText(windowBuffer + 113, &entriesRect, replyText, NULL, fontGetLineHeight(), 640, _colorTable[768] | 0x2000000, 1); + + if (dialogReviewEntry->optionMessageListId != -3) { + sprintf(name, "%s:", objectGetName(gDude)); + windowDrawText(win, name, 180, 88, y, _colorTable[21140] | 0x2000000); + entriesRect.top += v20; + + char* optionText; + if (dialogReviewEntry->optionMessageListId <= -3) { + optionText = dialogReviewEntry->optionText; + } else { + optionText = _scr_get_msg_str(dialogReviewEntry->optionMessageListId, dialogReviewEntry->optionMessageId); + } + + if (optionText == NULL) { + showMesageBox("\nGDialog::Error Grabbing text message!"); + exit(1); + } + + y = gameDialogDrawText(windowBuffer + 113, &entriesRect, optionText, NULL, fontGetLineHeight(), 640, _colorTable[15855] | 0x2000000, 1); + } + + if (y >= 407) { + break; + } + } + + entriesRect.left = 88; + entriesRect.top = 76; + entriesRect.bottom += 14; + entriesRect.right = 434; + windowRefreshRect(win, &entriesRect); +} + +// 0x445FDC +void dialogReviewEntriesClear() +{ + for (int index = 0; index < gGameDialogReviewEntriesLength; index++) { + GameDialogReviewEntry* entry = &(gDialogReviewEntries[index]); + entry->replyMessageListId = 0; + entry->replyMessageId = 0; + + if (entry->replyText != NULL) { + internal_free(entry->replyText); + entry->replyText = NULL; + } + + entry->optionMessageListId = 0; + entry->optionMessageId = 0; + } +} + +// 0x446040 +int gameDialogAddReviewMessage(int messageListId, int messageId) +{ + if (gGameDialogReviewEntriesLength >= DIALOG_REVIEW_ENTRIES_CAPACITY) { + debugPrint("\nError: Ran out of review slots!"); + return -1; + } + + GameDialogReviewEntry* entry = &(gDialogReviewEntries[gGameDialogReviewEntriesLength]); + 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; + + gGameDialogReviewEntriesLength++; + + return 0; +} + +// 0x4460B4 +int gameDialogAddReviewText(const char* string) +{ + if (gGameDialogReviewEntriesLength >= DIALOG_REVIEW_ENTRIES_CAPACITY) { + debugPrint("\nError: Ran out of review slots!"); + return -1; + } + + GameDialogReviewEntry* entry = &(gDialogReviewEntries[gGameDialogReviewEntriesLength]); + entry->replyMessageListId = -4; + entry->replyMessageId = -4; + + if (entry->replyText != NULL) { + internal_free(entry->replyText); + entry->replyText = NULL; + } + + entry->replyText = internal_malloc(strlen(string) + 1); + strcpy(entry->replyText, string); + + entry->optionMessageListId = -3; + entry->optionMessageId = -3; + entry->optionText = NULL; + + gGameDialogReviewEntriesLength++; + + return 0; +} + +// 0x4461A4 +int gameDialogSetReviewOptionMessage(int messageListId, int messageId) +{ + if (gGameDialogReviewEntriesLength >= DIALOG_REVIEW_ENTRIES_CAPACITY) { + debugPrint("\nError: Ran out of review slots!"); + return -1; + } + + GameDialogReviewEntry* entry = &(gDialogReviewEntries[gGameDialogReviewEntriesLength - 1]); + entry->optionMessageListId = messageListId; + entry->optionMessageId = messageId; + entry->optionText = NULL; + + return 0; +} + +// 0x4461F0 +int gameDialogSetReviewOptionText(const char* string) +{ + if (gGameDialogReviewEntriesLength >= DIALOG_REVIEW_ENTRIES_CAPACITY) { + debugPrint("\nError: Ran out of review slots!"); + return -1; + } + + GameDialogReviewEntry* entry = &(gDialogReviewEntries[gGameDialogReviewEntriesLength - 1]); + entry->optionMessageListId = -4; + entry->optionMessageId = -4; + + entry->optionText = internal_malloc(strlen(string) + 1); + strcpy(entry->optionText, string); + + return 0; +} + +// Creates dialog interface. +// +// 0x446288 +int _gdProcessInit() +{ + int fid; + + gGameDialogReplyWindow = windowCreate(135, 225, 379, 58, 256, WINDOW_FLAG_0x04); + if (gGameDialogReplyWindow == -1) { + goto err; + } + + // Top part of the reply window - scroll up. + int upBtn = buttonCreate(gGameDialogReplyWindow, 1, 1, 377, 28, -1, -1, KEY_ARROW_UP, -1, NULL, NULL, NULL, 32); + if (upBtn == -1) { + goto err_1; + } + + buttonSetCallbacks(upBtn, _gsound_red_butt_press, _gsound_red_butt_release); + buttonSetMouseCallbacks(upBtn, _reply_arrow_up, _reply_arrow_restore, 0, 0); + + // Bottom part of the reply window - scroll down. + int downBtn = buttonCreate(gGameDialogReplyWindow, 1, 29, 377, 28, -1, -1, KEY_ARROW_DOWN, -1, NULL, NULL, NULL, 32); + if (downBtn == -1) { + goto err_1; + } + + buttonSetCallbacks(downBtn, _gsound_red_butt_press, _gsound_red_butt_release); + buttonSetMouseCallbacks(downBtn, _reply_arrow_down, _reply_arrow_restore, 0, 0); + + gGameDialogOptionsWindow = windowCreate(127, 335, 393, 117, 256, WINDOW_FLAG_0x04); + if (gGameDialogOptionsWindow == -1) { + goto err_2; + } + + // di_rdbt2.frm - dialog red button down + fid = buildFid(6, 96, 0, 0, 0); + gGameDialogRedButtonUpFrmData = artLockFrameData(fid, 0, 0, &gGameDialogRedButtonUpFrmHandle); + if (gGameDialogRedButtonUpFrmData == NULL) { + goto err_3; + } + + // di_rdbt1.frm - dialog red button up + fid = buildFid(6, 95, 0, 0, 0); + gGameDialogRedButtonDownFrmData = artLockFrameData(fid, 0, 0, &gGameDialogRedButtonDownFrmHandle); + if (gGameDialogRedButtonDownFrmData == NULL) { + goto err_3; + } + + _talkOldFont = fontGetCurrent(); + fontSetCurrent(101); + + return 0; + +err_3: + + artUnlock(gGameDialogRedButtonUpFrmHandle); + gGameDialogRedButtonUpFrmHandle = NULL; + +err_2: + + windowDestroy(gGameDialogOptionsWindow); + gGameDialogOptionsWindow = -1; + +err_1: + + windowDestroy(gGameDialogReplyWindow); + gGameDialogReplyWindow = -1; + +err: + + return -1; +} + +// RELASE: Rename/comment. +// free dialog option buttons +// 0x446454 +void _gdProcessCleanup() +{ + for (int index = 0; index < gGameDialogOptionEntriesLength; index++) { + GameDialogOptionEntry* optionEntry = &(gDialogOptionEntries[index]); + + if (optionEntry->btn != -1) { + buttonDestroy(optionEntry->btn); + optionEntry->btn = -1; + } + } +} + +// RELASE: Rename/comment. +// free dialog interface +// 0x446498 +int _gdProcessExit() +{ + _gdProcessCleanup(); + + artUnlock(gGameDialogRedButtonDownFrmHandle); + gGameDialogRedButtonDownFrmHandle = NULL; + gGameDialogRedButtonDownFrmData = NULL; + + artUnlock(gGameDialogRedButtonUpFrmHandle); + gGameDialogRedButtonUpFrmHandle = NULL; + gGameDialogRedButtonUpFrmData = NULL; + + windowDestroy(gGameDialogReplyWindow); + gGameDialogReplyWindow = -1; + + windowDestroy(gGameDialogOptionsWindow); + gGameDialogOptionsWindow = -1; + + fontSetCurrent(_talkOldFont); + + return 0; +} + +// 0x446504 +void gameDialogRenderCaps() +{ + Rect rect; + rect.left = 5; + rect.right = 70; + rect.top = 36; + rect.bottom = fontGetLineHeight() + 36; + + _talkToRefreshDialogWindowRect(&rect); + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + int caps = itemGetTotalCaps(gDude); + char text[20]; + sprintf(text, "$%d", caps); + + int width = fontGetStringWidth(text); + if (width > 60) { + width = 60; + } + + windowDrawText(gGameDialogWindow, text, width, 38 - width / 2, 36, _colorTable[992] | 0x7000000); + + fontSetCurrent(oldFont); +} + +// 0x4465C0 +int _gdProcess() +{ + if (_gdReenterLevel == 0) { + if (_gdProcessInit() == -1) { + return -1; + } + } + + _gdReenterLevel += 1; + + _gdProcessUpdate(); + + int v18 = 0; + if (dword_58F4E0 != 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) { + showQuitConfirmationDialog(); + } + + if (_game_user_wants_to_quit != 0) { + break; + } + + if (keyCode == KEY_CTRL_B && !_mouse_click_in(135, 225, 514, 283)) { + if (gameMouseGetCursor() != MOUSE_CURSOR_ARROW) { + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + } + } else { + if (_dialogue_switch_mode == 3) { + _dialogue_state = 4; + inventoryOpenTrade(gGameDialogWindow, gGameDialogSpeaker, _peon_table_obj, _barterer_table_obj, gGameDialogBarterModifier); + _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; + partyMemberControlWindowHandleEvents(); + partyMemberControlWindowFree(); + continue; + } else if (_dialogue_switch_mode == 12) { + _dialogue_state = 13; + partyMemberCustomizationWindowHandleEvents(); + partyMemberCustomizationWindowFree(); + continue; + } + + if (keyCode == KEY_LOWERCASE_B) { + gameDialogBarterButtonUpMouseUp(-1, -1); + } + } + + if (_gdReplyTooBig) { + unsigned int v6 = _get_bk_time(); + if (v18) { + if (getTicksBetween(v6, tick) >= 10000 || keyCode == KEY_SPACE) { + pageCount++; + pageIndex++; + pageOffsets[pageCount] = dword_58F4E0; + gameDialogRenderReply(); + tick = v6; + if (!dword_58F4E0) { + v18 = 0; + } + } + } + + if (keyCode == KEY_ARROW_UP) { + if (pageIndex > 0) { + pageIndex--; + dword_58F4E0 = pageOffsets[pageIndex]; + v18 = 0; + gameDialogRenderReply(); + } + } else if (keyCode == KEY_ARROW_DOWN) { + if (pageIndex < pageCount) { + pageIndex++; + dword_58F4E0 = pageOffsets[pageIndex]; + v18 = 0; + gameDialogRenderReply(); + } else { + if (dword_58F4E0 != 0) { + tick = v6; + pageIndex++; + pageCount++; + pageOffsets[pageCount] = dword_58F4E0; + v18 = 0; + gameDialogRenderReply(); + } + } + } + } + + if (keyCode != -1) { + if (keyCode >= 1200 && keyCode <= 1250) { + gameDialogOptionOnMouseEnter(keyCode - 1200); + } else if (keyCode >= 1300 && keyCode <= 1330) { + gameDialogOptionOnMouseExit(keyCode - 1300); + } else if (keyCode >= 48 && keyCode <= 57) { + int v11 = keyCode - 49; + if (v11 < gGameDialogOptionEntriesLength) { + pageCount = 0; + pageIndex = 0; + pageOffsets[0] = 0; + _gdReplyTooBig = 0; + + if (_gdProcessChoice(v11) == -1) { + break; + } + + tick = _get_time(); + + if (dword_58F4E0) { + v18 = 1; + _gdReplyTooBig = 1; + } else { + v18 = 0; + } + } + } + } + } + + _gdReenterLevel -= 1; + + if (_gdReenterLevel == 0) { + if (_gdProcessExit() == -1) { + return -1; + } + } + + return 0; +} + +// 0x4468DC +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 `gDialogReplyText` (within 0x58F868-0x58FF70 range) which + // is initialized to 0 according to C spec. I was not able to replicate the + // same behaviour by extending gDialogReplyText 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. + GameDialogOptionEntry dummy; + memset(&dummy, 0, sizeof(dummy)); + + mouseHideCursor(); + _gdProcessCleanup(); + + GameDialogOptionEntry* dialogOptionEntry = a1 != -1 ? &(gDialogOptionEntries[a1]) : &dummy; + if (dialogOptionEntry->messageListId == -4) { + gameDialogSetReviewOptionText(dialogOptionEntry->text); + } else { + gameDialogSetReviewOptionMessage(dialogOptionEntry->messageListId, dialogOptionEntry->messageId); + } + + _can_start_new_fidget = false; + + gameDialogEndLips(); + + 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; + debugPrint("\nError: dialog: Empathy Perk: invalid reaction!"); + break; + } + + _demo_copy_title(gGameDialogReplyWindow); + _demo_copy_options(gGameDialogOptionsWindow); + windowRefresh(gGameDialogReplyWindow); + windowRefresh(gGameDialogOptionsWindow); + + gameDialogOptionOnMouseEnter(a1); + _talk_to_critter_reacts(v1); + + gGameDialogOptionEntriesLength = 0; + + if (_gdReenterLevel < 2) { + if (dialogOptionEntry->proc != 0) { + _executeProcedure(gDialogReplyProgram, dialogOptionEntry->proc); + } + } + + mouseShowCursor(); + + if (gGameDialogOptionEntriesLength == 0) { + return -1; + } + + _gdProcessUpdate(); + + return 0; +} + +// 0x446A18 +void gameDialogOptionOnMouseEnter(int index) +{ + // FIXME: See explanation in `_gdProcessChoice`. + GameDialogOptionEntry dummy; + memset(&dummy, 0, sizeof(dummy)); + + GameDialogOptionEntry* dialogOptionEntry = index != -1 ? &(gDialogOptionEntries[index]) : &dummy; + if (dialogOptionEntry->btn == 0) { + return; + } + + _optionRect.left = 0; + _optionRect.top = dialogOptionEntry->field_14; + _optionRect.right = 391; + _optionRect.bottom = dialogOptionEntry->field_39C; + _gDialogRefreshOptionsRect(gGameDialogOptionsWindow, &_optionRect); + + _optionRect.left = 5; + _optionRect.right = 388; + + int color = _colorTable[32747] | 0x2000000; + if (perkHasRank(gDude, 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: + debugPrint("\nError: dialog: Empathy Perk: invalid reaction!"); + break; + } + } + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogOptionsWindow); + gameDialogDrawText(windowBuffer, &_optionRect, dialogOptionEntry->text, NULL, fontGetLineHeight(), 393, color, 1); + + _optionRect.left = 0; + _optionRect.right = 391; + _optionRect.top = dialogOptionEntry->field_14; + windowRefreshRect(gGameDialogOptionsWindow, &_optionRect); +} + +// 0x446B5C +void gameDialogOptionOnMouseExit(int index) +{ + GameDialogOptionEntry* dialogOptionEntry = &(gDialogOptionEntries[index]); + + _optionRect.left = 0; + _optionRect.top = dialogOptionEntry->field_14; + _optionRect.right = 391; + _optionRect.bottom = dialogOptionEntry->field_39C; + _gDialogRefreshOptionsRect(gGameDialogOptionsWindow, &_optionRect); + + int color = _colorTable[992] | 0x2000000; + if (perkGetRank(gDude, 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: + debugPrint("\nError: dialog: Empathy Perk: invalid reaction!"); + break; + } + } + + _optionRect.left = 5; + _optionRect.right = 388; + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogOptionsWindow); + gameDialogDrawText(windowBuffer, &_optionRect, dialogOptionEntry->text, NULL, fontGetLineHeight(), 393, color, 1); + + _optionRect.right = 391; + _optionRect.top = dialogOptionEntry->field_14; + _optionRect.left = 0; + windowRefreshRect(gGameDialogOptionsWindow, &_optionRect); +} + +// 0x446C94 +void gameDialogRenderReply() +{ + _replyRect.left = 5; + _replyRect.top = 10; + _replyRect.right = 374; + _replyRect.bottom = 58; + + // NOTE: There is an unused if condition. + perkGetRank(gDude, PERK_EMPATHY); + + _demo_copy_title(gGameDialogReplyWindow); + + // Render reply. + unsigned char* windowBuffer = windowGetBuffer(gGameDialogReplyWindow); + gameDialogDrawText(windowBuffer, + &_replyRect, + gDialogReplyText, + &dword_58F4E0, + fontGetLineHeight(), + 379, + _colorTable[992] | 0x2000000, + 1); + windowRefresh(gGameDialogReplyWindow); +} + +// 0x446D30 +void _gdProcessUpdate() +{ + _replyRect.left = 5; + _replyRect.top = 10; + _replyRect.right = 374; + _replyRect.bottom = 58; + + _optionRect.left = 5; + _optionRect.top = 5; + _optionRect.right = 388; + _optionRect.bottom = 112; + + _demo_copy_title(gGameDialogReplyWindow); + _demo_copy_options(gGameDialogOptionsWindow); + + if (gDialogReplyMessageListId > 0) { + char* s = _scr_get_msg_str_speech(gDialogReplyMessageListId, gDialogReplyMessageId, 1); + if (s == NULL) { + showMesageBox("\n'GDialog::Error Grabbing text message!"); + exit(1); + } + + strncpy(gDialogReplyText, s, sizeof(gDialogReplyText) - 1); + *(gDialogReplyText + sizeof(gDialogReplyText) - 1) = '\0'; + } + + gameDialogRenderReply(); + + int color = _colorTable[992] | 0x2000000; + + bool hasEmpathy = perkGetRank(gDude, PERK_EMPATHY) != 0; + + int width = _optionRect.right - _optionRect.left - 4; + + MessageListItem messageListItem; + + int v21 = 0; + + for (int index = 0; index < gGameDialogOptionEntriesLength; index++) { + GameDialogOptionEntry* dialogOptionEntry = &(gDialogOptionEntries[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: + debugPrint("\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) { + showMesageBox("\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(gDude, STAT_INTELLIGENCE) < 4) { + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + strcpy(dialogOptionEntry->text, messageListItem.text); + } else { + debugPrint("\nError...can't find message!"); + return; + } + } + } else { + // TODO: Why only space? + strcpy(dialogOptionEntry->text, " "); + } + } else if (dialogOptionEntry->messageListId == -2) { + // [Done] + messageListItem.num = 650; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + sprintf(dialogOptionEntry->text, "%c %s", '\x95', messageListItem.text); + } else { + debugPrint("\nError...can't find message!"); + return; + } + } + + int v11 = _text_num_lines(dialogOptionEntry->text, _optionRect.right - _optionRect.left) * fontGetLineHeight() + _optionRect.top + 2; + if (v11 < _optionRect.bottom) { + int y = _optionRect.top; + + dialogOptionEntry->field_39C = v11; + dialogOptionEntry->field_14 = y; + + if (index == 0) { + y = 0; + } + + gameDialogDrawText(windowGetBuffer(gGameDialogOptionsWindow), + &_optionRect, + dialogOptionEntry->text, + NULL, + fontGetLineHeight(), + 393, + color, + 1); + + _optionRect.top += 2; + + dialogOptionEntry->btn = buttonCreate(gGameDialogOptionsWindow, 2, y, width, _optionRect.top - y - 4, 1200 + index, 1300 + index, -1, 49 + index, NULL, NULL, NULL, 0); + if (dialogOptionEntry->btn != -1) { + buttonSetCallbacks(dialogOptionEntry->btn, _gsound_red_butt_press, _gsound_red_butt_release); + } else { + debugPrint("\nError: Can't create button!"); + } + } else { + if (!v21) { + v21 = 1; + } else { + debugPrint("Error: couldn't make button because it went below the window.\n"); + } + } + } + + gameDialogRenderCaps(); + windowRefresh(gGameDialogReplyWindow); + windowRefresh(gGameDialogOptionsWindow); +} + +// 0x44715C +int _gdCreateHeadWindow() +{ + _dialogue_state = 1; + + int windowWidth = _scr_size.right - _scr_size.left + 1; + + gGameDialogBackgroundWindow = windowCreate(0, 0, windowWidth, 480, 256, WINDOW_FLAG_0x02); + gameDialogWindowRenderBackground(); + + unsigned char* buf = windowGetBuffer(gGameDialogBackgroundWindow); + + for (int index = 0; index < 8; index++) { + soundContinueAll(); + + Rect* rect = &(_backgrndRects[index]); + int width = rect->right - rect->left; + int height = rect->bottom - rect->top; + _backgrndBufs[index] = internal_malloc(width * height); + if (_backgrndBufs[index] == NULL) { + return -1; + } + + unsigned char* src = buf; + src += windowWidth * rect->top + rect->left; + + blitBufferToBuffer(src, width, height, windowWidth, _backgrndBufs[index], width); + } + + _gdialog_window_create(); + + gGameDialogDisplayBuffer = windowGetBuffer(gGameDialogBackgroundWindow) + windowWidth * 14 + 126; + + // TODO: jnz at 0x447275 without cmp or test, not sure what that means. + if (false) { + _gdDestroyHeadWindow(); + return -1; + } + + return 0; +} + +// 0x447294 +void _gdDestroyHeadWindow() +{ + if (gGameDialogWindow != -1) { + gGameDialogDisplayBuffer = NULL; + } + + if (_dialogue_state == 1) { + _gdialog_window_destroy(); + } else if (_dialogue_state == 4) { + _gdialog_barter_destroy_win(); + } + + if (gGameDialogBackgroundWindow != -1) { + windowDestroy(gGameDialogBackgroundWindow); + gGameDialogBackgroundWindow = -1; + } + + for (int index = 0; index < 8; index++) { + internal_free(_backgrndBufs[index]); + } +} + +// 0x447300 +void _gdSetupFidget(int headFrmId, int reaction) +{ + gGameDialogFidgetFrmCurrentFrame = 0; + + if (headFrmId == -1) { + gGameDialogFidgetFid = -1; + gGameDialogFidgetFrm = NULL; + gGameDialogFidgetFrmHandle = INVALID_CACHE_ENTRY; + gGameDialogFidgetReaction = -1; + gGameDialogFidgetUpdateDelay = 0; + gGameDialogFidgetLastUpdateTimestamp = 0; + gameDialogRenderTalkingHead(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 (artUnlock(_lipsKey) == -1) { + debugPrint("failure unlocking lips frame!\n"); + } + _lipsKey = NULL; + _lipsFp = NULL; + _lipsFID = 0; + } + } + + if (_lipsFID == 0) { + _phone_anim = anim; + _lipsFID = buildFid(OBJ_TYPE_HEAD, headFrmId, anim, 0, 0); + _lipsFp = artLock(_lipsFID, &_lipsKey); + if (_lipsFp == NULL) { + debugPrint("failure!\n"); + + char stats[200]; + cachePrintStats(&gArtCache, stats); + debugPrint("%s", stats); + } + } + + int fid = buildFid(OBJ_TYPE_HEAD, headFrmId, reaction, 0, 0); + int fidgetCount = artGetFidgetCount(fid); + if (fidgetCount == -1) { + debugPrint("\tError - No available fidgets for given frame id\n"); + return; + } + + int chance = randomBetween(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; + } + + debugPrint("Choosing fidget %d out of %d\n", fidget, fidgetCount); + + if (gGameDialogFidgetFrm != NULL) { + if (artUnlock(gGameDialogFidgetFrmHandle) == -1) { + debugPrint("failure!\n"); + } + } + + gGameDialogFidgetFid = buildFid(OBJ_TYPE_HEAD, headFrmId, reaction, fidget, 0); + gGameDialogFidgetFrmCurrentFrame = 0; + gGameDialogFidgetFrm = artLock(gGameDialogFidgetFid, &gGameDialogFidgetFrmHandle); + if (gGameDialogFidgetFrm == NULL) { + debugPrint("failure!\n"); + + char stats[200]; + cachePrintStats(&gArtCache, stats); + debugPrint("%s", stats); + } + + gGameDialogFidgetLastUpdateTimestamp = 0; + gGameDialogFidgetReaction = reaction; + gGameDialogFidgetUpdateDelay = 1000 / artGetFramesPerSecond(gGameDialogFidgetFrm); +} + +// 0x447598 +void gameDialogWaitForFidgetToComplete() +{ + if (gGameDialogFidgetFrm == NULL) { + return; + } + + if (gGameDialogWindow == -1) { + return; + } + + debugPrint("Waiting for fidget to complete...\n"); + + while (artGetFrameCount(gGameDialogFidgetFrm) > gGameDialogFidgetFrmCurrentFrame) { + if (getTicksSince(gGameDialogFidgetLastUpdateTimestamp) >= gGameDialogFidgetUpdateDelay) { + gameDialogRenderTalkingHead(gGameDialogFidgetFrm, gGameDialogFidgetFrmCurrentFrame); + gGameDialogFidgetLastUpdateTimestamp = _get_time(); + gGameDialogFidgetFrmCurrentFrame++; + } + } + + gGameDialogFidgetFrmCurrentFrame = 0; +} + +// 0x447614 +void _gdPlayTransition(int anim) +{ + if (gGameDialogFidgetFrm == NULL) { + return; + } + + if (gGameDialogWindow == -1) { + return; + } + + mouseHideCursor(); + + debugPrint("Starting transition...\n"); + + gameDialogWaitForFidgetToComplete(); + + if (gGameDialogFidgetFrm != NULL) { + if (artUnlock(gGameDialogFidgetFrmHandle) == -1) { + debugPrint("\tError unlocking fidget in transition func..."); + } + gGameDialogFidgetFrm = NULL; + } + + CacheEntry* headFrmHandle; + int headFid = buildFid(OBJ_TYPE_HEAD, gGameDialogHeadFid, anim, 0, 0); + Art* headFrm = artLock(headFid, &headFrmHandle); + if (headFrm == NULL) { + debugPrint("\tError locking transition...\n"); + } + + unsigned int delay = 1000 / artGetFramesPerSecond(headFrm); + + int frame = 0; + unsigned int time = 0; + while (frame < artGetFrameCount(headFrm)) { + if (getTicksSince(time) >= delay) { + gameDialogRenderTalkingHead(headFrm, frame); + time = _get_time(); + frame++; + } + } + + if (artUnlock(headFrmHandle) == -1) { + debugPrint("\tError unlocking transition...\n"); + } + + debugPrint("Finished transition...\n"); + mouseShowCursor(); +} + +// 0x447724 +void _reply_arrow_up(int btn, int keyCode) +{ + if (_gdReplyTooBig) { + gameMouseSetCursor(MOUSE_CURSOR_SMALL_ARROW_UP); + } +} + +// 0x447738 +void _reply_arrow_down(int btn, int keyCode) +{ + if (_gdReplyTooBig) { + gameMouseSetCursor(MOUSE_CURSOR_SMALL_ARROW_DOWN); + } +} + +// 0x44774C +void _reply_arrow_restore(int btn, int keyCode) +{ + gameMouseSetCursor(MOUSE_CURSOR_ARROW); +} + +// demo_copy_title +// 0x447758 +void _demo_copy_title(int win) +{ + _gd_replyWin = win; + + if (win == -1) { + debugPrint("\nError: demo_copy_title: win invalid!"); + return; + } + + int width = windowGetWidth(win); + if (width < 1) { + debugPrint("\nError: demo_copy_title: width invalid!"); + return; + } + + int height = windowGetHeight(win); + if (height < 1) { + debugPrint("\nError: demo_copy_title: length invalid!"); + return; + } + + if (gGameDialogBackgroundWindow == -1) { + debugPrint("\nError: demo_copy_title: dialogueBackWindow wasn't created!"); + return; + } + + unsigned char* src = windowGetBuffer(gGameDialogBackgroundWindow); + if (src == NULL) { + debugPrint("\nError: demo_copy_title: couldn't get buffer!"); + return; + } + + unsigned char* dest = windowGetBuffer(win); + + blitBufferToBuffer(src + 640 * 225 + 135, width, height, 640, dest, width); +} + +// demo_copy_options +// 0x447818 +void _demo_copy_options(int win) +{ + _gd_optionsWin = win; + + if (win == -1) { + debugPrint("\nError: demo_copy_options: win invalid!"); + return; + } + + int width = windowGetWidth(win); + if (width < 1) { + debugPrint("\nError: demo_copy_options: width invalid!"); + return; + } + + int height = windowGetHeight(win); + if (height < 1) { + debugPrint("\nError: demo_copy_options: length invalid!"); + return; + } + + if (gGameDialogBackgroundWindow == -1) { + debugPrint("\nError: demo_copy_options: dialogueBackWindow wasn't created!"); + return; + } + + Rect windowRect; + windowGetRect(gGameDialogWindow, &windowRect); + + unsigned char* src = windowGetBuffer(gGameDialogWindow); + if (src == NULL) { + debugPrint("\nError: demo_copy_options: couldn't get buffer!"); + return; + } + + unsigned char* dest = windowGetBuffer(win); + blitBufferToBuffer(src + 640 * (335 - windowRect.top) + 127, width, height, 640, dest, width); +} + +// gDialogRefreshOptionsRect +// 0x447914 +void _gDialogRefreshOptionsRect(int win, Rect* drawRect) +{ + if (drawRect == NULL) { + debugPrint("\nError: gDialogRefreshOptionsRect: drawRect NULL!"); + return; + } + + if (win == -1) { + debugPrint("\nError: gDialogRefreshOptionsRect: win invalid!"); + return; + } + + if (gGameDialogBackgroundWindow == -1) { + debugPrint("\nError: gDialogRefreshOptionsRect: dialogueBackWindow wasn't created!"); + return; + } + + Rect windowRect; + windowGetRect(gGameDialogWindow, &windowRect); + + unsigned char* src = windowGetBuffer(gGameDialogWindow); + if (src == NULL) { + debugPrint("\nError: gDialogRefreshOptionsRect: couldn't get buffer!"); + return; + } + + if (drawRect->top >= drawRect->bottom) { + debugPrint("\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!"); + return; + } + + if (drawRect->left >= drawRect->right) { + debugPrint("\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!"); + return; + } + + int destWidth = windowGetWidth(win); + unsigned char* dest = windowGetBuffer(win); + + blitBufferToBuffer( + src + (640 * (335 - windowRect.top) + 127) + (640 * drawRect->top + drawRect->left), + drawRect->right - drawRect->left, + drawRect->bottom - drawRect->top, + 640, + dest + destWidth * drawRect->top, + destWidth); +} + +// 0x447A58 +void gameDialogTicker() +{ + 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(); + if (_gd_replyWin != -1) { + windowUnhide(_gd_replyWin); + } + + if (_gd_optionsWin != -1) { + windowUnhide(_gd_optionsWin); + } + + break; + case 8: + _loop_cnt = -1; + _dialogue_switch_mode = 9; + _gdialog_window_destroy(); + partyMemberControlWindowInit(); + break; + case 11: + _loop_cnt = -1; + _dialogue_switch_mode = 12; + _gdialog_window_destroy(); + partyMemberCustomizationWindowInit(); + break; + } + + if (gGameDialogFidgetFrm == NULL) { + return; + } + + if (gGameDialogLipSyncStarted) { + lipsTicker(); + + if (gLipsPhonemeChanged) { + gameDialogRenderTalkingHead(_lipsFp, _head_phoneme_lookup[gLipsCurrentPhoneme]); + gLipsPhonemeChanged = false; + } + + if (!soundIsPlaying(gLipsData.sound)) { + gameDialogEndLips(); + gameDialogRenderTalkingHead(_lipsFp, 0); + _can_start_new_fidget = true; + _dialogue_seconds_since_last_input = 3; + gGameDialogFidgetFrmCurrentFrame = 0; + } + return; + } + + if (_can_start_new_fidget) { + if (getTicksSince(gGameDialogFidgetLastUpdateTimestamp) >= _tocksWaiting) { + _can_start_new_fidget = false; + _dialogue_seconds_since_last_input += _tocksWaiting / 1000; + _tocksWaiting = 1000 * (randomBetween(0, 3) + 4); + _gdSetupFidget(gGameDialogFidgetFid & 0xFFF, (gGameDialogFidgetFid & 0xFF0000) >> 16); + } + return; + } + + if (getTicksSince(gGameDialogFidgetLastUpdateTimestamp) >= gGameDialogFidgetUpdateDelay) { + if (artGetFrameCount(gGameDialogFidgetFrm) <= gGameDialogFidgetFrmCurrentFrame) { + gameDialogRenderTalkingHead(gGameDialogFidgetFrm, 0); + _can_start_new_fidget = true; + } else { + gameDialogRenderTalkingHead(gGameDialogFidgetFrm, gGameDialogFidgetFrmCurrentFrame); + gGameDialogFidgetLastUpdateTimestamp = _get_time(); + gGameDialogFidgetFrmCurrentFrame += 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; + + debugPrint("Dialogue Reaction: "); + if (v1 < 3) { + debugPrint("%s\n", _react_strs[v1]); + } + + int v3 = a1 + 50; + _dialogue_seconds_since_last_input = 0; + + switch (v3) { + case GAME_DIALOG_REACTION_GOOD: + switch (gGameDialogFidgetReaction) { + case FIDGET_GOOD: + _gdPlayTransition(HEAD_ANIMATION_VERY_GOOD_REACTION); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_GOOD); + break; + case FIDGET_NEUTRAL: + _gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_GOOD); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_GOOD); + break; + case FIDGET_BAD: + _gdPlayTransition(HEAD_ANIMATION_BAD_TO_NEUTRAL); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_NEUTRAL); + break; + } + break; + case GAME_DIALOG_REACTION_NEUTRAL: + break; + case GAME_DIALOG_REACTION_BAD: + switch (gGameDialogFidgetReaction) { + case FIDGET_GOOD: + _gdPlayTransition(HEAD_ANIMATION_GOOD_TO_NEUTRAL); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_NEUTRAL); + break; + case FIDGET_NEUTRAL: + _gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_BAD); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_BAD); + break; + case FIDGET_BAD: + _gdPlayTransition(HEAD_ANIMATION_VERY_BAD_REACTION); + _gdSetupFidget(gGameDialogHeadFid, FIDGET_BAD); + break; + } + break; + } +} + +// 0x447D98 +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.left = 0; + rect.right = _scr_size.right - _scr_size.left; + rect.bottom = a6 - 1; + + int v18 = a6 / 10; + if (a7 == -1) { + rect.top = 10; + v18 = 0; + } else { + rect.top = v18 * 10; + v7 = a6 % 10; + v9 += (_scr_size.right - _scr_size.left + 1) * rect.top; + } + + for (; v18 >= 0; v18--) { + soundContinueAll(); + blitBufferToBuffer(a3, + _scr_size.right - _scr_size.left + 1, + v7, + _scr_size.right - _scr_size.left + 1, + v9, + _scr_size.right - _scr_size.left + 1); + rect.top -= 10; + windowRefreshRect(win, &rect); + v7 += 10; + v9 -= 10 * (_scr_size.right - _scr_size.left + 1); + + tick = _get_time(); + while (getTicksSince(tick) < 33) { + } + } + } else { + rect.right = _scr_size.right - _scr_size.left; + rect.bottom = a6 - 1; + rect.left = 0; + rect.top = 0; + + for (int index = a6 / 10; index > 0; index--) { + soundContinueAll(); + + blitBufferToBuffer(a5, + _scr_size.right - _scr_size.left + 1, + 10, + _scr_size.right - _scr_size.left + 1, + v9, + _scr_size.right - _scr_size.left + 1); + + v9 += 10 * (_scr_size.right - _scr_size.left + 1); + v7 -= 10; + a5 += 10 * (_scr_size.right - _scr_size.left + 1); + + blitBufferToBuffer(a3, + _scr_size.right - _scr_size.left + 1, + v7, + _scr_size.right - _scr_size.left + 1, + v9, + _scr_size.right - _scr_size.left + 1); + + windowRefreshRect(win, &rect); + + rect.top += 10; + + tick = _get_time(); + while (getTicksSince(tick) < 33) { + } + } + } +} + +// 0x447F64 +int _text_num_lines(const char* a1, int a2) +{ + int width = fontGetStringWidth(a1); + + int v1 = 0; + while (width > 0) { + width -= a2; + v1++; + } + + return v1; +} + +// display_msg +// 0x447FA0 +int gameDialogDrawText(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->right - rect->left; + char* end = NULL; + while (start != NULL && *start != '\0') { + if (fontGetStringWidth(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 (fontGetStringWidth(start) >= maxWidth) { + *lookahead = ' '; + lookahead = NULL; + } else { + end = lookahead; + *lookahead = ' '; + lookahead++; + } + } + } + + if (*end == ' ') { + *end = '\0'; + } + } else { + if (rect->bottom - fontGetLineHeight() < rect->top) { + return rect->top; + } + + if (a7 != 1 || start == string) { + fontDrawText(buffer + pitch * rect->top + 10, start, maxWidth, pitch, color); + } else { + fontDrawText(buffer + pitch * rect->top, start, maxWidth, pitch, color); + } + + if (a4 != NULL) { + *a4 += strlen(start) + 1; + } + + rect->top += height; + return rect->top; + } + } + + if (fontGetStringWidth(start) > maxWidth) { + debugPrint("\nError: display_msg: word too long!"); + break; + } + + if (a7 != 0) { + if (rect->bottom - fontGetLineHeight() < rect->top) { + if (end != NULL && *end == '\0') { + *end = ' '; + } + return rect->top; + } + + unsigned char* dest; + if (a7 != 1 || start == string) { + dest = buffer + 10; + } else { + dest = buffer; + } + fontDrawText(dest + pitch * rect->top, start, maxWidth, pitch, color); + } + + if (a4 != NULL && end != NULL) { + *a4 += strlen(start) + 1; + } + + rect->top += height; + + if (end != NULL) { + start = end + 1; + if (*end == '\0') { + *end = ' '; + } + end = NULL; + } else { + start = NULL; + } + } + + if (a4 != NULL) { + *a4 = 0; + } + + return rect->top; +} + +// 0x448214 +void gameDialogSetBarterModifier(int modifier) +{ + gGameDialogBarterModifier = modifier; +} + +// gdialog_barter +// 0x44821C +int gameDialogBarter(int modifier) +{ + if (!_dialog_state_fix) { + return -1; + } + + gGameDialogBarterModifier = modifier; + gameDialogBarterButtonUpMouseUp(-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 +int _gdialog_barter_create_win() +{ + _dialogue_state = 4; + + int frmId; + if (gGameDialogSpeakerIsPartyMember) { + // trade.frm - party member barter/trade interface + frmId = 420; + } else { + // barter.frm - barter window + frmId = 111; + } + + int backgroundFid = buildFid(6, frmId, 0, 0, 0); + CacheEntry* backgroundHandle; + Art* backgroundFrm = artLock(backgroundFid, &backgroundHandle); + if (backgroundFrm == NULL) { + return -1; + } + + unsigned char* backgroundData = artGetFrameData(backgroundFrm, 0, 0); + if (backgroundData == NULL) { + artUnlock(backgroundHandle); + return -1; + } + + _dialogue_subwin_len = artGetHeight(backgroundFrm, 0, 0); + gGameDialogWindow = windowCreate(0, 480 - _dialogue_subwin_len, _scr_size.right - _scr_size.left + 1, _dialogue_subwin_len, 256, WINDOW_FLAG_0x02); + if (gGameDialogWindow == -1) { + artUnlock(backgroundHandle); + return -1; + } + + int width = _scr_size.right - _scr_size.left + 1; + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + unsigned char* backgroundWindowBuffer = windowGetBuffer(gGameDialogBackgroundWindow); + blitBufferToBuffer(backgroundWindowBuffer + width * (480 - _dialogue_subwin_len), width, _dialogue_subwin_len, width, windowBuffer, width); + + _gdialog_scroll_subwin(gGameDialogWindow, 1, backgroundData, windowBuffer, NULL, _dialogue_subwin_len, 0); + + artUnlock(backgroundHandle); + + // TRADE + _gdialog_buttons[0] = buttonCreate(gGameDialogWindow, 41, 163, 14, 14, -1, -1, -1, KEY_LOWERCASE_M, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, 0, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[0] != -1) { + buttonSetCallbacks(_gdialog_buttons[0], _gsound_med_butt_press, _gsound_med_butt_release); + + // TALK + _gdialog_buttons[1] = buttonCreate(gGameDialogWindow, 584, 162, 14, 14, -1, -1, -1, KEY_LOWERCASE_T, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, 0, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[1] != -1) { + buttonSetCallbacks(_gdialog_buttons[1], _gsound_med_butt_press, _gsound_med_butt_release); + + if (objectCreateWithFidPid(&_peon_table_obj, -1, -1) != -1) { + _peon_table_obj->flags |= OBJECT_HIDDEN; + + if (objectCreateWithFidPid(&_barterer_table_obj, -1, -1) != -1) { + _barterer_table_obj->flags |= OBJECT_HIDDEN; + + if (objectCreateWithFidPid(&_barterer_temp_obj, gGameDialogSpeaker->fid, -1) != -1) { + _barterer_temp_obj->flags |= OBJECT_HIDDEN | OBJECT_TEMPORARY; + _barterer_temp_obj->sid = -1; + return 0; + } + + objectDestroy(_barterer_table_obj, 0); + } + + objectDestroy(_peon_table_obj, 0); + } + + buttonDestroy(_gdialog_buttons[1]); + _gdialog_buttons[1] = -1; + } + + buttonDestroy(_gdialog_buttons[0]); + _gdialog_buttons[0] = -1; + } + + windowDestroy(gGameDialogWindow); + gGameDialogWindow = -1; + + return -1; +} + +// 0x44854C +void _gdialog_barter_destroy_win() +{ + if (gGameDialogWindow == -1) { + return; + } + + objectDestroy(_barterer_temp_obj, 0); + objectDestroy(_barterer_table_obj, 0); + objectDestroy(_peon_table_obj, 0); + + for (int index = 0; index < 9; index++) { + buttonDestroy(_gdialog_buttons[index]); + _gdialog_buttons[index] = -1; + } + + unsigned char* backgroundWindowBuffer = windowGetBuffer(gGameDialogBackgroundWindow); + backgroundWindowBuffer += (_scr_size.right - _scr_size.left + 1) * (480 - _dialogue_subwin_len); + + int frmId; + if (gGameDialogSpeakerIsPartyMember) { + // trade.frm - party member barter/trade interface + frmId = 420; + } else { + // barter.frm - barter window + frmId = 111; + } + + CacheEntry* backgroundFrmHandle; + int fid = buildFid(6, frmId, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + _gdialog_scroll_subwin(gGameDialogWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + } + + windowDestroy(gGameDialogWindow); + gGameDialogWindow = -1; + + _cai_attempt_w_reload(gGameDialogSpeaker, 0); +} + +// 0x448660 +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, gDude, 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, gGameDialogSpeaker, 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, gGameDialogSpeaker, item, quantity); + } + } +} + +// 0x448740 +int partyMemberControlWindowInit() +{ + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 390, 0, 0, 0); + Art* backgroundFrm = artLock(backgroundFid, &backgroundFrmHandle); + if (backgroundFrm == NULL) { + return -1; + } + + unsigned char* backgroundData = artGetFrameData(backgroundFrm, 0, 0); + if (backgroundData == NULL) { + partyMemberControlWindowFree(); + return -1; + } + + _dialogue_subwin_len = artGetHeight(backgroundFrm, 0, 0); + gGameDialogWindow = windowCreate(0, 480 - _dialogue_subwin_len, _scr_size.right - _scr_size.left + 1, _dialogue_subwin_len, 256, WINDOW_FLAG_0x02); + if (gGameDialogWindow == -1) { + partyMemberControlWindowFree(); + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + unsigned char* src = windowGetBuffer(gGameDialogBackgroundWindow); + blitBufferToBuffer(src + (_scr_size.right - _scr_size.left + 1) * (480 - _dialogue_subwin_len), _scr_size.right - _scr_size.left + 1, _dialogue_subwin_len, _scr_size.right - _scr_size.left + 1, windowBuffer, _scr_size.right - _scr_size.left + 1); + _gdialog_scroll_subwin(gGameDialogWindow, 1, backgroundData, windowBuffer, 0, _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + + // TALK + _gdialog_buttons[0] = buttonCreate(gGameDialogWindow, 593, 41, 14, 14, -1, -1, -1, KEY_ESCAPE, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[0] == -1) { + partyMemberControlWindowFree(); + return -1; + } + buttonSetCallbacks(_gdialog_buttons[0], _gsound_med_butt_press, _gsound_med_butt_release); + + // TRADE + _gdialog_buttons[1] = buttonCreate(gGameDialogWindow, 593, 97, 14, 14, -1, -1, -1, KEY_LOWERCASE_D, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[1] == -1) { + partyMemberControlWindowFree(); + return -1; + } + buttonSetCallbacks(_gdialog_buttons[1], _gsound_med_butt_press, _gsound_med_butt_release); + + // USE BEST WEAPON + _gdialog_buttons[2] = buttonCreate(gGameDialogWindow, 236, 15, 14, 14, -1, -1, -1, KEY_LOWERCASE_W, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[2] == -1) { + partyMemberControlWindowFree(); + return -1; + } + buttonSetCallbacks(_gdialog_buttons[1], _gsound_med_butt_press, _gsound_med_butt_release); + + // USE BEST ARMOR + _gdialog_buttons[3] = buttonCreate(gGameDialogWindow, 235, 46, 14, 14, -1, -1, -1, KEY_LOWERCASE_A, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[3] == -1) { + partyMemberControlWindowFree(); + return -1; + } + buttonSetCallbacks(_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 = &(gGameDialogDispositionButtonsData[index]); + int fid; + + fid = buildFid(6, buttonData->upFrmId, 0, 0, 0); + Art* upButtonFrm = artLock(fid, &(buttonData->upFrmHandle)); + if (upButtonFrm == NULL) { + partyMemberControlWindowFree(); + return -1; + } + + int width = artGetWidth(upButtonFrm, 0, 0); + int height = artGetHeight(upButtonFrm, 0, 0); + unsigned char* upButtonFrmData = artGetFrameData(upButtonFrm, 0, 0); + + fid = buildFid(6, buttonData->downFrmId, 0, 0, 0); + Art* downButtonFrm = artLock(fid, &(buttonData->downFrmHandle)); + if (downButtonFrm == NULL) { + partyMemberControlWindowFree(); + return -1; + } + + unsigned char* downButtonFrmData = artGetFrameData(downButtonFrm, 0, 0); + + fid = buildFid(6, buttonData->disabledFrmId, 0, 0, 0); + Art* disabledButtonFrm = artLock(fid, &(buttonData->disabledFrmHandle)); + if (disabledButtonFrm == NULL) { + partyMemberControlWindowFree(); + return -1; + } + + unsigned char* disabledButtonFrmData = artGetFrameData(disabledButtonFrm, 0, 0); + + v21++; + + _gdialog_buttons[v21] = buttonCreate(gGameDialogWindow, + 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) { + partyMemberControlWindowFree(); + return -1; + } + + _win_register_button_disable(_gdialog_buttons[v21], disabledButtonFrmData, disabledButtonFrmData, disabledButtonFrmData); + buttonSetCallbacks(_gdialog_buttons[v21], _gsound_med_butt_press, _gsound_med_butt_release); + + if (!partyMemberSupportsDisposition(gGameDialogSpeaker, buttonData->value)) { + buttonDisable(_gdialog_buttons[v21]); + } + } + + _win_group_radio_buttons(5, &(_gdialog_buttons[_control_buttons_start])); + + int disposition = aiGetDisposition(gGameDialogSpeaker); + _win_set_button_rest_state(_gdialog_buttons[_control_buttons_start + 4 - disposition], 1, 0); + + partyMemberControlWindowUpdate(); + + _dialogue_state = 10; + + windowRefresh(gGameDialogWindow); + + return 0; +} + +// 0x448C10 +void partyMemberControlWindowFree() +{ + if (gGameDialogWindow == -1) { + return; + } + + for (int index = 0; index < 9; index++) { + buttonDestroy(_gdialog_buttons[index]); + _gdialog_buttons[index] = -1; + } + + for (int index = 0; index < 5; index++) { + GameDialogButtonData* buttonData = &(gGameDialogDispositionButtonsData[index]); + + if (buttonData->upFrmHandle) { + artUnlock(buttonData->upFrmHandle); + buttonData->upFrmHandle = NULL; + } + + if (buttonData->downFrmHandle) { + artUnlock(buttonData->downFrmHandle); + buttonData->downFrmHandle = NULL; + } + + if (buttonData->disabledFrmHandle) { + artUnlock(buttonData->disabledFrmHandle); + buttonData->disabledFrmHandle = NULL; + } + } + + // control.frm - party member control interface + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 390, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + _gdialog_scroll_subwin(gGameDialogWindow, 0, backgroundFrmData, windowGetBuffer(gGameDialogWindow), windowGetBuffer(gGameDialogBackgroundWindow) + (_scr_size.right - _scr_size.left + 1) * (480 - _dialogue_subwin_len), _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + } + + windowDestroy(gGameDialogWindow); + gGameDialogWindow = -1; +} + +// 0x448D30 +void partyMemberControlWindowUpdate() +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + int windowWidth = windowGetWidth(gGameDialogWindow); + + CacheEntry* backgroundHandle; + int backgroundFid = buildFid(6, 390, 0, 0, 0); + Art* background = artLock(backgroundFid, &backgroundHandle); + if (background != NULL) { + int width = artGetWidth(background, 0, 0); + unsigned char* buffer = artGetFrameData(background, 0, 0); + + // Clear "Weapon Used:". + blitBufferToBuffer(buffer + width * 20 + 112, 110, fontGetLineHeight(), width, windowBuffer + windowWidth * 20 + 112, windowWidth); + + // Clear "Armor Used:". + blitBufferToBuffer(buffer + width * 49 + 112, 110, fontGetLineHeight(), width, windowBuffer + windowWidth * 49 + 112, windowWidth); + + // Clear character preview. + blitBufferToBuffer(buffer + width * 84 + 8, 70, 98, width, windowBuffer + windowWidth * 84 + 8, windowWidth); + + // Clear ? + blitBufferToBuffer(buffer + width * 80 + 232, 132, 106, width, windowBuffer + windowWidth * 80 + 232, windowWidth); + + artUnlock(backgroundHandle); + } + + MessageListItem messageListItem; + char* text; + char formattedText[256]; + + // Render item in right hand. + Object* item2 = critterGetItem2(gGameDialogSpeaker); + text = item2 != NULL ? itemGetName(item2) : getmsg(&gProtoMessageList, &messageListItem, 10); + sprintf(formattedText, "%s", text); + fontDrawText(windowBuffer + windowWidth * 20 + 112, formattedText, 110, windowWidth, _colorTable[992]); + + // Render armor. + Object* armor = critterGetArmor(gGameDialogSpeaker); + text = armor != NULL ? itemGetName(armor) : getmsg(&gProtoMessageList, &messageListItem, 10); + sprintf(formattedText, "%s", text); + fontDrawText(windowBuffer + windowWidth * 49 + 112, formattedText, 110, windowWidth, _colorTable[992]); + + // Render preview. + CacheEntry* previewHandle; + int previewFid = buildFid((gGameDialogSpeaker->fid & 0xF000000) >> 24, gGameDialogSpeaker->fid & 0xFFF, ANIM_STAND, (gGameDialogSpeaker->fid & 0xF000) >> 12, ROTATION_SW); + Art* preview = artLock(previewFid, &previewHandle); + if (preview != NULL) { + int width = artGetWidth(preview, 0, ROTATION_SW); + int height = artGetHeight(preview, 0, ROTATION_SW); + unsigned char* buffer = artGetFrameData(preview, 0, ROTATION_SW); + blitBufferToBufferTrans(buffer, width, height, width, windowBuffer + windowWidth * (132 - height / 2) + 39 - width / 2, windowWidth); + artUnlock(previewHandle); + } + + // Render hit points. + int maximumHitPoints = critterGetStat(gGameDialogSpeaker, STAT_MAXIMUM_HIT_POINTS); + int hitPoints = critterGetStat(gGameDialogSpeaker, STAT_CURRENT_HIT_POINTS); + sprintf(formattedText, "%d/%d", hitPoints, maximumHitPoints); + fontDrawText(windowBuffer + windowWidth * 96 + 240, formattedText, 115, windowWidth, _colorTable[992]); + + // Render best skill. + int bestSkill = partyMemberGetBestSkill(gGameDialogSpeaker); + text = skillGetName(bestSkill); + sprintf(formattedText, "%s", text); + fontDrawText(windowBuffer + windowWidth * 113 + 240, formattedText, 115, windowWidth, _colorTable[992]); + + // Render weight summary. + int inventoryWeight = objectGetInventoryWeight(gGameDialogSpeaker); + int carryWeight = critterGetStat(gGameDialogSpeaker, STAT_CARRY_WEIGHT); + sprintf(formattedText, "%d/%d ", inventoryWeight, carryWeight); + fontDrawText(windowBuffer + windowWidth * 131 + 240, formattedText, 115, windowWidth, critterIsEncumbered(gGameDialogSpeaker) ? _colorTable[31744] : _colorTable[992]); + + // Render melee damage. + int meleeDamage = critterGetStat(gGameDialogSpeaker, STAT_MELEE_DAMAGE); + sprintf(formattedText, "%d", meleeDamage); + fontDrawText(windowBuffer + windowWidth * 148 + 240, formattedText, 115, windowWidth, _colorTable[992]); + + int actionPoints; + if (isInCombat()) { + actionPoints = gGameDialogSpeaker->data.critter.combat.ap; + } else { + actionPoints = critterGetStat(gGameDialogSpeaker, STAT_MAXIMUM_ACTION_POINTS); + } + int maximumActionPoints = critterGetStat(gGameDialogSpeaker, STAT_MAXIMUM_ACTION_POINTS); + sprintf(formattedText, "%d/%d ", actionPoints, maximumActionPoints); + fontDrawText(windowBuffer + windowWidth * 167 + 240, formattedText, 115, windowWidth, _colorTable[992]); + + fontSetCurrent(oldFont); + windowRefresh(gGameDialogWindow); +} + +// 0x44928C +void gameDialogCombatControlButtonOnMouseUp(int btn, int keyCode) +{ + _dialogue_switch_mode = 8; + _dialogue_state = 10; + + if (_gd_replyWin != -1) { + windowHide(_gd_replyWin); + } + + if (_gd_optionsWin != -1) { + windowHide(_gd_optionsWin); + } +} + +// 0x4492D0 +int _gdPickAIUpdateMsg(Object* critter) +{ + int pids[3]; + static_assert(sizeof(pids) == sizeof(_Dogs), "wrong size"); + memcpy(pids, _Dogs, sizeof(pids)); + + for (int index = 0; index < 3; index++) { + if (critter->pid == pids[index]) { + return 677 + randomBetween(0, 1); + } + } + + return 670 + randomBetween(0, 4); +} + +// 0x449330 +int _gdCanBarter() +{ + if ((gGameDialogSpeaker->pid >> 24) != OBJ_TYPE_CRITTER) { + return 1; + } + + Proto* proto; + if (protoGetProto(gGameDialogSpeaker->pid, &proto) == -1) { + return 1; + } + + if (proto->critter.data.flags & 0x02) { + return 1; + } + + MessageListItem messageListItem; + + // This person will not barter with you. + messageListItem.num = 903; + if (gGameDialogSpeakerIsPartyMember) { + // This critter can't carry anything. + messageListItem.num = 913; + } + + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + debugPrint("\nError: gdialog: Can't find message!"); + return 0; + } + + gameDialogRenderSupplementaryMessage(messageListItem.text); + + return 0; +} + +// 0x4493B8 +void partyMemberControlWindowHandleEvents() +{ + 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) { + showQuitConfirmationDialog(); + } + + if (_game_user_wants_to_quit != 0) { + break; + } + + if (keyCode == KEY_LOWERCASE_W) { + _inven_unwield(gGameDialogSpeaker, 1); + + Object* weapon = _ai_search_inven_weap(gGameDialogSpeaker, 0, NULL); + if (weapon != NULL) { + _inven_wield(gGameDialogSpeaker, weapon, 1); + _cai_attempt_w_reload(gGameDialogSpeaker, 0); + + int num = _gdPickAIUpdateMsg(gGameDialogSpeaker); + char* msg = getmsg(&gProtoMessageList, &messageListItem, num); + gameDialogRenderSupplementaryMessage(msg); + partyMemberControlWindowUpdate(); + } + } else if (keyCode == 2098) { + aiSetDisposition(gGameDialogSpeaker, 4); + } else if (keyCode == 2099) { + aiSetDisposition(gGameDialogSpeaker, 0); + _dialogue_state = 13; + _dialogue_switch_mode = 11; + done = true; + } else if (keyCode == 2102) { + aiSetDisposition(gGameDialogSpeaker, 2); + } else if (keyCode == 2103) { + aiSetDisposition(gGameDialogSpeaker, 3); + } else if (keyCode == 2111) { + aiSetDisposition(gGameDialogSpeaker, 1); + } else if (keyCode == KEY_ESCAPE) { + _dialogue_switch_mode = 1; + _dialogue_state = 1; + return; + } else if (keyCode == KEY_LOWERCASE_A) { + if (gGameDialogSpeaker->pid != 0x10000A1) { + Object* armor = _ai_search_inven_armor(gGameDialogSpeaker); + if (armor != NULL) { + _inven_wield(gGameDialogSpeaker, armor, 0); + } + } + + int num = _gdPickAIUpdateMsg(gGameDialogSpeaker); + char* msg = getmsg(&gProtoMessageList, &messageListItem, num); + gameDialogRenderSupplementaryMessage(msg); + partyMemberControlWindowUpdate(); + } 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)) { + aiSetDisposition(gGameDialogSpeaker, 0); + _dialogue_state = 13; + _dialogue_switch_mode = 11; + done = true; + } + } + } + } +} + +// 0x4496A0 +int partyMemberCustomizationWindowInit() +{ + if (!messageListInit(&gCustomMessageList)) { + return -1; + } + + if (!messageListLoad(&gCustomMessageList, "game\\custom.msg")) { + return -1; + } + + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 391, 0, 0, 0); + Art* backgroundFrm = artLock(backgroundFid, &backgroundFrmHandle); + if (backgroundFrm == NULL) { + return -1; + } + + unsigned char* backgroundFrmData = artGetFrameData(backgroundFrm, 0, 0); + if (backgroundFrmData == NULL) { + // FIXME: Leaking background. + partyMemberCustomizationWindowFree(); + return -1; + } + + _dialogue_subwin_len = artGetHeight(backgroundFrm, 0, 0); + + int y = 480 - _dialogue_subwin_len; + gGameDialogWindow = windowCreate(0, y, _scr_size.right - _scr_size.left + 1, _dialogue_subwin_len, 256, WINDOW_FLAG_0x02); + if (gGameDialogWindow == -1) { + partyMemberCustomizationWindowFree(); + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + unsigned char* parentWindowBuffer = windowGetBuffer(gGameDialogBackgroundWindow); + blitBufferToBuffer(parentWindowBuffer + y * (_scr_size.right - _scr_size.left + 1), + _scr_size.right - _scr_size.left + 1, + _dialogue_subwin_len, + _scr_size.right - _scr_size.left + 1, + windowBuffer, + _scr_size.right - _scr_size.left + 1); + + _gdialog_scroll_subwin(gGameDialogWindow, 1, backgroundFrmData, windowBuffer, NULL, _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + + _gdialog_buttons[0] = buttonCreate(gGameDialogWindow, 593, 101, 14, 14, -1, -1, -1, 13, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, 0, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[0] == -1) { + partyMemberCustomizationWindowFree(); + return -1; + } + + buttonSetCallbacks(_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 = buildFid(6, buttonData->upFrmId, 0, 0, 0); + Art* upButtonFrm = artLock(upButtonFid, &(buttonData->upFrmHandle)); + if (upButtonFrm == NULL) { + partyMemberCustomizationWindowFree(); + return -1; + } + + int width = artGetWidth(upButtonFrm, 0, 0); + int height = artGetHeight(upButtonFrm, 0, 0); + unsigned char* upButtonFrmData = artGetFrameData(upButtonFrm, 0, 0); + + int downButtonFid = buildFid(6, buttonData->downFrmId, 0, 0, 0); + Art* downButtonFrm = artLock(downButtonFid, &(buttonData->downFrmHandle)); + if (downButtonFrm == NULL) { + partyMemberCustomizationWindowFree(); + return -1; + } + + unsigned char* downButtonFrmData = artGetFrameData(downButtonFrm, 0, 0); + + optionButton++; + _gdialog_buttons[optionButton] = buttonCreate(gGameDialogWindow, + buttonData->x, + buttonData->y, + width, + height, + -1, + -1, + -1, + buttonData->keyCode, + upButtonFrmData, + downButtonFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[optionButton] == -1) { + partyMemberCustomizationWindowFree(); + return -1; + } + + buttonSetCallbacks(_gdialog_buttons[index], _gsound_med_butt_press, _gsound_med_butt_release); + } + + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE] = aiGetAreaAttackMode(gGameDialogSpeaker); + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE] = aiGetRunAwayMode(gGameDialogSpeaker); + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON] = aiGetBestWeapon(gGameDialogSpeaker); + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE] = aiGetDistance(gGameDialogSpeaker); + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO] = aiGetAttackWho(gGameDialogSpeaker); + _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE] = aiGetChemUse(gGameDialogSpeaker); + + _dialogue_state = 13; + + partyMemberCustomizationWindowUpdate(); + + return 0; +} + +// 0x449A10 +void partyMemberCustomizationWindowFree() +{ + if (gGameDialogWindow == -1) { + return; + } + + for (int index = 0; index < 9; index++) { + buttonDestroy(_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) { + artUnlock(buttonData->upFrmHandle); + buttonData->upFrmHandle = NULL; + } + + if (buttonData->downFrmHandle != NULL) { + artUnlock(buttonData->downFrmHandle); + buttonData->downFrmHandle = NULL; + } + + if (buttonData->disabledFrmHandle != NULL) { + artUnlock(buttonData->disabledFrmHandle); + buttonData->disabledFrmHandle = NULL; + } + } + + CacheEntry* backgroundFrmHandle; + // custom.frm - party member control interface + int fid = buildFid(6, 391, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + _gdialog_scroll_subwin(gGameDialogWindow, 0, backgroundFrmData, windowGetBuffer(gGameDialogWindow), windowGetBuffer(gGameDialogBackgroundWindow) + (_scr_size.right - _scr_size.left + 1) * (480 - _dialogue_subwin_len), _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + } + + windowDestroy(gGameDialogWindow); + gGameDialogWindow = -1; + + messageListFree(&gCustomMessageList); +} + +// 0x449B3C +void partyMemberCustomizationWindowHandleEvents() +{ + 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) { + showQuitConfirmationDialog(); + } + + if (_game_user_wants_to_quit != 0) { + break; + } + + if (keyCode <= 5) { + _gdCustomSelect(keyCode); + partyMemberCustomizationWindowUpdate(); + } else if (keyCode == KEY_RETURN || keyCode == KEY_ESCAPE) { + done = true; + _dialogue_switch_mode = 8; + _dialogue_state = 10; + } + } + } +} + +// 0x449BB4 +void partyMemberCustomizationWindowUpdate() +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + int windowWidth = windowGetWidth(gGameDialogWindow); + + CacheEntry* backgroundHandle; + int backgroundFid = buildFid(6, 391, 0, 0, 0); + Art* background = artLock(backgroundFid, &backgroundHandle); + if (background == NULL) { + return; + } + + int backgroundWidth = artGetWidth(background, 0, 0); + int backgroundHeight = artGetHeight(background, 0, 0); + unsigned char* backgroundData = artGetFrameData(background, 0, 0); + blitBufferToBuffer(backgroundData, backgroundWidth, backgroundHeight, backgroundWidth, windowBuffer, _scr_size.right - _scr_size.left + 1); + + artUnlock(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 { + debugPrint("\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(&gCustomMessageList, &messageListItem, num); + fontDrawText(windowBuffer + windowWidth * 20 + 232, msg, 248, windowWidth, _colorTable[992]); + + // RUN AWAY + msg = getmsg(&gCustomMessageList, &messageListItem, _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE][_custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE]].messageId); + fontDrawText(windowBuffer + windowWidth * 48 + 232, msg, 248, windowWidth, _colorTable[992]); + + // WEAPON PREF + msg = getmsg(&gCustomMessageList, &messageListItem, _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON][_custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON]].messageId); + fontDrawText(windowBuffer + windowWidth * 78 + 232, msg, 248, windowWidth, _colorTable[992]); + + // DISTANCE + msg = getmsg(&gCustomMessageList, &messageListItem, _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE][_custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE]].messageId); + fontDrawText(windowBuffer + windowWidth * 108 + 232, msg, 248, windowWidth, _colorTable[992]); + + // ATTACK WHO + msg = getmsg(&gCustomMessageList, &messageListItem, _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO][_custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO]].messageId); + fontDrawText(windowBuffer + windowWidth * 137 + 232, msg, 248, windowWidth, _colorTable[992]); + + // CHEM USE + msg = getmsg(&gCustomMessageList, &messageListItem, _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE][_custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE]].messageId); + fontDrawText(windowBuffer + windowWidth * 166 + 232, msg, 248, windowWidth, _colorTable[992]); + + windowRefresh(gGameDialogWindow); + fontSetCurrent(oldFont); +} + +// 0x449E64 +void _gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex) +{ + MessageListItem messageListItem; + + fontSetCurrent(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 = partyMemberSupportsAreaAttackMode(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: + enabled = partyMemberSupportsRunAwayMode(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: + enabled = partyMemberSupportsBestWeapon(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: + enabled = partyMemberSupportsDistance(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: + enabled = partyMemberSupportsAttackWho(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: + enabled = partyMemberSupportsChemUse(gGameDialogSpeaker, 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(&gCustomMessageList, &messageListItem, ptr->messageId); + fontDrawText(dest + pitch * (fontGetLineHeight() * index + 42) + 42, msg, pitch - 84, pitch, color); + } + } +} + +// 0x449FC0 +int _gdCustomSelect(int a1) +{ + int oldFont = fontGetCurrent(); + + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 419, 0, 0, 0); + Art* backgroundFrm = artLock(backgroundFid, &backgroundFrmHandle); + if (backgroundFrm == NULL) { + return -1; + } + + int backgroundFrmWidth = artGetWidth(backgroundFrm, 0, 0); + int backgroundFrmHeight = artGetHeight(backgroundFrm, 0, 0); + + int x = (640 - backgroundFrmWidth) / 2; + int y = (480 - backgroundFrmHeight) / 2; + int win = windowCreate(x, y, backgroundFrmWidth, backgroundFrmHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + if (win == -1) { + artUnlock(backgroundFrmHandle); + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(win); + unsigned char* backgroundFrmData = artGetFrameData(backgroundFrm, 0, 0); + blitBufferToBuffer(backgroundFrmData, + backgroundFrmWidth, + backgroundFrmHeight, + backgroundFrmWidth, + windowBuffer, + backgroundFrmWidth); + + artUnlock(backgroundFrmHandle); + + int btn1 = buttonCreate(win, 70, 164, 14, 14, -1, -1, -1, KEY_RETURN, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn1 == -1) { + windowDestroy(win); + return -1; + } + + int btn2 = buttonCreate(win, 176, 163, 14, 14, -1, -1, -1, KEY_ESCAPE, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn2 == -1) { + windowDestroy(win); + return -1; + } + + fontSetCurrent(103); + + MessageListItem messageListItem; + const char* msg; + + msg = getmsg(&gCustomMessageList, &messageListItem, a1); + fontDrawText(windowBuffer + backgroundFrmWidth * 15 + 40, msg, backgroundFrmWidth, backgroundFrmWidth, _colorTable[18979]); + + msg = getmsg(&gCustomMessageList, &messageListItem, 10); + fontDrawText(windowBuffer + backgroundFrmWidth * 163 + 88, msg, backgroundFrmWidth, backgroundFrmWidth, _colorTable[18979]); + + msg = getmsg(&gCustomMessageList, &messageListItem, 11); + fontDrawText(windowBuffer + backgroundFrmWidth * 162 + 193, msg, backgroundFrmWidth, backgroundFrmWidth, _colorTable[18979]); + + int value = _custom_current_selected[a1]; + _gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, value); + windowRefresh(win); + + int minX = x + 42; + int minY = y + 42; + int maxX = x + backgroundFrmWidth - 42; + int maxY = y + 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) { + showQuitConfirmationDialog(); + } + + 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 ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { + continue; + } + + if (!_mouse_click_in(minX, minY, maxX, maxY)) { + continue; + } + + int mouseX; + int mouseY; + mouseGetPosition(&mouseX, &mouseY); + + int lineHeight = fontGetLineHeight(); + int newValue = (mouseY - minY) / lineHeight; + if (newValue >= 6) { + continue; + } + + unsigned int timestamp = _get_time(); + if (newValue == value) { + if (getTicksBetween(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 = partyMemberSupportsAreaAttackMode(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: + enabled = partyMemberSupportsRunAwayMode(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: + enabled = partyMemberSupportsBestWeapon(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: + enabled = partyMemberSupportsDistance(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: + enabled = partyMemberSupportsAttackWho(gGameDialogSpeaker, ptr->value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: + enabled = partyMemberSupportsChemUse(gGameDialogSpeaker, ptr->value); + break; + } + + if (enabled) { + value = newValue; + _gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, newValue); + windowRefresh(win); + } + } + } + v53 = timestamp; + } + } + + windowDestroy(win); + fontSetCurrent(oldFont); + return 0; +} + +// 0x44A4E0 +void _gdCustomUpdateSetting(int option, int value) +{ + switch (option) { + case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE: + aiSetAreaAttackMode(gGameDialogSpeaker, value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: + aiSetRunAwayMode(gGameDialogSpeaker, value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: + aiSetBestWeapon(gGameDialogSpeaker, value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: + aiSetDistance(gGameDialogSpeaker, value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: + aiSetAttackWho(gGameDialogSpeaker, value); + break; + case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: + aiSetChemUse(gGameDialogSpeaker, value); + break; + } +} + +// 0x44A52C +void gameDialogBarterButtonUpMouseUp(int btn, int keyCode) +{ + if ((gGameDialogSpeaker->pid >> 24) != OBJ_TYPE_CRITTER) { + return; + } + + Script* script; + if (scriptGetScript(gGameDialogSpeaker->sid, &script) == -1) { + return; + } + + Proto* proto; + protoGetProto(gGameDialogSpeaker->pid, &proto); + if (proto->critter.data.flags & 2) { + if (gGameDialogLipSyncStarted) { + if (soundIsPlaying(gLipsData.sound)) { + gameDialogEndLips(); + } + } + + _dialogue_switch_mode = 2; + _dialogue_state = 4; + + if (_gd_replyWin != -1) { + windowHide(_gd_replyWin); + } + + if (_gd_optionsWin != -1) { + windowHide(_gd_optionsWin); + } + } else { + MessageListItem messageListItem; + // This person will not barter with you. + messageListItem.num = 903; + if (gGameDialogSpeakerIsPartyMember) { + // This critter can't carry anything. + messageListItem.num = 913; + } + + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + gameDialogRenderSupplementaryMessage(messageListItem.text); + } else { + debugPrint("\nError: gdialog: Can't find message!"); + } + } +} + +// 0x44A62C +int _gdialog_window_create() +{ + const int screenWidth = _scr_size.right - _scr_size.left + 1; + + 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 = buildFid(6, gGameDialogSpeakerIsPartyMember ? 389 : 99, 0, 0, 0); + Art* backgroundFrm = artLock(backgroundFid, &backgroundFrmHandle); + if (backgroundFrm == NULL) { + return -1; + } + + unsigned char* backgroundFrmData = artGetFrameData(backgroundFrm, 0, 0); + if (backgroundFrmData != NULL) { + _dialogue_subwin_len = artGetHeight(backgroundFrm, 0, 0); + + gGameDialogWindow = windowCreate(0, 480 - _dialogue_subwin_len, screenWidth, _dialogue_subwin_len, 256, 2); + if (gGameDialogWindow != -1) { + + unsigned char* v10 = windowGetBuffer(gGameDialogWindow); + unsigned char* v14 = windowGetBuffer(gGameDialogBackgroundWindow); + // TODO: Not sure about offsets. + blitBufferToBuffer(v14 + screenWidth * (480 - _dialogue_subwin_len), screenWidth, _dialogue_subwin_len, screenWidth, v10, screenWidth); + + if (_dialogue_just_started) { + windowRefresh(gGameDialogBackgroundWindow); + _gdialog_scroll_subwin(gGameDialogWindow, 1, backgroundFrmData, v10, 0, _dialogue_subwin_len, -1); + _dialogue_just_started = 0; + } else { + _gdialog_scroll_subwin(gGameDialogWindow, 1, backgroundFrmData, v10, 0, _dialogue_subwin_len, 0); + } + + artUnlock(backgroundFrmHandle); + + // BARTER/TRADE + _gdialog_buttons[0] = buttonCreate(gGameDialogWindow, 593, 41, 14, 14, -1, -1, -1, -1, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[0] != -1) { + buttonSetMouseCallbacks(_gdialog_buttons[0], NULL, NULL, NULL, gameDialogBarterButtonUpMouseUp); + buttonSetCallbacks(_gdialog_buttons[0], _gsound_med_butt_press, _gsound_med_butt_release); + + // di_rest1.frm - dialog rest button up + int upFid = buildFid(6, 97, 0, 0, 0); + unsigned char* reviewButtonUpData = artLockFrameData(upFid, 0, 0, &gGameDialogReviewButtonUpFrmHandle); + if (reviewButtonUpData != NULL) { + // di_rest2.frm - dialog rest button down + int downFid = buildFid(6, 98, 0, 0, 0); + unsigned char* reivewButtonDownData = artLockFrameData(downFid, 0, 0, &gGameDialogReviewButtonDownFrmHandle); + if (reivewButtonDownData != NULL) { + // REVIEW + _gdialog_buttons[1] = buttonCreate(gGameDialogWindow, 13, 154, 51, 29, -1, -1, -1, -1, reviewButtonUpData, reivewButtonDownData, NULL, 0); + if (_gdialog_buttons[1] != -1) { + buttonSetMouseCallbacks(_gdialog_buttons[1], NULL, NULL, NULL, gameDialogReviewButtonOnMouseUp); + buttonSetCallbacks(_gdialog_buttons[1], _gsound_red_butt_press, _gsound_red_butt_release); + + if (!gGameDialogSpeakerIsPartyMember) { + _gdialog_window_created = true; + return 0; + } + + // COMBAT CONTROL + _gdialog_buttons[2] = buttonCreate(gGameDialogWindow, 593, 116, 14, 14, -1, -1, -1, -1, gGameDialogRedButtonUpFrmData, gGameDialogRedButtonDownFrmData, 0, BUTTON_FLAG_TRANSPARENT); + if (_gdialog_buttons[2] != -1) { + buttonSetMouseCallbacks(_gdialog_buttons[2], NULL, NULL, NULL, gameDialogCombatControlButtonOnMouseUp); + buttonSetCallbacks(_gdialog_buttons[2], _gsound_med_butt_press, _gsound_med_butt_release); + + _gdialog_window_created = true; + return 0; + } + + buttonDestroy(_gdialog_buttons[1]); + _gdialog_buttons[1] = -1; + } + + artUnlock(gGameDialogReviewButtonDownFrmHandle); + } + + artUnlock(gGameDialogReviewButtonUpFrmHandle); + } + + buttonDestroy(_gdialog_buttons[0]); + _gdialog_buttons[0] = -1; + } + + windowDestroy(gGameDialogWindow); + gGameDialogWindow = -1; + } + } + + artUnlock(backgroundFrmHandle); + + return -1; +} + +// 0x44A9D8 +void _gdialog_window_destroy() +{ + if (gGameDialogWindow == -1) { + return; + } + + for (int index = 0; index < 9; index++) { + buttonDestroy(_gdialog_buttons[index]); + _gdialog_buttons[index] = -1; + } + + artUnlock(gGameDialogReviewButtonDownFrmHandle); + artUnlock(gGameDialogReviewButtonUpFrmHandle); + + int offset = (_scr_size.right - _scr_size.left + 1) * (480 - _dialogue_subwin_len); + unsigned char* backgroundWindowBuffer = windowGetBuffer(gGameDialogBackgroundWindow) + offset; + + int frmId; + if (gGameDialogSpeakerIsPartyMember) { + // 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 = buildFid(6, frmId, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + _gdialog_scroll_subwin(gGameDialogWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, _dialogue_subwin_len, 0); + artUnlock(backgroundFrmHandle); + windowDestroy(gGameDialogWindow); + _gdialog_window_created = 0; + gGameDialogWindow = -1; + } +} + +// 0x44AB18 +int gameDialogWindowRenderBackground() +{ + CacheEntry* backgroundFrmHandle; + // alltlk.frm - dialog screen background + int fid = buildFid(6, 103, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData == NULL) { + return -1; + } + + int windowWidth = _scr_size.right - _scr_size.left + 1; + unsigned char* windowBuffer = windowGetBuffer(gGameDialogBackgroundWindow); + blitBufferToBuffer(backgroundFrmData, windowWidth, 480, windowWidth, windowBuffer, windowWidth); + artUnlock(backgroundFrmHandle); + + if (!_dialogue_just_started) { + windowRefresh(gGameDialogBackgroundWindow); + } + + return 0; +} + +// 0x44ABA8 +int _talkToRefreshDialogWindowRect(Rect* rect) +{ + int frmId; + if (gGameDialogSpeakerIsPartyMember) { + // 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 = buildFid(6, frmId, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData == NULL) { + return -1; + } + + int offset = 640 * rect->top + rect->left; + + unsigned char* windowBuffer = windowGetBuffer(gGameDialogWindow); + blitBufferToBuffer(backgroundFrmData + offset, + rect->right - rect->left, + rect->bottom - rect->top, + _scr_size.right - _scr_size.left + 1, + windowBuffer + offset, + _scr_size.right - _scr_size.left + 1); + + artUnlock(backgroundFrmHandle); + + windowRefreshRect(gGameDialogWindow, rect); + + return 0; +} + +// 0x44AC68 +void gameDialogRenderHighlight(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 +void gameDialogRenderTalkingHead(Art* headFrm, int frame) +{ + if (gGameDialogWindow == -1) { + return; + } + + if (headFrm != NULL) { + if (frame == 0) { + _totalHotx = 0; + } + + int backgroundFid = buildFid(OBJ_TYPE_BACKGROUND, gGameDialogBackground, 0, 0, 0); + + CacheEntry* backgroundHandle; + Art* backgroundFrm = artLock(backgroundFid, &backgroundHandle); + if (backgroundFrm == NULL) { + debugPrint("\tError locking background in display...\n"); + } + + unsigned char* backgroundFrmData = artGetFrameData(backgroundFrm, 0, 0); + if (backgroundFrmData != NULL) { + blitBufferToBuffer(backgroundFrmData, 388, 200, 388, gGameDialogDisplayBuffer, _scr_size.right - _scr_size.left + 1); + } else { + debugPrint("\tError getting background data in display...\n"); + } + + artUnlock(backgroundHandle); + + int width = artGetWidth(headFrm, frame, 0); + int height = artGetHeight(headFrm, frame, 0); + unsigned char* data = artGetFrameData(headFrm, frame, 0); + + int a3; + int v8; + artGetRotationOffsets(headFrm, 0, &a3, &v8); + + int a4; + int a5; + artGetFrameOffsets(headFrm, frame, 0, &a4, &a5); + + _totalHotx += a4; + a3 += _totalHotx; + + if (data != NULL) { + int destWidth = _scr_size.right - _scr_size.left + 1; + int destOffset = destWidth * (200 - height) + a3 + (388 - width) / 2; + if (destOffset + width * v8 > 0) { + destOffset += width * v8; + } + + blitBufferToBufferTrans( + data, + width, + height, + width, + gGameDialogDisplayBuffer + destOffset, + destWidth); + } else { + debugPrint("\tError getting head data in display...\n"); + } + } else { + if (_talk_need_to_center == 1) { + _talk_need_to_center = 0; + tileWindowRefresh(); + } + + unsigned char* src = windowGetBuffer(gIsoWindow); + blitBufferToBuffer( + src + ((_scr_size.bottom - _scr_size.top - 331) / 2) * (_scr_size.right - _scr_size.left + 1) + (_scr_size.right - _scr_size.left - 387) / 2, + 388, + 200, + _scr_size.right - _scr_size.left + 1, + gGameDialogDisplayBuffer, + _scr_size.right - _scr_size.left + 1); + } + + Rect v27; + v27.left = 126; + v27.top = 14; + v27.right = 514; + v27.bottom = 214; + + unsigned char* dest = windowGetBuffer(gGameDialogBackgroundWindow); + + unsigned char* data1 = artGetFrameData(gGameDialogUpperHighlightFrm, 0, 0); + gameDialogRenderHighlight(data1, gGameDialogUpperHighlightFrmWidth, gGameDialogUpperHighlightFrmHeight, gGameDialogUpperHighlightFrmWidth, dest, 426, 15, _scr_size.right - _scr_size.left + 1, _light_BlendTable, _light_GrayTable); + + unsigned char* data2 = artGetFrameData(gGameDialogLowerHighlightFrm, 0, 0); + gameDialogRenderHighlight(data2, gGameDialogLowerHighlightFrmWidth, gGameDialogLowerHighlightFrmHeight, gGameDialogLowerHighlightFrmWidth, dest, 129, 214 - gGameDialogLowerHighlightFrmHeight - 2, _scr_size.right - _scr_size.left + 1, _dark_BlendTable, _dark_GrayTable); + + for (int index = 0; index < 8; ++index) { + Rect* rect = &(_backgrndRects[index]); + int width = rect->right - rect->left; + + blitBufferToBufferTrans(_backgrndBufs[index], + width, + rect->bottom - rect->top, + width, + dest + (_scr_size.right - _scr_size.left + 1) * rect->top + rect->left, + _scr_size.right - _scr_size.left + 1); + } + + windowRefreshRect(gGameDialogBackgroundWindow, &v27); +} + +// 0x44B080 +void gameDialogPrepareHighlights() +{ + 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 = buildFid(6, 115, 0, 0, 0); + gGameDialogUpperHighlightFrm = artLock(upperHighlightFid, &gGameDialogUpperHighlightFrmHandle); + gGameDialogUpperHighlightFrmWidth = artGetWidth(gGameDialogUpperHighlightFrm, 0, 0); + gGameDialogUpperHighlightFrmHeight = artGetHeight(gGameDialogUpperHighlightFrm, 0, 0); + + // hilight2.frm - dialogue lower hilight + int lowerHighlightFid = buildFid(6, 116, 0, 0, 0); + gGameDialogLowerHighlightFrm = artLock(lowerHighlightFid, &gGameDialogLowerHighlightFrmHandle); + gGameDialogLowerHighlightFrmWidth = artGetWidth(gGameDialogLowerHighlightFrm, 0, 0); + gGameDialogLowerHighlightFrmHeight = artGetHeight(gGameDialogLowerHighlightFrm, 0, 0); +} diff --git a/src/game_dialog.h b/src/game_dialog.h new file mode 100644 index 0000000..d299d04 --- /dev/null +++ b/src/game_dialog.h @@ -0,0 +1,287 @@ +#ifndef GAME_DIALOG_H +#define GAME_DIALOG_H + +#include "art.h" +#include "geometry.h" +#include "interpreter.h" +#include "lips.h" +#include "message.h" +#include "obj_types.h" + +#include + +#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; + +// 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; + +extern int _Dogs[3]; + +extern int _dialog_state_fix; +extern int gGameDialogOptionEntriesLength; +extern int gGameDialogReviewEntriesLength; +extern unsigned char* gGameDialogDisplayBuffer; +extern int gGameDialogReplyWindow; +extern int gGameDialogOptionsWindow; +extern bool _gdialog_window_created; +extern int _boxesWereDisabled; +extern int gGameDialogFidgetFid; +extern CacheEntry* gGameDialogFidgetFrmHandle; +extern Art* gGameDialogFidgetFrm; +extern int gGameDialogBackground; +extern int _lipsFID; +extern int _lipsFID; +extern Art* _lipsFp; +extern bool gGameDialogLipSyncStarted; +extern int _dialogue_state; +extern int _dialogue_switch_mode; +extern int _gdialog_state; +extern bool _gdDialogWentOff; +extern bool _gdDialogTurnMouseOff; +extern int _gdReenterLevel; +extern bool _gdReplyTooBig; +extern Object* _peon_table_obj; +extern Object* _barterer_table_obj; +extern Object* _barterer_temp_obj; +extern int gGameDialogBarterModifier; +extern int gGameDialogBackgroundWindow; +extern int gGameDialogWindow; +extern Rect _backgrndRects[8]; +extern int _talk_need_to_center; +extern bool _can_start_new_fidget; +extern int _gd_replyWin; +extern int _gd_optionsWin; +extern int gGameDialogOldMusicVolume; +extern int gGameDialogOldCenterTile; +extern int gGameDialogOldDudeTile; +extern unsigned char* _light_BlendTable; +extern unsigned char* _dark_BlendTable; +extern int _dialogue_just_started; +extern int _dialogue_seconds_since_last_input; +extern CacheEntry* gGameDialogReviewWindowButtonFrmHandles[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT]; +extern CacheEntry* _reviewBackKey; +extern CacheEntry* gGameDialogReviewWindowBackgroundFrmHandle; +extern unsigned char* gGameDialogReviewWindowBackgroundFrmData; +extern const int gGameDialogReviewWindowButtonWidths[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT]; +extern const int gGameDialogReviewWindowButtonHeights[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT]; +extern int gGameDialogReviewWindowButtonFrmIds[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT]; +extern Object* gGameDialogSpeaker; +extern bool gGameDialogSpeakerIsPartyMember; +extern int gGameDialogHeadFid; +extern int gGameDialogSid; +extern int _head_phoneme_lookup[PHONEME_COUNT]; +extern int _phone_anim; +extern int _loop_cnt; +extern unsigned int _tocksWaiting; +extern const char* _react_strs[3]; +extern int _dialogue_subwin_len; +extern GameDialogButtonData gGameDialogDispositionButtonsData[5]; +extern STRUCT_5189E4 _custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT][6]; +extern GameDialogButtonData _custom_button_info[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT]; +extern int _totalHotx; + +extern int _custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT]; +extern MessageList gCustomMessageList; +extern unsigned char _light_GrayTable[256]; +extern unsigned char _dark_GrayTable[256]; +extern unsigned char* _backgrndBufs[8]; +extern Rect _optionRect; +extern Rect _replyRect; +extern GameDialogReviewEntry gDialogReviewEntries[DIALOG_REVIEW_ENTRIES_CAPACITY]; +extern int _custom_buttons_start; +extern int _control_buttons_start; +extern int gGameDialogReviewWindowOldFont; +extern CacheEntry* gGameDialogRedButtonUpFrmHandle; +extern int _gdialog_buttons[9]; +extern CacheEntry* gGameDialogUpperHighlightFrmHandle; +extern CacheEntry* gGameDialogReviewButtonUpFrmHandle; +extern int gGameDialogLowerHighlightFrmHeight; +extern CacheEntry* gGameDialogReviewButtonDownFrmHandle; +extern unsigned char* gGameDialogRedButtonDownFrmData; +extern int gGameDialogLowerHighlightFrmWidth; +extern unsigned char* gGameDialogRedButtonUpFrmData; +extern int gGameDialogUpperHighlightFrmWidth; +extern Art* gGameDialogLowerHighlightFrm; +extern int gGameDialogUpperHighlightFrmHeight; +extern CacheEntry* gGameDialogRedButtonDownFrmHandle; +extern CacheEntry* gGameDialogLowerHighlightFrmHandle; +extern Art* gGameDialogUpperHighlightFrm; +extern int _oldFont; +extern unsigned int gGameDialogFidgetLastUpdateTimestamp; +extern int gGameDialogFidgetReaction; +extern Program* gDialogReplyProgram; +extern int gDialogReplyMessageListId; +extern int gDialogReplyMessageId; +extern int dword_58F4E0; +extern char gDialogReplyText[900]; +extern GameDialogOptionEntry gDialogOptionEntries[DIALOG_OPTION_ENTRIES_CAPACITY]; +extern int _talkOldFont; +extern unsigned int gGameDialogFidgetUpdateDelay; +extern int gGameDialogFidgetFrmCurrentFrame; + +int gameDialogInit(); +int _gdialogReset(); +int gameDialogReset(); +int gameDialogExit(); +bool _gdialogActive(); +void gameDialogEnter(Object* a1, int a2); +void _gdialogSystemEnter(); +void gameDialogStartLips(const char* a1); +void gameDialogEndLips(); +int gameDialogEnable(); +int gameDialogDisable(); +int _gdialogInitFromScript(int headFid, int reaction); +int _gdialogExitFromScript(); +void gameDialogSetBackground(int a1); +void gameDialogRenderSupplementaryMessage(char* msg); +int _gdialogStart(); +int _gdialogSayMessage(); +int gameDialogAddMessageOptionWithProcIdentifier(int messageListId, int messageId, const char* a3, int reaction); +int gameDialogAddTextOptionWithProcIdentifier(int messageListId, const char* text, const char* a3, int reaction); +int gameDialogAddMessageOptionWithProc(int messageListId, int messageId, int proc, int reaction); +int gameDialogAddTextOptionWithProc(int messageListId, const char* text, int proc, int reaction); +int gameDialogSetMessageReply(Program* a1, int a2, int a3); +int gameDialogSetTextReply(Program* a1, int a2, const char* a3); +int _gdialogGo(); +void _gdialogUpdatePartyStatus(); +int gameDialogAddMessageOption(int a1, int a2, int a3); +int gameDialogAddTextOption(int a1, const char* a2, int a3); +int gameDialogReviewWindowInit(int* win); +int gameDialogReviewWindowFree(int* win); +int gameDialogShowReview(); +void gameDialogReviewButtonOnMouseUp(int btn, int keyCode); +void gameDialogReviewWindowUpdate(int win, int origin); +void dialogReviewEntriesClear(); +int gameDialogAddReviewMessage(int messageListId, int messageId); +int gameDialogAddReviewText(const char* text); +int gameDialogSetReviewOptionMessage(int messageListId, int messageId); +int gameDialogSetReviewOptionText(const char* text); +int _gdProcessInit(); +void _gdProcessCleanup(); +int _gdProcessExit(); +void gameDialogRenderCaps(); +int _gdProcess(); +int _gdProcessChoice(int a1); +void gameDialogOptionOnMouseEnter(int a1); +void gameDialogOptionOnMouseExit(int a1); +void gameDialogRenderReply(); +void _gdProcessUpdate(); +int _gdCreateHeadWindow(); +void _gdDestroyHeadWindow(); +void _gdSetupFidget(int headFid, int reaction); +void gameDialogWaitForFidgetToComplete(); +void _gdPlayTransition(int a1); +void _reply_arrow_up(int btn, int a2); +void _reply_arrow_down(int btn, int a2); +void _reply_arrow_restore(int btn, int a2); +void _demo_copy_title(int win); +void _demo_copy_options(int win); +void _gDialogRefreshOptionsRect(int win, Rect* drawRect); +void gameDialogTicker(); +void _talk_to_critter_reacts(int a1); +void _gdialog_scroll_subwin(int a1, int a2, unsigned char* a3, unsigned char* a4, unsigned char* a5, int a6, int a7); +int _text_num_lines(const char* a1, int a2); +int gameDialogDrawText(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color, int a7); +void gameDialogSetBarterModifier(int modifier); +int gameDialogBarter(int modifier); +void _barter_end_to_talk_to(); +int _gdialog_barter_create_win(); +void _gdialog_barter_destroy_win(); +void _gdialog_barter_cleanup_tables(); +int partyMemberControlWindowInit(); +void partyMemberControlWindowFree(); +void partyMemberControlWindowUpdate(); +void gameDialogCombatControlButtonOnMouseUp(int a1, int a2); +int _gdPickAIUpdateMsg(Object* obj); +int _gdCanBarter(); +void partyMemberControlWindowHandleEvents(); +int partyMemberCustomizationWindowInit(); +void partyMemberCustomizationWindowFree(); +void partyMemberCustomizationWindowHandleEvents(); +void partyMemberCustomizationWindowUpdate(); +void _gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex); +int _gdCustomSelect(int a1); +void _gdCustomUpdateSetting(int option, int value); +void gameDialogBarterButtonUpMouseUp(int btn, int a2); +int _gdialog_window_create(); +void _gdialog_window_destroy(); +int gameDialogWindowRenderBackground(); +int _talkToRefreshDialogWindowRect(Rect* rect); +void gameDialogRenderHighlight(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int x, int y, int destPitch, unsigned char* a9, unsigned char* a10); +void gameDialogRenderTalkingHead(Art* art, int frame); +void gameDialogPrepareHighlights(); + +#endif /* GAME_DIALOG_H */ diff --git a/src/game_memory.c b/src/game_memory.c new file mode 100644 index 0000000..888aed1 --- /dev/null +++ b/src/game_memory.c @@ -0,0 +1,34 @@ +#include "game_memory.h" + +#include "db.h" +#include "dictionary.h" +#include "memory.h" +#include "memory_manager.h" + +// 0x44B250 +int gameMemoryInit() +{ + dictionarySetMemoryProcs(internal_malloc, internal_realloc, internal_free); + _db_register_mem(internal_malloc, internal_strdup, internal_free); + memoryManagerSetProcs(gameMemoryMalloc, gameMemoryRealloc, gameMemoryFree); + + return 0; +} + +// 0x44B294 +void* gameMemoryMalloc(size_t size) +{ + return internal_malloc(size); +} + +// 0x44B29C +void* gameMemoryRealloc(void* ptr, size_t newSize) +{ + return internal_realloc(ptr, newSize); +} + +// 0x44B2A4 +void gameMemoryFree(void* ptr) +{ + internal_free(ptr); +} diff --git a/src/game_memory.h b/src/game_memory.h new file mode 100644 index 0000000..1c543f7 --- /dev/null +++ b/src/game_memory.h @@ -0,0 +1,11 @@ +#ifndef GAME_MEMORY_H +#define GAME_MEMORY_H + +#include "memory_defs.h" + +int gameMemoryInit(); +void* gameMemoryMalloc(size_t size); +void* gameMemoryRealloc(void* ptr, size_t newSize); +void gameMemoryFree(void* ptr); + +#endif /* GAME_MEMORY_H */ diff --git a/src/game_mouse.c b/src/game_mouse.c new file mode 100644 index 0000000..f35eade --- /dev/null +++ b/src/game_mouse.c @@ -0,0 +1,2369 @@ +#include "game_mouse.h" + +#include "actions.h" +#include "color.h" +#include "combat.h" +#include "core.h" +#include "critter.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "object.h" +#include "proto.h" +#include "proto_instance.h" +#include "skilldex.h" +#include "text_font.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include +#include + +// 0x518BF8 +bool gGameMouseInitialized = false; + +// 0x518BFC +int _gmouse_enabled = 0; + +// 0x518C00 +int _gmouse_mapper_mode = 0; + +// 0x518C04 +int _gmouse_click_to_scroll = 0; + +// 0x518C08 +int _gmouse_scrolling_enabled = 1; + +// 0x518C0C +int gGameMouseCursor = MOUSE_CURSOR_NONE; + +// 0x518C10 +CacheEntry* gGameMouseCursorFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518C14 +const int gGameMouseCursorFrmIds[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 +bool gGameMouseObjectsInitialized = false; + +// 0x518C84 +bool _gmouse_3d_hover_test = false; + +// 0x518C88 +unsigned int _gmouse_3d_last_move_time = 0; + +// actmenu.frm +// 0x518C8C +Art* gGameMouseActionMenuFrm = NULL; + +// 0x518C90 +CacheEntry* gGameMouseActionMenuFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518C94 +int gGameMouseActionMenuFrmWidth = 0; + +// 0x518C98 +int gGameMouseActionMenuFrmHeight = 0; + +// 0x518C9C +int gGameMouseActionMenuFrmDataSize = 0; + +// 0x518CA0 +int _gmouse_3d_menu_frame_hot_x = 0; + +// 0x518CA4 +int _gmouse_3d_menu_frame_hot_y = 0; + +// 0x518CA8 +unsigned char* gGameMouseActionMenuFrmData = NULL; + +// actpick.frm +// 0x518CAC +Art* gGameMouseActionPickFrm = NULL; + +// 0x518CB0 +CacheEntry* gGameMouseActionPickFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518CB4 +int gGameMouseActionPickFrmWidth = 0; + +// 0x518CB8 +int gGameMouseActionPickFrmHeight = 0; + +// 0x518CBC +int gGameMouseActionPickFrmDataSize = 0; + +// 0x518CC0 +int _gmouse_3d_pick_frame_hot_x = 0; + +// 0x518CC4 +int _gmouse_3d_pick_frame_hot_y = 0; + +// 0x518CC8 +unsigned char* gGameMouseActionPickFrmData = NULL; + +// acttohit.frm +// 0x518CCC +Art* gGameMouseActionHitFrm = NULL; + +// 0x518CD0 +CacheEntry* gGameMouseActionHitFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518CD4 +int gGameMouseActionHitFrmWidth = 0; + +// 0x518CD8 +int gGameMouseActionHitFrmHeight = 0; + +// 0x518CDC +int gGameMouseActionHitFrmDataSize = 0; + +// 0x518CE0 +unsigned char* gGameMouseActionHitFrmData = NULL; + +// blank.frm +// 0x518CE4 +Art* gGameMouseBouncingCursorFrm = NULL; + +// 0x518CE8 +CacheEntry* gGameMouseBouncingCursorFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518CEC +int gGameMouseBouncingCursorFrmWidth = 0; + +// 0x518CF0 +int gGameMouseBouncingCursorFrmHeight = 0; + +// 0x518CF4 +int gGameMouseBouncingCursorFrmDataSize = 0; + +// 0x518CF8 +unsigned char* gGameMouseBouncingCursorFrmData = NULL; + +// msef000.frm +// 0x518CFC +Art* gGameMouseHexCursorFrm = NULL; + +// 0x518D00 +CacheEntry* gGameMouseHexCursorFrmHandle = INVALID_CACHE_ENTRY; + +// 0x518D04 +int gGameMouseHexCursorFrmWidth = 0; + +// 0x518D08 +int gGameMouseHexCursorHeight = 0; + +// 0x518D0C +int gGameMouseHexCursorDataSize = 0; + +// 0x518D10 +unsigned char* gGameMouseHexCursorFrmData = NULL; + +// 0x518D14 +unsigned char gGameMouseActionMenuItemsLength = 0; + +// 0x518D18 +unsigned char* _gmouse_3d_menu_actions_start = NULL; + +// 0x518D1C +unsigned char gGameMouseActionMenuHighlightedItemIndex = 0; + +// 0x518D1E +const short gGameMouseActionMenuItemFrmIds[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 +int _gmouse_3d_modes_enabled = 1; + +// 0x518D38 +int gGameMouseMode = GAME_MOUSE_MODE_MOVE; + +// 0x518D3C +int gGameMouseModeFrmIds[GAME_MOUSE_MODE_COUNT] = { + 249, + 250, + 251, + 293, + 293, + 293, + 293, + 293, + 293, + 293, + 293, +}; + +// 0x518D68 +const int gGameMouseModeSkills[GAME_MOUSE_MODE_SKILL_COUNT] = { + SKILL_FIRST_AID, + SKILL_DOCTOR, + SKILL_LOCKPICK, + SKILL_STEAL, + SKILL_TRAPS, + SKILL_SCIENCE, + SKILL_REPAIR, +}; + +// 0x518D84 +int gGameMouseAnimatedCursorNextFrame = 0; + +// 0x518D88 +unsigned int gGameMouseAnimatedCursorLastUpdateTimestamp = 0; + +// 0x518D8C +int _gmouse_bk_last_cursor = -1; + +// 0x518D90 +bool gGameMouseItemHighlightEnabled = true; + +// 0x518D94 +Object* gGameMouseHighlightedItem = NULL; + +// 0x518D98 +bool _gmouse_clicked_on_edge = false; + +// 0x518D9C +int dword_518D9C = -1; + +// 0x596C3C +int gGameMouseActionMenuItems[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; + +// 0x596C64 +int gGameMouseLastX; + +// 0x596C68 +int gGameMouseLastY; + +// blank.frm +// 0x596C6C +Object* gGameMouseBouncingCursor; + +// msef000.frm +// 0x596C70 +Object* gGameMouseHexCursor; + +// 0x596C74 +Object* gGameMousePointedObject; + +// 0x44B2B0 +int gameMouseInit() +{ + if (gGameMouseInitialized) { + return -1; + } + + if (gameMouseObjectsInit() != 0) { + return -1; + } + + gGameMouseInitialized = true; + _gmouse_enabled = 1; + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + return 0; +} + +// 0x44B2E8 +int gameMouseReset() +{ + if (!gGameMouseInitialized) { + return -1; + } + + // NOTE: Uninline. + if (gameMouseObjectsReset() != 0) { + return -1; + } + + // NOTE: Uninline. + _gmouse_enable(); + + _gmouse_scrolling_enabled = 1; + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + gGameMouseAnimatedCursorNextFrame = 0; + gGameMouseAnimatedCursorLastUpdateTimestamp = 0; + _gmouse_clicked_on_edge = 0; + + return 0; +} + +// 0x44B3B8 +void gameMouseExit() +{ + if (!gGameMouseInitialized) { + return; + } + + mouseHideCursor(); + + mouseSetFrame(NULL, 0, 0, 0, 0, 0, 0); + + // NOTE: Uninline. + gameMouseObjectsFree(); + + if (gGameMouseCursorFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseCursorFrmHandle); + } + gGameMouseCursorFrmHandle = INVALID_CACHE_ENTRY; + + _gmouse_enabled = 0; + gGameMouseInitialized = false; + gGameMouseCursor = -1; +} + +// 0x44B454 +void _gmouse_enable() +{ + if (!_gmouse_enabled) { + gGameMouseCursor = -1; + gameMouseSetCursor(MOUSE_CURSOR_NONE); + _gmouse_scrolling_enabled = 1; + _gmouse_enabled = 1; + _gmouse_bk_last_cursor = -1; + } +} + +// 0x44B48C +void _gmouse_disable(int a1) +{ + if (_gmouse_enabled) { + gameMouseSetCursor(MOUSE_CURSOR_NONE); + _gmouse_enabled = 0; + + if (a1 & 1) { + _gmouse_scrolling_enabled = 1; + } else { + _gmouse_scrolling_enabled = 0; + } + } +} + +// 0x44B4CC +void _gmouse_enable_scrolling() +{ + _gmouse_scrolling_enabled = 1; +} + +// 0x44B4D8 +void _gmouse_disable_scrolling() +{ + _gmouse_scrolling_enabled = 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; + mouseGetPosition(&x, &y); + if (x == _scr_size.left || x == _scr_size.right || y == _scr_size.top || y == _scr_size.bottom) { + switch (gGameMouseCursor) { + 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 gameMouseRefresh() +{ + if (!gGameMouseInitialized) { + return; + } + + int mouseX; + int mouseY; + + if (gGameMouseCursor >= FIRST_GAME_MOUSE_ANIMATED_CURSOR) { + _mouse_info(); + + if (_gmouse_scrolling_enabled) { + mouseGetPosition(&mouseX, &mouseY); + int oldMouseCursor = gGameMouseCursor; + + if (gameMouseHandleScrolling(mouseX, mouseY, gGameMouseCursor) == 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) { + gameMouseSetCursor(_gmouse_bk_last_cursor); + _gmouse_bk_last_cursor = -1; + return; + } + } + + gameMouseSetCursor(gGameMouseCursor); + return; + } + + if (!_gmouse_enabled) { + if (_gmouse_scrolling_enabled) { + mouseGetPosition(&mouseX, &mouseY); + int oldMouseCursor = gGameMouseCursor; + + if (gameMouseHandleScrolling(mouseX, mouseY, gGameMouseCursor) == 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) { + gameMouseSetCursor(_gmouse_bk_last_cursor); + _gmouse_bk_last_cursor = -1; + } + } + + return; + } + + mouseGetPosition(&mouseX, &mouseY); + + int oldMouseCursor = gGameMouseCursor; + if (gameMouseHandleScrolling(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) { + gameMouseSetCursor(_gmouse_bk_last_cursor); + _gmouse_bk_last_cursor = -1; + } + + if (windowGetAtPoint(mouseX, mouseY) != gIsoWindow) { + if (gGameMouseCursor == MOUSE_CURSOR_NONE) { + gameMouseObjectsHide(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (gGameMouseMode >= 2 && !isInCombat()) { + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + } + } + return; + } + + // NOTE: Strange set of conditions and jumps. Not sure about this one. + switch (gGameMouseCursor) { + 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 (gGameMouseCursor != MOUSE_CURSOR_NONE) { + gameMouseSetCursor(MOUSE_CURSOR_NONE); + } + + if ((gGameMouseHexCursor->flags & OBJECT_HIDDEN) != 0) { + gameMouseObjectsShow(); + } + + break; + } + + Rect r1; + if (_gmouse_3d_move_to(mouseX, mouseY, gElevation, &r1) == 0) { + tileWindowRefreshRect(&r1, gElevation); + } + + if ((gGameMouseHexCursor->flags & OBJECT_HIDDEN) != 0 || _gmouse_mapper_mode != 0) { + return; + } + + unsigned int v3 = _get_bk_time(); + if (mouseX == gGameMouseLastX && mouseY == gGameMouseLastY) { + if (_gmouse_3d_hover_test || getTicksBetween(v3, _gmouse_3d_last_move_time) < 250) { + return; + } + + if (gGameMouseMode != GAME_MOUSE_MODE_MOVE) { + if (gGameMouseMode == GAME_MOUSE_MODE_ARROW) { + _gmouse_3d_last_move_time = v3; + _gmouse_3d_hover_test = true; + + Object* pointedObject = gameMouseGetObjectUnderCursor(-1, true, gElevation); + if (pointedObject != NULL) { + int primaryAction = -1; + + switch ((pointedObject->fid & 0xF000000) >> 24) { + case OBJ_TYPE_ITEM: + primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE; + if (gGameMouseItemHighlightEnabled) { + Rect tmp; + if (objectSetOutline(pointedObject, OUTLINE_TYPE_ITEM, &tmp) == 0) { + tileWindowRefreshRect(&tmp, gElevation); + gGameMouseHighlightedItem = pointedObject; + } + } + break; + case OBJ_TYPE_CRITTER: + if (pointedObject == gDude) { + 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, 32)) { + 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 (gameMouseRenderPrimaryAction(mouseX, mouseY, primaryAction, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99) == 0) { + Rect tmp; + int fid = buildFid(6, 282, 0, 0, 0); + if (objectSetFid(gGameMouseHexCursor, fid, &tmp) == 0) { + tileWindowRefreshRect(&tmp, gElevation); + } + } + } + + if (pointedObject != gGameMousePointedObject) { + gGameMousePointedObject = pointedObject; + _obj_look_at(gDude, gGameMousePointedObject); + } + } + } else if (gGameMouseMode == GAME_MOUSE_MODE_CROSSHAIR) { + Object* pointedObject = gameMouseGetObjectUnderCursor(OBJ_TYPE_CRITTER, false, gElevation); + if (pointedObject == NULL) { + pointedObject = gameMouseGetObjectUnderCursor(-1, false, gElevation); + if (!objectIsDoor(pointedObject)) { + pointedObject = NULL; + } + } + + if (pointedObject != NULL) { + bool pointedObjectIsCritter = (pointedObject->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER; + + int combatLooks = 0; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &combatLooks); + if (combatLooks != 0) { + if (_obj_examine(gDude, pointedObject) == -1) { + _obj_look_at(gDude, 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 (gameMouseRenderAccuracy(formattedAccuracy, color) == 0) { + Rect tmp; + int fid = buildFid(6, 284, 0, 0, 0); + if (objectSetFid(gGameMouseHexCursor, fid, &tmp) == 0) { + tileWindowRefreshRect(&tmp, gElevation); + } + } + + if (gGameMousePointedObject != pointedObject) { + gGameMousePointedObject = pointedObject; + } + } else { + Rect tmp; + if (gameMouseUpdateHexCursorFid(&tmp) == 0) { + tileWindowRefreshRect(&tmp, gElevation); + } + } + + _gmouse_3d_last_move_time = v3; + _gmouse_3d_hover_test = true; + } + return; + } + + char formattedActionPoints[8]; + int color; + int v6 = _make_path(gDude, gDude->tile, gGameMouseHexCursor->tile, NULL, 1); + if (v6) { + if (!isInCombat()) { + formattedActionPoints[0] = '\0'; + color = _colorTable[31744]; + } else { + int v7 = critterGetMovementPointCostAdjustedForCrippledLegs(gDude, v6); + int v8; + if (v7 - _combat_free_move >= 0) { + v8 = v7 - _combat_free_move; + } else { + v8 = 0; + } + + if (v8 <= gDude->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 (gameMouseRenderActionPoints(formattedActionPoints, color) == 0) { + Rect tmp; + objectGetRect(gGameMouseHexCursor, &tmp); + tileWindowRefreshRect(&tmp, 0); + } + + _gmouse_3d_last_move_time = v3; + _gmouse_3d_hover_test = true; + dword_518D9C = gGameMouseHexCursor->tile; + return; + } + + _gmouse_3d_last_move_time = v3; + _gmouse_3d_hover_test = false; + gGameMouseLastX = mouseX; + gGameMouseLastY = mouseY; + + if (!_gmouse_mapper_mode) { + int fid = buildFid(6, 0, 0, 0, 0); + gameMouseSetBouncingCursorFid(fid); + } + + int v34 = 0; + + Rect r2; + Rect r26; + if (gameMouseUpdateHexCursorFid(&r2) == 0) { + v34 |= 1; + } + + if (gGameMouseHighlightedItem != NULL) { + if (objectClearOutline(gGameMouseHighlightedItem, &r26) == 0) { + v34 |= 2; + } + gGameMouseHighlightedItem = NULL; + } + + switch (v34) { + case 3: + rectUnion(&r2, &r26, &r2); + // FALLTHROUGH + case 1: + tileWindowRefreshRect(&r2, gElevation); + break; + case 2: + tileWindowRefreshRect(&r26, gElevation); + break; + } +} + +// 0x44BFA8 +void _gmouse_handle_event(int mouseX, int mouseY, int mouseState) +{ + if (!gGameMouseInitialized) { + return; + } + + if (gGameMouseCursor >= 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.right - _scr_size.left, _scr_size.bottom - _scr_size.top - 100)) { + return; + } + + if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { + if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_REPEAT) == 0 && (gGameMouseHexCursor->flags & OBJECT_HIDDEN) == 0) { + gameMouseCycleMode(); + } + return; + } + + if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { + if (gGameMouseMode == GAME_MOUSE_MODE_MOVE) { + int actionPoints; + if (isInCombat()) { + actionPoints = _combat_free_move + gDude->data.critter.combat.ap; + } else { + actionPoints = -1; + } + + bool running; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &running); + + if (gPressedPhysicalKeys[DIK_LSHIFT] || gPressedPhysicalKeys[DIK_RSHIFT]) { + if (running) { + _dude_move(actionPoints); + return; + } + } else { + if (!running) { + _dude_move(actionPoints); + return; + } + } + + _dude_run(actionPoints); + return; + } + + if (gGameMouseMode == GAME_MOUSE_MODE_ARROW) { + Object* v5 = gameMouseGetObjectUnderCursor(-1, true, gElevation); + if (v5 != NULL) { + switch ((v5->fid & 0xF000000) >> 24) { + case OBJ_TYPE_ITEM: + actionPickUp(gDude, v5); + break; + case OBJ_TYPE_CRITTER: + if (v5 == gDude) { + if (((gDude->fid & 0xFF0000) >> 16) == ANIM_STAND) { + Rect a1; + if (objectRotateClockwise(v5, &a1) == 0) { + tileWindowRefreshRect(&a1, v5->elevation); + } + } + } else { + if (_obj_action_can_talk_to(v5)) { + if (isInCombat()) { + if (_obj_examine(gDude, v5) == -1) { + _obj_look_at(gDude, v5); + } + } else { + actionTalk(gDude, v5); + } + } else { + _action_loot_container(gDude, v5); + } + } + break; + case OBJ_TYPE_SCENERY: + if (_obj_action_can_use(v5)) { + _action_use_an_object(gDude, v5); + } else { + if (_obj_examine(gDude, v5) == -1) { + _obj_look_at(gDude, v5); + } + } + break; + case OBJ_TYPE_WALL: + if (_obj_examine(gDude, v5) == -1) { + _obj_look_at(gDude, v5); + } + break; + } + } + return; + } + + if (gGameMouseMode == GAME_MOUSE_MODE_CROSSHAIR) { + Object* v7 = gameMouseGetObjectUnderCursor(OBJ_TYPE_CRITTER, false, gElevation); + if (v7 == NULL) { + v7 = gameMouseGetObjectUnderCursor(-1, false, gElevation); + if (!objectIsDoor(v7)) { + v7 = NULL; + } + } + + if (v7 != NULL) { + _combat_attack_this(v7); + _gmouse_3d_hover_test = true; + gGameMouseLastY = mouseY; + gGameMouseLastX = mouseX; + _gmouse_3d_last_move_time = _get_time() - 250; + } + return; + } + + if (gGameMouseMode == GAME_MOUSE_MODE_USE_CROSSHAIR) { + Object* object = gameMouseGetObjectUnderCursor(-1, true, gElevation); + if (object != NULL) { + Object* weapon; + if (interfaceGetActiveItem(&weapon) != -1) { + if (isInCombat()) { + int hitMode = interfaceGetCurrentHand() + ? HIT_MODE_RIGHT_WEAPON_PRIMARY + : HIT_MODE_LEFT_WEAPON_PRIMARY; + + int actionPointsRequired = _item_mp_cost(gDude, hitMode, false); + if (actionPointsRequired <= gDude->data.critter.combat.ap) { + if (_action_use_an_item_on_object(gDude, object, weapon) != -1) { + int actionPoints = gDude->data.critter.combat.ap; + if (actionPointsRequired > actionPoints) { + gDude->data.critter.combat.ap = 0; + } else { + gDude->data.critter.combat.ap -= actionPointsRequired; + } + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + } + } else { + _action_use_an_item_on_object(gDude, object, weapon); + } + } + } + gameMouseSetCursor(MOUSE_CURSOR_NONE); + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + return; + } + + if (gGameMouseMode == GAME_MOUSE_MODE_USE_FIRST_AID + || gGameMouseMode == GAME_MOUSE_MODE_USE_DOCTOR + || gGameMouseMode == GAME_MOUSE_MODE_USE_LOCKPICK + || gGameMouseMode == GAME_MOUSE_MODE_USE_STEAL + || gGameMouseMode == GAME_MOUSE_MODE_USE_TRAPS + || gGameMouseMode == GAME_MOUSE_MODE_USE_SCIENCE + || gGameMouseMode == GAME_MOUSE_MODE_USE_REPAIR) { + Object* object = gameMouseGetObjectUnderCursor(-1, 1, gElevation); + if (object == NULL || actionUseSkill(gDude, object, gGameMouseModeSkills[gGameMouseMode - FIRST_GAME_MOUSE_MODE_SKILL]) != -1) { + gameMouseSetCursor(MOUSE_CURSOR_NONE); + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + } + return; + } + } + + if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) == MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT && gGameMouseMode == GAME_MOUSE_MODE_ARROW) { + Object* v16 = gameMouseGetObjectUnderCursor(-1, true, gElevation); + if (v16 != NULL) { + int actionMenuItemsCount = 0; + int actionMenuItems[6]; + switch ((v16->fid & 0xF000000) >> 24) { + case OBJ_TYPE_ITEM: + actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE; + actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; + if (itemGetType(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 == gDude) { + 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, 32)) { + actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE; + } + } + + if (actionCheckPush(gDude, 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 (gameMouseRenderActionMenuItems(mouseX, mouseY, actionMenuItems, actionMenuItemsCount, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99) == 0) { + Rect v43; + int fid = buildFid(6, 283, 0, 0, 0); + if (objectSetFid(gGameMouseHexCursor, fid, &v43) == 0 && _gmouse_3d_move_to(mouseX, mouseY, gElevation, &v43) == 0) { + tileWindowRefreshRect(&v43, gElevation); + isoDisable(); + + int v33 = mouseY; + int actionIndex = 0; + while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { + _get_input(); + + if (_game_user_wants_to_quit != 0) { + actionMenuItems[actionIndex] = 0; + } + + int v48; + int v47; + mouseGetPosition(&v48, &v47); + + if (abs(v47 - v33) > 10) { + if (v33 >= v47) { + actionIndex -= 1; + } else { + actionIndex += 1; + } + + if (gameMouseHighlightActionMenuItemAtIndex(actionIndex) == 0) { + tileWindowRefreshRect(&v43, gElevation); + } + v33 = v47; + } + } + + isoEnable(); + + _gmouse_3d_hover_test = false; + gGameMouseLastX = mouseX; + gGameMouseLastY = mouseY; + _gmouse_3d_last_move_time = _get_time(); + + _mouse_set_position(mouseX, v33); + + if (gameMouseUpdateHexCursorFid(&v43) == 0) { + tileWindowRefreshRect(&v43, gElevation); + } + + switch (actionMenuItems[actionIndex]) { + case GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY: + inventoryOpenUseItemOn(v16); + break; + case GAME_MOUSE_ACTION_MENU_ITEM_LOOK: + if (_obj_examine(gDude, v16) == -1) { + _obj_look_at(gDude, v16); + } + break; + case GAME_MOUSE_ACTION_MENU_ITEM_ROTATE: + if (objectRotateClockwise(v16, &v43) == 0) { + tileWindowRefreshRect(&v43, v16->elevation); + } + break; + case GAME_MOUSE_ACTION_MENU_ITEM_TALK: + actionTalk(gDude, v16); + break; + case GAME_MOUSE_ACTION_MENU_ITEM_USE: + switch ((v16->fid & 0xF000000) >> 24) { + case OBJ_TYPE_SCENERY: + _action_use_an_object(gDude, v16); + break; + case OBJ_TYPE_CRITTER: + _action_loot_container(gDude, v16); + break; + default: + actionPickUp(gDude, v16); + break; + } + break; + case GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL: + if (1) { + int skill = -1; + + int rc = skilldexOpen(); + 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) { + actionUseSkill(gDude, v16, skill); + } + } + break; + case GAME_MOUSE_ACTION_MENU_ITEM_PUSH: + actionPush(gDude, v16); + break; + } + } + } + } + } +} + +// 0x44C840 +int gameMouseSetCursor(int cursor) +{ + if (!gGameMouseInitialized) { + return -1; + } + + if (cursor != MOUSE_CURSOR_ARROW && cursor == gGameMouseCursor && (gGameMouseCursor < 25 || gGameMouseCursor >= 27)) { + return -1; + } + + CacheEntry* mouseCursorFrmHandle; + int fid = buildFid(6, gGameMouseCursorFrmIds[cursor], 0, 0, 0); + Art* mouseCursorFrm = artLock(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 ((gGameMouseHexCursor->flags & OBJECT_HIDDEN) == 0) { + gameMouseObjectsHide(); + } + + unsigned int delay = 1000 / artGetFramesPerSecond(mouseCursorFrm); + if (getTicksBetween(tick, gGameMouseAnimatedCursorLastUpdateTimestamp) < delay) { + shouldUpdate = false; + } else { + if (artGetFrameCount(mouseCursorFrm) <= gGameMouseAnimatedCursorNextFrame) { + gGameMouseAnimatedCursorNextFrame = 0; + } + + frame = gGameMouseAnimatedCursorNextFrame; + gGameMouseAnimatedCursorLastUpdateTimestamp = tick; + gGameMouseAnimatedCursorNextFrame++; + } + } + + if (!shouldUpdate) { + return -1; + } + + int width = artGetWidth(mouseCursorFrm, frame, 0); + int height = artGetHeight(mouseCursorFrm, frame, 0); + + int offsetX; + int offsetY; + artGetRotationOffsets(mouseCursorFrm, 0, &offsetX, &offsetY); + + offsetX = width / 2 - offsetX; + offsetY = height - 1 - offsetY; + + unsigned char* mouseCursorFrmData = artGetFrameData(mouseCursorFrm, frame, 0); + if (mouseSetFrame(mouseCursorFrmData, width, height, width, offsetX, offsetY, 0) != 0) { + return -1; + } + + if (gGameMouseCursorFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseCursorFrmHandle); + } + + gGameMouseCursor = cursor; + gGameMouseCursorFrmHandle = mouseCursorFrmHandle; + + return 0; +} + +// 0x44C9E8 +int gameMouseGetCursor() +{ + return gGameMouseCursor; +} + +// 0x44C9F8 +void _gmouse_3d_enable_modes() +{ + _gmouse_3d_modes_enabled = 1; +} + +// 0x44CA18 +void gameMouseSetMode(int mode) +{ + if (!gGameMouseInitialized) { + return; + } + + if (!_gmouse_3d_modes_enabled) { + return; + } + + if (mode == gGameMouseMode) { + return; + } + + int fid = buildFid(6, 0, 0, 0, 0); + gameMouseSetBouncingCursorFid(fid); + + fid = buildFid(6, gGameMouseModeFrmIds[mode], 0, 0, 0); + + Rect rect; + if (objectSetFid(gGameMouseHexCursor, fid, &rect) == -1) { + return; + } + + int mouseX; + int mouseY; + mouseGetPosition(&mouseX, &mouseY); + + Rect r2; + if (_gmouse_3d_move_to(mouseX, mouseY, gElevation, &r2) == 0) { + rectUnion(&rect, &r2, &rect); + } + + int v5 = 0; + if (gGameMouseMode == GAME_MOUSE_MODE_CROSSHAIR) { + v5 = -1; + } + + if (mode != 0) { + if (mode == GAME_MOUSE_MODE_CROSSHAIR) { + v5 = 1; + } + + if (gGameMouseMode == 0) { + if (objectDisableOutline(gGameMouseHexCursor, &r2) == 0) { + rectUnion(&rect, &r2, &rect); + } + } + } else { + if (objectEnableOutline(gGameMouseHexCursor, &r2) == 0) { + rectUnion(&rect, &r2, &rect); + } + } + + gGameMouseMode = mode; + _gmouse_3d_hover_test = false; + _gmouse_3d_last_move_time = _get_time(); + + tileWindowRefreshRect(&rect, gElevation); + + switch (v5) { + case 1: + _combat_outline_on(); + break; + case -1: + _combat_outline_off(); + break; + } +} + +// 0x44CB6C +int gameMouseGetMode() +{ + return gGameMouseMode; +} + +// 0x44CB74 +void gameMouseCycleMode() +{ + int mode = (gGameMouseMode + 1) % 3; + + if (isInCombat()) { + Object* item; + if (interfaceGetActiveItem(&item) == 0) { + if (item != NULL && itemGetType(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; + } + } + + gameMouseSetMode(mode); +} + +// 0x44CBD0 +void _gmouse_3d_refresh() +{ + gGameMouseLastX = -1; + gGameMouseLastY = -1; + _gmouse_3d_hover_test = false; + _gmouse_3d_last_move_time = 0; + gameMouseRefresh(); +} + +// 0x44CBFC +int gameMouseSetBouncingCursorFid(int fid) +{ + if (!gGameMouseInitialized) { + return -1; + } + + if (!artExists(fid)) { + return -1; + } + + if (gGameMouseBouncingCursor->fid == fid) { + return -1; + } + + if (!_gmouse_mapper_mode) { + return objectSetFid(gGameMouseBouncingCursor, fid, NULL); + } + + int v1 = 0; + + Rect oldRect; + if (gGameMouseBouncingCursor->fid != -1) { + objectGetRect(gGameMouseBouncingCursor, &oldRect); + v1 |= 1; + } + + int rc = -1; + + Rect rect; + if (objectSetFid(gGameMouseBouncingCursor, fid, &rect) == 0) { + rc = 0; + v1 |= 2; + } + + if ((gGameMouseHexCursor->flags & OBJECT_HIDDEN) == 0) { + if (v1 == 1) { + tileWindowRefreshRect(&oldRect, gElevation); + } else if (v1 == 2) { + tileWindowRefreshRect(&rect, gElevation); + } else if (v1 == 3) { + rectUnion(&oldRect, &rect, &oldRect); + tileWindowRefreshRect(&oldRect, gElevation); + } + } + + return rc; +} + +// 0x44CD0C +void gameMouseResetBouncingCursorFid() +{ + int fid = buildFid(6, 0, 0, 0, 0); + gameMouseSetBouncingCursorFid(fid); +} + +// 0x44CD2C +void gameMouseObjectsShow() +{ + if (!gGameMouseInitialized) { + return; + } + + int v2 = 0; + + Rect rect1; + if (objectShow(gGameMouseBouncingCursor, &rect1) == 0) { + v2 |= 1; + } + + Rect rect2; + if (objectShow(gGameMouseHexCursor, &rect2) == 0) { + v2 |= 2; + } + + Rect tmp; + if (gGameMouseMode != GAME_MOUSE_MODE_MOVE) { + if (objectDisableOutline(gGameMouseHexCursor, &tmp) == 0) { + if ((v2 & 2) != 0) { + rectUnion(&rect2, &tmp, &rect2); + } else { + memcpy(&rect2, &tmp, sizeof(rect2)); + v2 |= 2; + } + } + } + + if (gameMouseUpdateHexCursorFid(&tmp) == 0) { + if ((v2 & 2) != 0) { + rectUnion(&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: + rectUnion(&rect1, &rect2, &rect1); + rect = &rect1; + break; + default: + assert(false && "Should be unreachable"); + } + + tileWindowRefreshRect(rect, gElevation); + } + + _gmouse_3d_hover_test = false; + _gmouse_3d_last_move_time = _get_time() - 250; +} + +// 0x44CE34 +void gameMouseObjectsHide() +{ + if (!gGameMouseInitialized) { + return; + } + + int v1 = 0; + + Rect rect1; + if (objectHide(gGameMouseBouncingCursor, &rect1) == 0) { + v1 |= 1; + } + + Rect rect2; + if (objectHide(gGameMouseHexCursor, &rect2) == 0) { + v1 |= 2; + } + + if (v1 == 1) { + tileWindowRefreshRect(&rect1, gElevation); + } else if (v1 == 2) { + tileWindowRefreshRect(&rect2, gElevation); + } else if (v1 == 3) { + rectUnion(&rect1, &rect2, &rect1); + tileWindowRefreshRect(&rect1, gElevation); + } +} + +// 0x44CEB0 +bool gameMouseObjectsIsVisible() +{ + return (gGameMouseHexCursor->flags & OBJECT_HIDDEN) == 0; +} + +// 0x44CEC4 +Object* gameMouseGetObjectUnderCursor(int objectType, bool a2, int elevation) +{ + int mouseX; + int mouseY; + mouseGetPosition(&mouseX, &mouseY); + + bool v13 = false; + if (objectType == -1) { + if (_square_roof_intersect(mouseX, mouseY, elevation)) { + if (_obj_intersects_with(gEgg, 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 || gDude != ptr->object) { + v4 = ptr->object; + if ((ptr->flags & 0x01) != 0) { + if ((ptr->flags & 0x04) == 0) { + if ((ptr->object->fid & 0xF000000) >> 24 != 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 gameMouseRenderPrimaryAction(int x, int y, int menuItem, int width, int height) +{ + CacheEntry* menuItemFrmHandle; + int menuItemFid = buildFid(6, gGameMouseActionMenuItemFrmIds[menuItem], 0, 0, 0); + Art* menuItemFrm = artLock(menuItemFid, &menuItemFrmHandle); + if (menuItemFrm == NULL) { + return -1; + } + + CacheEntry* arrowFrmHandle; + int arrowFid = buildFid(6, gGameMouseModeFrmIds[GAME_MOUSE_MODE_ARROW], 0, 0, 0); + Art* arrowFrm = artLock(arrowFid, &arrowFrmHandle); + if (arrowFrm == NULL) { + artUnlock(menuItemFrmHandle); + // FIXME: Why this is success? + return 0; + } + + unsigned char* arrowFrmData = artGetFrameData(arrowFrm, 0, 0); + int arrowFrmWidth = artGetWidth(arrowFrm, 0, 0); + int arrowFrmHeight = artGetHeight(arrowFrm, 0, 0); + + unsigned char* menuItemFrmData = artGetFrameData(menuItemFrm, 0, 0); + int menuItemFrmWidth = artGetWidth(menuItemFrm, 0, 0); + int menuItemFrmHeight = artGetHeight(menuItemFrm, 0, 0); + + unsigned char* arrowFrmDest = gGameMouseActionPickFrmData; + unsigned char* menuItemFrmDest = gGameMouseActionPickFrmData; + + _gmouse_3d_pick_frame_hot_x = 0; + _gmouse_3d_pick_frame_hot_y = 0; + + gGameMouseActionPickFrm->xOffsets[0] = gGameMouseActionPickFrmWidth / 2; + gGameMouseActionPickFrm->yOffsets[0] = gGameMouseActionPickFrmHeight - 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; + gGameMouseActionPickFrm->yOffsets[0] -= shiftY; + arrowFrmDest += gGameMouseActionPickFrmWidth * shiftY; + } + } else { + artUnlock(arrowFrmHandle); + + arrowFid = buildFid(6, 285, 0, 0, 0); + arrowFrm = artLock(arrowFid, &arrowFrmHandle); + arrowFrmData = artGetFrameData(arrowFrm, 0, 0); + arrowFrmDest += menuItemFrmWidth; + + gGameMouseActionPickFrm->xOffsets[0] = -gGameMouseActionPickFrm->xOffsets[0]; + _gmouse_3d_pick_frame_hot_x += menuItemFrmWidth + arrowFrmWidth; + + if (maxY >= height) { + _gmouse_3d_pick_frame_hot_y += shiftY; + gGameMouseActionPickFrm->yOffsets[0] -= shiftY; + + arrowFrmDest += gGameMouseActionPickFrmWidth * shiftY; + } + } + + memset(gGameMouseActionPickFrmData, 0, gGameMouseActionPickFrmDataSize); + + blitBufferToBuffer(arrowFrmData, arrowFrmWidth, arrowFrmHeight, arrowFrmWidth, arrowFrmDest, gGameMouseActionPickFrmWidth); + blitBufferToBuffer(menuItemFrmData, menuItemFrmWidth, menuItemFrmHeight, menuItemFrmWidth, menuItemFrmDest, gGameMouseActionPickFrmWidth); + + artUnlock(arrowFrmHandle); + artUnlock(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 gameMouseRenderActionMenuItems(int x, int y, const int* menuItems, int menuItemsLength, int width, int height) +{ + _gmouse_3d_menu_actions_start = NULL; + gGameMouseActionMenuHighlightedItemIndex = 0; + gGameMouseActionMenuItemsLength = 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 = gGameMouseActionMenuItemFrmIds[menuItems[index]] & 0xFFFF; + if (index == 0) { + frmId -= 1; + } + + int fid = buildFid(6, frmId, 0, 0, 0); + + menuItemFrms[index] = artLock(fid, &(menuItemFrmHandles[index])); + if (menuItemFrms[index] == NULL) { + while (--index >= 0) { + artUnlock(menuItemFrmHandles[index]); + } + return -1; + } + } + + int fid = buildFid(6, gGameMouseModeFrmIds[GAME_MOUSE_MODE_ARROW], 0, 0, 0); + CacheEntry* arrowFrmHandle; + Art* arrowFrm = artLock(fid, &arrowFrmHandle); + if (arrowFrm == NULL) { + // FIXME: Unlock arts. + return -1; + } + + int arrowWidth = artGetWidth(arrowFrm, 0, 0); + int arrowHeight = artGetHeight(arrowFrm, 0, 0); + + int menuItemWidth = artGetWidth(menuItemFrms[0], 0, 0); + int menuItemHeight = artGetHeight(menuItemFrms[0], 0, 0); + + _gmouse_3d_menu_frame_hot_x = 0; + _gmouse_3d_menu_frame_hot_y = 0; + + gGameMouseActionMenuFrm->xOffsets[0] = gGameMouseActionMenuFrmWidth / 2; + gGameMouseActionMenuFrm->yOffsets[0] = gGameMouseActionMenuFrmHeight - 1; + + int v60 = y + menuItemsLength * menuItemHeight - 1; + int v24 = v60 - height + 2; + unsigned char* v22 = gGameMouseActionMenuFrmData; + unsigned char* v58 = v22; + + unsigned char* arrowData; + if (x + arrowWidth + menuItemWidth - 1 < width) { + arrowData = artGetFrameData(arrowFrm, 0, 0); + v58 = v22 + arrowWidth; + if (height <= v60) { + _gmouse_3d_menu_frame_hot_y += v24; + v22 += gGameMouseActionMenuFrmWidth * v24; + gGameMouseActionMenuFrm->yOffsets[0] -= v24; + } + } else { + // Mirrored arrow (from left to right). + fid = buildFid(6, 285, 0, 0, 0); + arrowFrm = artLock(fid, &arrowFrmHandle); + arrowData = artGetFrameData(arrowFrm, 0, 0); + gGameMouseActionMenuFrm->xOffsets[0] = -gGameMouseActionMenuFrm->xOffsets[0]; + _gmouse_3d_menu_frame_hot_x += menuItemWidth + arrowWidth; + if (v60 >= height) { + _gmouse_3d_menu_frame_hot_y += v24; + gGameMouseActionMenuFrm->yOffsets[0] -= v24; + v22 += gGameMouseActionMenuFrmWidth * v24; + } + } + + memset(gGameMouseActionMenuFrmData, 0, gGameMouseActionMenuFrmDataSize); + blitBufferToBuffer(arrowData, arrowWidth, arrowHeight, arrowWidth, v22, gGameMouseActionPickFrmWidth); + + unsigned char* v38 = v58; + for (int index = 0; index < menuItemsLength; index++) { + unsigned char* data = artGetFrameData(menuItemFrms[index], 0, 0); + blitBufferToBuffer(data, menuItemWidth, menuItemHeight, menuItemWidth, v38, gGameMouseActionPickFrmWidth); + v38 += gGameMouseActionMenuFrmWidth * menuItemHeight; + } + + artUnlock(arrowFrmHandle); + + for (int index = 0; index < menuItemsLength; index++) { + artUnlock(menuItemFrmHandles[index]); + } + + memcpy(gGameMouseActionMenuItems, menuItems, sizeof(*gGameMouseActionMenuItems) * menuItemsLength); + gGameMouseActionMenuItemsLength = menuItemsLength; + _gmouse_3d_menu_actions_start = v58; + + Sound* sound = soundEffectLoad("iaccuxx1", NULL); + if (sound != NULL) { + soundEffectPlay(sound); + } + + return 0; +} + +// 0x44D630 +int gameMouseHighlightActionMenuItemAtIndex(int menuItemIndex) +{ + if (menuItemIndex < 0 || menuItemIndex >= gGameMouseActionMenuItemsLength) { + return -1; + } + + CacheEntry* handle; + int fid = buildFid(6, gGameMouseActionMenuItemFrmIds[gGameMouseActionMenuItems[gGameMouseActionMenuHighlightedItemIndex]], 0, 0, 0); + Art* art = artLock(fid, &handle); + if (art == NULL) { + return -1; + } + + int width = artGetWidth(art, 0, 0); + int height = artGetHeight(art, 0, 0); + unsigned char* data = artGetFrameData(art, 0, 0); + blitBufferToBuffer(data, width, height, width, _gmouse_3d_menu_actions_start + gGameMouseActionMenuFrmWidth * height * gGameMouseActionMenuHighlightedItemIndex, gGameMouseActionMenuFrmWidth); + artUnlock(handle); + + fid = buildFid(6, gGameMouseActionMenuItemFrmIds[gGameMouseActionMenuItems[menuItemIndex]] - 1, 0, 0, 0); + art = artLock(fid, &handle); + if (art == NULL) { + return -1; + } + + data = artGetFrameData(art, 0, 0); + blitBufferToBuffer(data, width, height, width, _gmouse_3d_menu_actions_start + gGameMouseActionMenuFrmWidth * height * menuItemIndex, gGameMouseActionMenuFrmWidth); + artUnlock(handle); + + gGameMouseActionMenuHighlightedItemIndex = menuItemIndex; + + return 0; +} + +// 0x44D774 +int gameMouseRenderAccuracy(const char* string, int color) +{ + CacheEntry* crosshairFrmHandle; + int fid = buildFid(6, gGameMouseModeFrmIds[GAME_MOUSE_MODE_CROSSHAIR], 0, 0, 0); + Art* crosshairFrm = artLock(fid, &crosshairFrmHandle); + if (crosshairFrm == NULL) { + return -1; + } + + memset(gGameMouseActionHitFrmData, 0, gGameMouseActionHitFrmDataSize); + + int crosshairFrmWidth = artGetWidth(crosshairFrm, 0, 0); + int crosshairFrmHeight = artGetHeight(crosshairFrm, 0, 0); + unsigned char* crosshairFrmData = artGetFrameData(crosshairFrm, 0, 0); + blitBufferToBuffer(crosshairFrmData, + crosshairFrmWidth, + crosshairFrmHeight, + crosshairFrmWidth, + gGameMouseActionHitFrmData, + gGameMouseActionHitFrmWidth); + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + fontDrawText(gGameMouseActionHitFrmData + gGameMouseActionHitFrmWidth + crosshairFrmWidth + 1, + string, + gGameMouseActionHitFrmWidth - crosshairFrmWidth, + gGameMouseActionHitFrmWidth, + color); + + bufferOutline(gGameMouseActionHitFrmData + crosshairFrmWidth, + gGameMouseActionHitFrmWidth - crosshairFrmWidth, + gGameMouseActionHitFrmHeight, + gGameMouseActionHitFrmWidth, + _colorTable[0]); + + fontSetCurrent(oldFont); + + artUnlock(crosshairFrmHandle); + + return 0; +} + +// 0x44D878 +int gameMouseRenderActionPoints(const char* string, int color) +{ + memset(gGameMouseHexCursorFrmData, 0, gGameMouseHexCursorFrmWidth * gGameMouseHexCursorHeight); + + if (*string == '\0') { + return 0; + } + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + int length = fontGetStringWidth(string); + fontDrawText(gGameMouseHexCursorFrmData + gGameMouseHexCursorFrmWidth * (gGameMouseHexCursorHeight - fontGetLineHeight()) / 2 + (gGameMouseHexCursorFrmWidth - length) / 2, string, gGameMouseHexCursorFrmWidth, gGameMouseHexCursorFrmWidth, color); + + bufferOutline(gGameMouseHexCursorFrmData, gGameMouseHexCursorFrmWidth, gGameMouseHexCursorHeight, gGameMouseHexCursorFrmWidth, _colorTable[0]); + + fontSetCurrent(oldFont); + + int fid = buildFid(6, 1, 0, 0, 0); + gameMouseSetBouncingCursorFid(fid); + + return 0; +} + +// 0x44D954 +void gameMouseLoadItemHighlight() +{ + bool itemHighlight; + if (configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &itemHighlight)) { + gGameMouseItemHighlightEnabled = itemHighlight; + } +} + +// 0x44D984 +int gameMouseObjectsInit() +{ + int fid; + + if (gGameMouseObjectsInitialized) { + return -1; + } + + fid = buildFid(6, 0, 0, 0, 0); + if (objectCreateWithFidPid(&gGameMouseBouncingCursor, fid, -1) != 0) { + return -1; + } + + fid = buildFid(6, 1, 0, 0, 0); + if (objectCreateWithFidPid(&gGameMouseHexCursor, fid, -1) != 0) { + return -1; + } + + if (objectSetOutline(gGameMouseHexCursor, OUTLINE_PALETTED | OUTLINE_TYPE_2, NULL) != 0) { + return -1; + } + + if (gameMouseActionMenuInit() != 0) { + return -1; + } + + gGameMouseBouncingCursor->flags |= OBJECT_FLAG_0x20000000; + gGameMouseBouncingCursor->flags |= OBJECT_TEMPORARY; + gGameMouseBouncingCursor->flags |= OBJECT_FLAG_0x400; + gGameMouseBouncingCursor->flags |= OBJECT_FLAG_0x80000000; + gGameMouseBouncingCursor->flags |= OBJECT_NO_BLOCK; + + gGameMouseHexCursor->flags |= OBJECT_FLAG_0x400; + gGameMouseHexCursor->flags |= OBJECT_TEMPORARY; + gGameMouseHexCursor->flags |= OBJECT_FLAG_0x20000000; + gGameMouseHexCursor->flags |= OBJECT_FLAG_0x80000000; + gGameMouseHexCursor->flags |= OBJECT_NO_BLOCK; + + _obj_toggle_flat(gGameMouseHexCursor, NULL); + + int x; + int y; + mouseGetPosition(&x, &y); + + Rect v9; + _gmouse_3d_move_to(x, y, gElevation, &v9); + + gGameMouseObjectsInitialized = true; + + gameMouseLoadItemHighlight(); + + return 0; +} + +// NOTE: Inlined. +// +// 0x44DAC0 +int gameMouseObjectsReset() +{ + if (!gGameMouseObjectsInitialized) { + return -1; + } + + // NOTE: Uninline. + _gmouse_3d_enable_modes(); + + // NOTE: Uninline. + gameMouseResetBouncingCursorFid(); + + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + gameMouseObjectsShow(); + + gGameMouseLastX = -1; + gGameMouseLastY = -1; + _gmouse_3d_hover_test = false; + _gmouse_3d_last_move_time = _get_time(); + gameMouseLoadItemHighlight(); + + return 0; +} + +// NOTE: Inlined. +// +// 0x44DB34 +void gameMouseObjectsFree() +{ + if (gGameMouseObjectsInitialized) { + gameMouseActionMenuFree(); + + gGameMouseBouncingCursor->flags &= ~OBJECT_TEMPORARY; + gGameMouseHexCursor->flags &= ~OBJECT_TEMPORARY; + + objectDestroy(gGameMouseBouncingCursor, NULL); + objectDestroy(gGameMouseHexCursor, NULL); + + gGameMouseObjectsInitialized = false; + } +} + +// 0x44DB78 +int gameMouseActionMenuInit() +{ + int fid; + + // actmenu.frm - action menu + fid = buildFid(6, 283, 0, 0, 0); + gGameMouseActionMenuFrm = artLock(fid, &gGameMouseActionMenuFrmHandle); + if (gGameMouseActionMenuFrm == NULL) { + goto err; + } + + // actpick.frm - action pick + fid = buildFid(6, 282, 0, 0, 0); + gGameMouseActionPickFrm = artLock(fid, &gGameMouseActionPickFrmHandle); + if (gGameMouseActionPickFrm == NULL) { + goto err; + } + + // acttohit.frm - action to hit + fid = buildFid(6, 284, 0, 0, 0); + gGameMouseActionHitFrm = artLock(fid, &gGameMouseActionHitFrmHandle); + if (gGameMouseActionHitFrm == NULL) { + goto err; + } + + // blank.frm - used be mset000.frm for top of bouncing mouse cursor + fid = buildFid(6, 0, 0, 0, 0); + gGameMouseBouncingCursorFrm = artLock(fid, &gGameMouseBouncingCursorFrmHandle); + if (gGameMouseBouncingCursorFrm == NULL) { + goto err; + } + + // msef000.frm - hex mouse cursor + fid = buildFid(6, 1, 0, 0, 0); + gGameMouseHexCursorFrm = artLock(fid, &gGameMouseHexCursorFrmHandle); + if (gGameMouseHexCursorFrm == NULL) { + goto err; + } + + gGameMouseActionMenuFrmWidth = artGetWidth(gGameMouseActionMenuFrm, 0, 0); + gGameMouseActionMenuFrmHeight = artGetHeight(gGameMouseActionMenuFrm, 0, 0); + gGameMouseActionMenuFrmDataSize = gGameMouseActionMenuFrmWidth * gGameMouseActionMenuFrmHeight; + gGameMouseActionMenuFrmData = artGetFrameData(gGameMouseActionMenuFrm, 0, 0); + + gGameMouseActionPickFrmWidth = artGetWidth(gGameMouseActionPickFrm, 0, 0); + gGameMouseActionPickFrmHeight = artGetHeight(gGameMouseActionPickFrm, 0, 0); + gGameMouseActionPickFrmDataSize = gGameMouseActionPickFrmWidth * gGameMouseActionPickFrmHeight; + gGameMouseActionPickFrmData = artGetFrameData(gGameMouseActionPickFrm, 0, 0); + + gGameMouseActionHitFrmWidth = artGetWidth(gGameMouseActionHitFrm, 0, 0); + gGameMouseActionHitFrmHeight = artGetHeight(gGameMouseActionHitFrm, 0, 0); + gGameMouseActionHitFrmDataSize = gGameMouseActionHitFrmWidth * gGameMouseActionHitFrmHeight; + gGameMouseActionHitFrmData = artGetFrameData(gGameMouseActionHitFrm, 0, 0); + + gGameMouseBouncingCursorFrmWidth = artGetWidth(gGameMouseBouncingCursorFrm, 0, 0); + gGameMouseBouncingCursorFrmHeight = artGetHeight(gGameMouseBouncingCursorFrm, 0, 0); + gGameMouseBouncingCursorFrmDataSize = gGameMouseBouncingCursorFrmWidth * gGameMouseBouncingCursorFrmHeight; + gGameMouseBouncingCursorFrmData = artGetFrameData(gGameMouseBouncingCursorFrm, 0, 0); + + gGameMouseHexCursorFrmWidth = artGetWidth(gGameMouseHexCursorFrm, 0, 0); + gGameMouseHexCursorHeight = artGetHeight(gGameMouseHexCursorFrm, 0, 0); + gGameMouseHexCursorDataSize = gGameMouseHexCursorFrmWidth * gGameMouseHexCursorHeight; + gGameMouseHexCursorFrmData = artGetFrameData(gGameMouseHexCursorFrm, 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. + gameMouseActionMenuFree(); + + return -1; +} + +// 0x44DE44 +void gameMouseActionMenuFree() +{ + if (gGameMouseBouncingCursorFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseBouncingCursorFrmHandle); + } + gGameMouseBouncingCursorFrm = NULL; + gGameMouseBouncingCursorFrmHandle = INVALID_CACHE_ENTRY; + + if (gGameMouseHexCursorFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseHexCursorFrmHandle); + } + gGameMouseHexCursorFrm = NULL; + gGameMouseHexCursorFrmHandle = INVALID_CACHE_ENTRY; + + if (gGameMouseActionHitFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseActionHitFrmHandle); + } + gGameMouseActionHitFrm = NULL; + gGameMouseActionHitFrmHandle = INVALID_CACHE_ENTRY; + + if (gGameMouseActionMenuFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseActionMenuFrmHandle); + } + gGameMouseActionMenuFrm = NULL; + gGameMouseActionMenuFrmHandle = INVALID_CACHE_ENTRY; + + if (gGameMouseActionPickFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gGameMouseActionPickFrmHandle); + } + + gGameMouseActionPickFrm = NULL; + gGameMouseActionPickFrmHandle = INVALID_CACHE_ENTRY; + + gGameMouseActionPickFrmData = NULL; + gGameMouseActionPickFrmWidth = 0; + gGameMouseActionPickFrmHeight = 0; + gGameMouseActionPickFrmDataSize = 0; +} + +// 0x44DF40 +int gameMouseUpdateHexCursorFid(Rect* rect) +{ + int fid = buildFid(6, gGameMouseModeFrmIds[gGameMouseMode], 0, 0, 0); + if (gGameMouseHexCursor->fid == fid) { + return -1; + } + + return objectSetFid(gGameMouseHexCursor, fid, rect); +} + +// 0x44DF94 +int _gmouse_3d_move_to(int x, int y, int elevation, Rect* a4) +{ + if (_gmouse_mapper_mode == 0) { + if (gGameMouseMode != GAME_MOUSE_MODE_MOVE) { + int offsetX = 0; + int offsetY = 0; + CacheEntry* hexCursorFrmHandle; + Art* hexCursorFrm = artLock(gGameMouseHexCursor->fid, &hexCursorFrmHandle); + if (hexCursorFrm != NULL) { + artGetRotationOffsets(hexCursorFrm, 0, &offsetX, &offsetY); + + int frameOffsetX; + int frameOffsetY; + artGetFrameOffsets(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY); + + offsetX += frameOffsetX; + offsetY += frameOffsetY; + + artUnlock(hexCursorFrmHandle); + } + + _obj_move(gGameMouseHexCursor, x + offsetX, y + offsetY, elevation, a4); + } else { + int tile = tileFromScreenXY(x, y, 0); + if (tile != -1) { + int screenX; + int screenY; + + bool v1 = false; + Rect rect1; + if (tileToScreenXY(tile, &screenX, &screenY, 0) == 0) { + if (_obj_move(gGameMouseBouncingCursor, screenX + 16, screenY + 15, 0, &rect1) == 0) { + v1 = true; + } + } + + Rect rect2; + if (objectSetLocation(gGameMouseHexCursor, tile, elevation, &rect2) == 0) { + if (v1) { + rectUnion(&rect1, &rect2, &rect1); + } else { + rectCopy(&rect1, &rect2); + } + + rectCopy(a4, &rect1); + } + } + } + return 0; + } + + int tile; + int x1 = 0; + int y1 = 0; + + int fid = gGameMouseBouncingCursor->fid; + if ((fid & 0xF000000) >> 24 == 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; + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, &executable); + if (stricmp(executable, "mapper") == 0) { + if (_tile_roof_visible()) { + if ((gDude->flags & OBJECT_HIDDEN) == 0) { + y1 = -83; + } + } + } + } else { + tile = -1; + } + } else { + tile = tileFromScreenXY(x, y, elevation); + } + + if (tile != -1) { + bool v1 = false; + + Rect rect1; + Rect rect2; + + if (objectSetLocation(gGameMouseBouncingCursor, tile, elevation, &rect1) == 0) { + if (x1 != 0 || y1 != 0) { + if (_obj_offset(gGameMouseBouncingCursor, x1, y1, &rect2) == 0) { + rectUnion(&rect1, &rect2, &rect1); + } + } + v1 = true; + } + + if (gGameMouseMode != GAME_MOUSE_MODE_MOVE) { + int offsetX = 0; + int offsetY = 0; + CacheEntry* hexCursorFrmHandle; + Art* hexCursorFrm = artLock(gGameMouseHexCursor->fid, &hexCursorFrmHandle); + if (hexCursorFrm != NULL) { + artGetRotationOffsets(hexCursorFrm, 0, &offsetX, &offsetY); + + int frameOffsetX; + int frameOffsetY; + artGetFrameOffsets(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY); + + offsetX += frameOffsetX; + offsetY += frameOffsetY; + + artUnlock(hexCursorFrmHandle); + } + + if (_obj_move(gGameMouseHexCursor, x + offsetX, y + offsetY, elevation, &rect2) == 0) { + if (v1) { + rectUnion(&rect1, &rect2, &rect1); + } else { + rectCopy(&rect1, &rect2); + v1 = true; + } + } + } else { + if (objectSetLocation(gGameMouseHexCursor, tile, elevation, &rect2) == 0) { + if (v1) { + rectUnion(&rect1, &rect2, &rect1); + } else { + rectCopy(&rect1, &rect2); + v1 = true; + } + } + } + + if (v1) { + rectCopy(a4, &rect1); + } + } + + return 0; +} + +// 0x44E42C +int gameMouseHandleScrolling(int x, int y, int cursor) +{ + if (!_gmouse_scrolling_enabled) { + return -1; + } + + int flags = 0; + + if (x <= _scr_size.left) { + flags |= SCROLLABLE_W; + } + + if (x >= _scr_size.right) { + flags |= SCROLLABLE_E; + } + + if (y <= _scr_size.top) { + flags |= SCROLLABLE_N; + } + + if (y >= _scr_size.bottom) { + 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 = mapScroll(dx, dy); + switch (rc) { + case -1: + // Scrolling is blocked for whatever reason, upgrade cursor to + // appropriate blocked version. + cursor += 8; + // FALLTHROUGH + case 0: + gameMouseSetCursor(cursor); + break; + } + + return 0; +} + +// 0x44E544 +void _gmouse_remove_item_outline(Object* object) +{ + if (gGameMouseHighlightedItem != NULL && gGameMouseHighlightedItem == object) { + Rect rect; + if (objectClearOutline(object, &rect) == 0) { + tileWindowRefreshRect(&rect, gElevation); + } + gGameMouseHighlightedItem = NULL; + } +} + +// 0x44E580 +int objectIsDoor(Object* object) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_SCENERY) { + return false; + } + + Proto* proto; + if (protoGetProto(object->pid, &proto) == -1) { + return false; + } + + return proto->scenery.type == SCENERY_TYPE_DOOR; +} diff --git a/src/game_mouse.h b/src/game_mouse.h new file mode 100644 index 0000000..5c70a19 --- /dev/null +++ b/src/game_mouse.h @@ -0,0 +1,190 @@ +#ifndef GAME_MOUSE_H +#define GAME_MOUSE_H + +#include "art.h" +#include "geometry.h" +#include "obj_types.h" + +#include + +typedef enum ScrollableDirections { + SCROLLABLE_W = 0x01, + SCROLLABLE_E = 0x02, + SCROLLABLE_N = 0x04, + SCROLLABLE_S = 0x08, +} ScrollableDirections; + +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 gGameMouseInitialized; +extern int _gmouse_enabled; +extern int _gmouse_mapper_mode; +extern int _gmouse_click_to_scroll; +extern int _gmouse_scrolling_enabled; +extern int gGameMouseCursor; +extern CacheEntry* gGameMouseCursorFrmHandle; +extern const int gGameMouseCursorFrmIds[MOUSE_CURSOR_TYPE_COUNT]; +extern bool gGameMouseObjectsInitialized; +extern bool _gmouse_3d_hover_test; +extern unsigned int _gmouse_3d_last_move_time; +extern Art* gGameMouseActionMenuFrm; +extern CacheEntry* gGameMouseActionMenuFrmHandle; +extern int gGameMouseActionMenuFrmWidth; +extern int gGameMouseActionMenuFrmHeight; +extern int gGameMouseActionMenuFrmDataSize; +extern int _gmouse_3d_menu_frame_hot_x; +extern int _gmouse_3d_menu_frame_hot_y; +extern unsigned char* gGameMouseActionMenuFrmData; +extern Art* gGameMouseActionPickFrm; +extern CacheEntry* gGameMouseActionPickFrmHandle; +extern int gGameMouseActionPickFrmWidth; +extern int gGameMouseActionPickFrmHeight; +extern int gGameMouseActionPickFrmDataSize; +extern int _gmouse_3d_pick_frame_hot_x; +extern int _gmouse_3d_pick_frame_hot_y; +extern unsigned char* gGameMouseActionPickFrmData; +extern Art* gGameMouseActionHitFrm; +extern CacheEntry* gGameMouseActionHitFrmHandle; +extern int gGameMouseActionHitFrmWidth; +extern int gGameMouseActionHitFrmHeight; +extern int gGameMouseActionHitFrmDataSize; +extern unsigned char* gGameMouseActionHitFrmData; +extern Art* gGameMouseBouncingCursorFrm; +extern CacheEntry* gGameMouseBouncingCursorFrmHandle; +extern int gGameMouseBouncingCursorFrmWidth; +extern int gGameMouseBouncingCursorFrmHeight; +extern int gGameMouseBouncingCursorFrmDataSize; +extern unsigned char* gGameMouseBouncingCursorFrmData; +extern Art* gGameMouseHexCursorFrm; +extern CacheEntry* gGameMouseHexCursorFrmHandle; +extern int gGameMouseHexCursorFrmWidth; +extern int gGameMouseHexCursorHeight; +extern int gGameMouseHexCursorDataSize; +extern unsigned char* gGameMouseHexCursorFrmData; +extern unsigned char gGameMouseActionMenuItemsLength; +extern unsigned char* _gmouse_3d_menu_actions_start; +extern unsigned char gGameMouseActionMenuHighlightedItemIndex; +extern const short gGameMouseActionMenuItemFrmIds[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; +extern int _gmouse_3d_modes_enabled; +extern int gGameMouseMode; +extern int gGameMouseModeFrmIds[GAME_MOUSE_MODE_COUNT]; +extern const int gGameMouseModeSkills[GAME_MOUSE_MODE_SKILL_COUNT]; +extern int gGameMouseAnimatedCursorNextFrame; +extern unsigned int gGameMouseAnimatedCursorLastUpdateTimestamp; +extern int _gmouse_bk_last_cursor; +extern bool gGameMouseItemHighlightEnabled; +extern Object* gGameMouseHighlightedItem; +extern bool _gmouse_clicked_on_edge; +extern int dword_518D9C; + +extern int gGameMouseActionMenuItems[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; +extern int gGameMouseLastX; +extern int gGameMouseLastY; +extern Object* gGameMouseBouncingCursor; +extern Object* gGameMouseHexCursor; +extern Object* gGameMousePointedObject; + +int gameMouseInit(); +int gameMouseReset(); +void gameMouseExit(); +void _gmouse_enable(); +void _gmouse_disable(int a1); +void _gmouse_enable_scrolling(); +void _gmouse_disable_scrolling(); +int _gmouse_get_click_to_scroll(); +int _gmouse_is_scrolling(); +void gameMouseRefresh(); +void _gmouse_handle_event(int mouseX, int mouseY, int mouseState); +int gameMouseSetCursor(int cursor); +int gameMouseGetCursor(); +void _gmouse_3d_enable_modes(); +void gameMouseSetMode(int a1); +int gameMouseGetMode(); +void gameMouseCycleMode(); +void _gmouse_3d_refresh(); +int gameMouseSetBouncingCursorFid(int fid); +void gameMouseResetBouncingCursorFid(); +void gameMouseObjectsShow(); +void gameMouseObjectsHide(); +bool gameMouseObjectsIsVisible(); +Object* gameMouseGetObjectUnderCursor(int objectType, bool a2, int elevation); +int gameMouseRenderPrimaryAction(int x, int y, int menuItem, int width, int height); +int _gmouse_3d_pick_frame_hot(int* a1, int* a2); +int gameMouseRenderActionMenuItems(int x, int y, const int* menuItems, int menuItemsCount, int width, int height); +int gameMouseHighlightActionMenuItemAtIndex(int menuItemIndex); +int gameMouseRenderAccuracy(const char* string, int color); +int gameMouseRenderActionPoints(const char* string, int color); +void gameMouseLoadItemHighlight(); +int gameMouseObjectsInit(); +int gameMouseObjectsReset(); +void gameMouseObjectsFree(); +int gameMouseActionMenuInit(); +void gameMouseActionMenuFree(); +int gameMouseUpdateHexCursorFid(Rect* rect); +int _gmouse_3d_move_to(int x, int y, int elevation, Rect* a4); +int gameMouseHandleScrolling(int x, int y, int cursor); +void _gmouse_remove_item_outline(Object* object); +int objectIsDoor(Object* object); + +#endif /* GAME_MOUSE_H */ diff --git a/src/game_movie.c b/src/game_movie.c new file mode 100644 index 0000000..58d391e --- /dev/null +++ b/src/game_movie.c @@ -0,0 +1,339 @@ +#include "game_movie.h" + +#include "color.h" +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "movie.h" +#include "movie_effect.h" +#include "palette.h" +#include "text_font.h" +#include "widget.h" +#include "window_manager.h" + +#include +#include + +// 0x50352A +const float flt_50352A = 0.032258064f; + +// 0x518DA0 +const char* gMovieFileNames[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 +char* gMoviePaletteFilePaths[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 +bool gGameMovieIsPlaying = false; + +// 0x518E2C +bool gGameMovieFaded = false; + +// 0x596C78 +unsigned char gGameMoviesSeen[MOVIE_COUNT]; + +// 0x596C89 +char gGameMovieSubtitlesFilePath[MAX_PATH]; + +// gmovie_init +// 0x44E5C0 +int gameMoviesInit() +{ + int v1 = 0; + if (backgroundSoundIsEnabled()) { + v1 = backgroundSoundGetVolume(); + } + + movieSetVolume(v1); + + movieSetBuildSubtitleFilePathProc(gameMovieBuildSubtitlesFilePath); + + memset(gGameMoviesSeen, 0, sizeof(gGameMoviesSeen)); + + gGameMovieIsPlaying = false; + gGameMovieFaded = false; + + return 0; +} + +// 0x44E60C +void gameMoviesReset() +{ + memset(gGameMoviesSeen, 0, sizeof(gGameMoviesSeen)); + + gGameMovieIsPlaying = false; + gGameMovieFaded = false; +} + +// 0x44E638 +int gameMoviesLoad(File* stream) +{ + if (fileRead(gGameMoviesSeen, sizeof(*gGameMoviesSeen), MOVIE_COUNT, stream) != MOVIE_COUNT) { + return -1; + } + + return 0; +} + +// 0x44E664 +int gameMoviesSave(File* stream) +{ + if (fileWrite(gGameMoviesSeen, sizeof(*gGameMoviesSeen), MOVIE_COUNT, stream) != MOVIE_COUNT) { + return -1; + } + + return 0; +} + +// gmovie_play +// 0x44E690 +int gameMoviePlay(int movie, int flags) +{ + gGameMovieIsPlaying = true; + + const char* movieFileName = gMovieFileNames[movie]; + debugPrint("\nPlaying movie: %s\n", movieFileName); + + char* language; + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { + debugPrint("\ngmovie_play() - Error: Unable to determine language!\n"); + gGameMovieIsPlaying = false; + return -1; + } + + char movieFilePath[MAX_PATH]; + int movieFileSize; + bool movieFound = false; + + if (stricmp(language, ENGLISH) != 0) { + sprintf(movieFilePath, "art\\%s\\cuts\\%s", language, gMovieFileNames[movie]); + movieFound = dbGetFileSize(movieFilePath, &movieFileSize) == 0; + } + + if (!movieFound) { + sprintf(movieFilePath, "art\\cuts\\%s", gMovieFileNames[movie]); + movieFound = dbGetFileSize(movieFilePath, &movieFileSize) == 0; + } + + if (!movieFound) { + debugPrint("\ngmovie_play() - Error: Unable to open %s\n", gMovieFileNames[movie]); + gGameMovieIsPlaying = false; + return -1; + } + + if ((flags & GAME_MOVIE_FADE_IN) != 0) { + paletteFadeTo(gPaletteBlack); + gGameMovieFaded = true; + } + + int win = windowCreate(0, 0, 640, 480, 0, WINDOW_FLAG_0x10); + if (win == -1) { + gGameMovieIsPlaying = false; + return -1; + } + + if ((flags & GAME_MOVIE_STOP_MUSIC) != 0) { + backgroundSoundDelete(); + } else if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) { + backgroundSoundPause(); + } + + windowRefresh(win); + + bool subtitlesEnabled = false; + int v1 = 4; + configGetBool(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitlesEnabled); + if (subtitlesEnabled) { + char* subtitlesFilePath = gameMovieBuildSubtitlesFilePath(movieFilePath); + + int subtitlesFileSize; + if (dbGetFileSize(subtitlesFilePath, &subtitlesFileSize) == 0) { + v1 = 12; + } else { + subtitlesEnabled = false; + } + } + + movieSetFlags(v1); + + int oldTextColor; + int oldFont; + if (subtitlesEnabled) { + char* subtitlesPaletteFilePath; + if (gMoviePaletteFilePaths[movie] != NULL) { + subtitlesPaletteFilePath = gMoviePaletteFilePaths[movie]; + } else { + subtitlesPaletteFilePath = "art\\cuts\\subtitle.pal"; + } + + colorPaletteLoad(subtitlesPaletteFilePath); + + oldTextColor = widgetGetTextColor(); + widgetSetTextColor(1.0, 1.0, 1.0); + + oldFont = fontGetCurrent(); + widgetSetFont(101); + } + + bool cursorWasHidden = cursorIsHidden(); + if (cursorWasHidden) { + gameMouseSetCursor(MOUSE_CURSOR_NONE); + mouseShowCursor(); + } + + while (mouseGetEvent() != 0) { + _mouse_info(); + } + + mouseHideCursor(); + colorCycleDisable(); + + movieEffectsLoad(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(); + paletteSetEntries(gPaletteBlack); + + gGameMoviesSeen[movie] = 1; + + colorCycleEnable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (!cursorWasHidden) { + mouseShowCursor(); + } + + if (subtitlesEnabled) { + colorPaletteLoad("color.pal"); + + widgetSetFont(oldFont); + + float r = (float)((_Color2RGB_(oldTextColor) & 0x7C00) >> 10) * flt_50352A; + float g = (float)((_Color2RGB_(oldTextColor) & 0x3E0) >> 5) * flt_50352A; + float b = (float)(_Color2RGB_(oldTextColor) & 0x1F) * flt_50352A; + widgetSetTextColor(r, g, b); + } + + windowDestroy(win); + + if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) { + backgroundSoundResume(); + } + + if ((flags & GAME_MOVIE_FADE_OUT) != 0) { + if (!subtitlesEnabled) { + colorPaletteLoad("color.pal"); + } + + paletteFadeTo(_cmap); + gGameMovieFaded = false; + } + + gGameMovieIsPlaying = false; + return 0; +} + +// 0x44EAE4 +void gameMovieFadeOut() +{ + if (gGameMovieFaded) { + paletteFadeTo(_cmap); + gGameMovieFaded = false; + } +} + +// 0x44EB04 +bool gameMovieIsSeen(int movie) +{ + return gGameMoviesSeen[movie] == 1; +} + +// 0x44EB14 +bool gameMovieIsPlaying() +{ + return gGameMovieIsPlaying; +} + +// 0x44EB1C +char* gameMovieBuildSubtitlesFilePath(char* movieFilePath) +{ + char* language; + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language); + + char* path = movieFilePath; + + char* separator = strrchr(path, '\\'); + if (separator != NULL) { + path = separator + 1; + } + + sprintf(gGameMovieSubtitlesFilePath, "text\\%s\\cuts\\%s", language, path); + + char* pch = strrchr(gGameMovieSubtitlesFilePath, '.'); + if (*pch != '\0') { + *pch = '\0'; + } + + strcpy(gGameMovieSubtitlesFilePath + strlen(gGameMovieSubtitlesFilePath), ".SVE"); + + return gGameMovieSubtitlesFilePath; +} diff --git a/src/game_movie.h b/src/game_movie.h new file mode 100644 index 0000000..88a70b6 --- /dev/null +++ b/src/game_movie.h @@ -0,0 +1,59 @@ +#ifndef GAME_MOVIE_H +#define GAME_MOVIE_H + +#include "db.h" + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +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; + +extern const float flt_50352A; + +extern const char* gMovieFileNames[MOVIE_COUNT]; +extern char* gMoviePaletteFilePaths[MOVIE_COUNT]; +extern bool gGameMovieIsPlaying; +extern bool gGameMovieFaded; + +extern char gGameMovieSubtitlesFilePath[MAX_PATH]; +extern unsigned char gGameMoviesSeen[MOVIE_COUNT]; + +int gameMoviesInit(); +void gameMoviesReset(); +int gameMoviesLoad(File* stream); +int gameMoviesSave(File* stream); +int gameMoviePlay(int movie, int flags); +void gameMovieFadeOut(); +bool gameMovieIsSeen(int movie); +bool gameMovieIsPlaying(); +char* gameMovieBuildSubtitlesFilePath(char* movieFilePath); + +#endif /* GAME_MOVIE_H */ diff --git a/src/game_palette.c b/src/game_palette.c new file mode 100644 index 0000000..09cd371 --- /dev/null +++ b/src/game_palette.c @@ -0,0 +1,25 @@ +#include "game_palette.h" + +#include "color.h" + +// 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; +} diff --git a/src/game_palette.h b/src/game_palette.h new file mode 100644 index 0000000..774ea34 --- /dev/null +++ b/src/game_palette.h @@ -0,0 +1,6 @@ +#ifndef GAME_PALETTE_H +#define GAME_PALETTE_H + +int _HighRGB_(int a1); + +#endif /* GAME_PALETTE_H */ diff --git a/src/game_sound.c b/src/game_sound.c new file mode 100644 index 0000000..865aedd --- /dev/null +++ b/src/game_sound.c @@ -0,0 +1,2107 @@ +#include "game_sound.h" + +#include "animation.h" +#include "audio.h" +#include "audio_file.h" +#include "combat.h" +#include "core.h" +#include "db.h" +#include "debug.h" +#include "game_config.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "movie.h" +#include "object.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "sound_effects_cache.h" +#include "stat.h" +#include "window_manager.h" +#include "world_map.h" + +#include +#include +#include + +// 0x5035BC +char _aSoundSfx[] = "sound\\sfx\\"; + +// 0x5035C8 +char _aSoundMusic_0[] = "sound\\music\\"; + +// 0x5035D8 +char _aSoundSpeech_0[] = "sound\\speech\\"; + +// 0x518E30 +bool gGameSoundInitialized = false; + +// 0x518E34 +bool gGameSoundDebugEnabled = false; + +// 0x518E38 +bool gMusicEnabled = false; + +// 0x518E3C +int _gsound_background_df_vol = 0; + +// 0x518E40 +int _gsound_background_fade = 0; + +// 0x518E44 +bool gSpeechEnabled = false; + +// 0x518E48 +bool gSoundEffectsEnabled = false; + +// number of active effects (max 4) +int _gsound_active_effect_counter; + +// 0x518E50 +Sound* gBackgroundSound = NULL; + +// 0x518E54 +Sound* gSpeechSound = NULL; + +// 0x518E58 +SoundEndCallback* gBackgroundSoundEndCallback = NULL; + +// 0x518E5C +SoundEndCallback* gSpeechEndCallback = NULL; + +// 0x518E60 +char _snd_lookup_weapon_type[WEAPON_SOUND_EFFECT_COUNT] = { + 'R', // Ready + 'A', // Attack + 'O', // Out of ammo + 'F', // Firing + 'H', // Hit +}; + +// 0x518E65 +char _snd_lookup_scenery_action[SCENERY_SOUND_EFFECT_COUNT] = { + 'O', // Open + 'C', // Close + 'L', // Lock + 'N', // Unlock + 'U', // Use +}; + +// 0x518E6C +int _background_storage_requested = -1; + +// 0x518E70 +int _background_loop_requested = -1; + +// 0x518E74 +char* _sound_sfx_path = _aSoundSfx; + +// 0x518E78 +char* _sound_music_path1 = _aSoundMusic_0; + +// 0x518E7C +char* _sound_music_path2 = _aSoundMusic_0; + +// 0x518E80 +char* _sound_speech_path = _aSoundSpeech_0; + +// 0x518E84 +int gMasterVolume = VOLUME_MAX; + +// 0x518E88 +int gMusicVolume = VOLUME_MAX; + +// 0x518E8C +int gSpeechVolume = VOLUME_MAX; + +// 0x518E90 +int gSoundEffectsVolume = VOLUME_MAX; + +// 0x518E94 +int _detectDevices = -1; + +// 0x518E98 +int _lastTime_1 = 0; + +// 0x596EB0 +char _background_fname_copied[MAX_PATH]; + +// 0x596FB5 +char _sfx_file_name[13]; + +// NOTE: I'm mot sure about it's size. Why not MAX_PATH? +// +// 0x596FC2 +char gBackgroundSoundFileName[270]; + +// 0x44FC70 +int gameSoundInit() +{ + if (gGameSoundInitialized) { + if (gGameSoundDebugEnabled) { + debugPrint("Trying to initialize gsound twice.\n"); + } + return -1; + } + + bool initialize; + configGetBool(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, &initialize); + if (!initialize) { + return 0; + } + + configGetBool(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_KEY, &gGameSoundDebugEnabled); + + if (gGameSoundDebugEnabled) { + debugPrint("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 (gGameSoundDebugEnabled) { + debugPrint("Music paths way too long.\n"); + } + return -1; + } + + // gsound_setup_paths + if (_gsound_setup_paths() != 0) { + return -1; + } + + soundSetMemoryProcs(internal_malloc, internal_realloc, internal_free); + + // initialize direct sound + if (soundInit(_detectDevices, 24, 0x8000, 0x8000, 22050) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed!\n"); + } + + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint("success.\n"); + } + + audioFileInit(gameSoundIsCompressed); + audioInit(gameSoundIsCompressed); + + int cacheSize; + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, &cacheSize); + if (cacheSize >= 0x40000) { + debugPrint("\n!!! Config file needs adustment. Please remove the "); + debugPrint("cache_size line and run fallout again. This will reset "); + debugPrint("cache_size to the new default, which is expressed in K.\n"); + return -1; + } + + if (soundEffectsCacheInit(cacheSize << 10, _sound_sfx_path) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Unable to initialize sound effects cache.\n"); + } + } + + if (soundSetDefaultFileIO(gameSoundFileOpen, gameSoundFileClose, gameSoundFileRead, gameSoundFileWrite, gameSoundFileSeek, gameSoundFileTell, gameSoundFileGetSize) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Failure setting sound I/O calls.\n"); + } + return -1; + } + + tickersAdd(_gsound_bkg_proc); + gGameSoundInitialized = true; + + // SOUNDS + bool sounds = 0; + configGetBool(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, &sounds); + + if (gGameSoundDebugEnabled) { + debugPrint("Sounds are "); + } + + if (sounds) { + // NOTE: Uninline. + soundEffectsEnable(); + } else { + if (gGameSoundDebugEnabled) { + debugPrint(" not "); + } + } + + if (gGameSoundDebugEnabled) { + debugPrint("on.\n"); + } + + // MUSIC + bool music = 0; + configGetBool(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, &music); + + if (gGameSoundDebugEnabled) { + debugPrint("Music is "); + } + + if (music) { + // NOTE: Uninline. + backgroundSoundEnable(); + } else { + if (gGameSoundDebugEnabled) { + debugPrint(" not "); + } + } + + if (gGameSoundDebugEnabled) { + debugPrint("on.\n"); + } + + // SPEEECH + bool speech = 0; + configGetBool(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, &speech); + + if (gGameSoundDebugEnabled) { + debugPrint("Speech is "); + } + + if (speech) { + // NOTE: Uninline. + speechEnable(); + } else { + if (gGameSoundDebugEnabled) { + debugPrint(" not "); + } + } + + if (gGameSoundDebugEnabled) { + debugPrint("on.\n"); + } + + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &gMasterVolume); + gameSoundSetMasterVolume(gMasterVolume); + + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &gMusicVolume); + backgroundSoundSetVolume(gMusicVolume); + + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &gSoundEffectsVolume); + soundEffectsSetVolume(gSoundEffectsVolume); + + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &gSpeechVolume); + speechSetVolume(gSpeechVolume); + + _gsound_background_fade = 0; + gBackgroundSoundFileName[0] = '\0'; + + return 0; +} + +// 0x450164 +void gameSoundReset() +{ + if (!gGameSoundInitialized) { + return; + } + + if (gGameSoundDebugEnabled) { + debugPrint("Resetting sound system..."); + } + + // NOTE: Uninline. + speechDelete(); + + if (_gsound_background_df_vol) { + // NOTE: Uninline. + backgroundSoundEnable(); + } + + backgroundSoundDelete(); + + _gsound_background_fade = 0; + + soundDeleteAll(); + + soundEffectsCacheFlush(); + + _gsound_active_effect_counter = 0; + + if (gGameSoundDebugEnabled) { + debugPrint("done.\n"); + } + + return; +} + +// 0x450244 +int gameSoundExit() +{ + if (!gGameSoundInitialized) { + return -1; + } + + tickersRemove(_gsound_bkg_proc); + + // NOTE: Uninline. + speechDelete(); + + backgroundSoundDelete(); + gameSoundDeleteOldMusicFile(); + soundExit(); + soundEffectsCacheExit(); + audioFileExit(); + audioExit(); + + gGameSoundInitialized = false; + + return 0; +} + +// NOTE: Inlined. +// +// 0x4502BC +void soundEffectsEnable() +{ + if (gGameSoundInitialized) { + gSoundEffectsEnabled = true; + } +} + +// NOTE: Inlined. +// +// 0x4502D0 +void soundEffectsDisable() +{ + if (gGameSoundInitialized) { + gSoundEffectsEnabled = false; + } +} + +// 0x4502E4 +int soundEffectsIsEnabled() +{ + return gSoundEffectsEnabled; +} + +// 0x4502EC +int gameSoundSetMasterVolume(int volume) +{ + if (!gGameSoundInitialized) { + return -1; + } + + if (volume < VOLUME_MIN && volume > VOLUME_MAX) { + if (gGameSoundDebugEnabled) { + debugPrint("Requested master volume out of range.\n"); + } + return -1; + } + + if (_gsound_background_df_vol && volume != 0 && backgroundSoundGetVolume() != 0) { + // NOTE: Uninline. + backgroundSoundEnable(); + _gsound_background_df_vol = 0; + } + + if (_soundSetMasterVolume(volume) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Error setting master sound volume.\n"); + } + return -1; + } + + gMasterVolume = volume; + if (gMusicEnabled && volume == 0) { + // NOTE: Uninline. + backgroundSoundDisable(); + _gsound_background_df_vol = 1; + } + + return 0; +} + +// 0x450410 +int gameSoundGetMasterVolume() +{ + return gMasterVolume; +} + +// 0x450418 +int soundEffectsSetVolume(int volume) +{ + if (!gGameSoundInitialized || volume < VOLUME_MIN || volume > VOLUME_MAX) { + if (gGameSoundDebugEnabled) { + debugPrint("Error setting sfx volume.\n"); + } + return -1; + } + + gSoundEffectsVolume = volume; + + return 0; +} + +// 0x450454 +int soundEffectsGetVolume() +{ + return gSoundEffectsVolume; +} + +// NOTE: Inlined. +// +// 0x45045C +void backgroundSoundDisable() +{ + if (gGameSoundInitialized) { + if (gMusicEnabled) { + backgroundSoundDelete(); + movieSetVolume(0); + gMusicEnabled = false; + } + } +} + +// NOTE: Inlined. +// +// 0x450488 +void backgroundSoundEnable() +{ + if (gGameSoundInitialized) { + if (!gMusicEnabled) { + movieSetVolume((int)(gMusicVolume * 0.94)); + gMusicEnabled = true; + backgroundSoundRestart(12); + } + } +} + +// 0x4504D4 +int backgroundSoundIsEnabled() +{ + return gMusicEnabled; +} + +// 0x4504DC +void backgroundSoundSetVolume(int volume) +{ + if (!gGameSoundInitialized) { + return; + } + + if (volume < VOLUME_MIN || volume > VOLUME_MAX) { + if (gGameSoundDebugEnabled) { + debugPrint("Requested background volume out of range.\n"); + } + return; + } + + gMusicVolume = volume; + + if (_gsound_background_df_vol) { + // NOTE: Uninline. + backgroundSoundEnable(); + _gsound_background_df_vol = 0; + } + + if (gMusicEnabled) { + movieSetVolume((int)(volume * 0.94)); + } + + if (gMusicEnabled) { + if (gBackgroundSound != NULL) { + soundSetVolume(gBackgroundSound, (int)(gMusicVolume * 0.94)); + } + } + + if (gMusicEnabled) { + if (volume == 0 || gameSoundGetMasterVolume() == 0) { + // NOTE: Uninline. + backgroundSoundDisable(); + _gsound_background_df_vol = 1; + } + } +} + +// 0x450618 +int backgroundSoundGetVolume() +{ + return gMusicVolume; +} + +// +int _gsound_background_volume_get_set(int volume) +{ + int oldMusicVolume = gMusicVolume; + backgroundSoundSetVolume(volume); + return oldMusicVolume; +} + +// 0x450650 +void backgroundSoundSetEndCallback(SoundEndCallback* callback) +{ + gBackgroundSoundEndCallback = callback; +} + +// NOTE: There are no references to this function. +// +// 0x450670 +int backgroundSoundGetDuration() +{ + return soundGetDuration(gBackgroundSound); +} + +// [fileName] is base file name, without path and extension. +// +// 0x45067C +int backgroundSoundLoad(const char* fileName, int a2, int a3, int a4) +{ + int rc; + + _background_storage_requested = a3; + _background_loop_requested = a4; + + strcpy(gBackgroundSoundFileName, fileName); + + if (!gGameSoundInitialized) { + return -1; + } + + if (!gMusicEnabled) { + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint("Loading background sound file %s%s...", fileName, ".acm"); + } + + backgroundSoundDelete(); + + rc = _gsound_background_allocate(&gBackgroundSound, a3, a4); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because sound could not be allocated.\n"); + } + + gBackgroundSound = NULL; + return -1; + } + + rc = soundSetFileIO(gBackgroundSound, audioFileOpen, audioFileClose, audioFileRead, NULL, audioFileSeek, gameSoundFileTellNotImplemented, audioFileGetSize); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because file IO could not be set for compression.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + + rc = soundSetChannels(gBackgroundSound, 3); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because the channel could not be set.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + + char path[MAX_PATH + 1]; + if (a3 == 13) { + rc = gameSoundFindBackgroundSoundPath(path, fileName); + } else if (a3 == 14) { + rc = gameSoundFindBackgroundSoundPathWithCopy(path, fileName); + } + + if (rc != SOUND_NO_ERROR) { + if (gGameSoundDebugEnabled) { + debugPrint("'failed because the file could not be found.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + + if (a4 == 16) { + rc = soundSetLooping(gBackgroundSound, 0xFFFF); + if (rc != SOUND_NO_ERROR) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because looping could not be set.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + } + + rc = soundSetCallback(gBackgroundSound, backgroundSoundCallback, NULL); + if (rc != SOUND_NO_ERROR) { + if (gGameSoundDebugEnabled) { + debugPrint("soundSetCallback failed for background sound\n"); + } + } + + if (a2 == 11) { + rc = soundSetReadLimit(gBackgroundSound, 0x40000); + if (rc != SOUND_NO_ERROR) { + if (gGameSoundDebugEnabled) { + debugPrint("unable to set read limit "); + } + } + } + + rc = soundLoad(gBackgroundSound, path); + if (rc != SOUND_NO_ERROR) { + if (gGameSoundDebugEnabled) { + debugPrint("failed on call to soundLoad.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + + if (a2 != 11) { + rc = soundSetReadLimit(gBackgroundSound, 0x40000); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("unable to set read limit "); + } + } + } + + if (a2 == 10) { + return 0; + } + + rc = backgroundSoundPlay(); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed starting to play.\n"); + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint("succeeded.\n"); + } + + return 0; +} + +// 0x450A08 +int _gsound_background_play_level_music(const char* a1, int a2) +{ + return backgroundSoundLoad(a1, a2, 14, 16); +} + +// 0x450AB4 +void backgroundSoundDelete() +{ + if (gGameSoundInitialized && gMusicEnabled && gBackgroundSound) { + if (_gsound_background_fade) { + if (_soundFade(gBackgroundSound, 2000, 0) == 0) { + gBackgroundSound = NULL; + return; + } + } + + soundDelete(gBackgroundSound); + gBackgroundSound = NULL; + } +} + +// 0x450B0C +void backgroundSoundRestart(int value) +{ + if (gBackgroundSoundFileName[0] != '\0') { + if (backgroundSoundLoad(gBackgroundSoundFileName, value, _background_storage_requested, _background_loop_requested) != 0) { + if (gGameSoundDebugEnabled) + debugPrint(" background restart failed "); + } + } +} + +// 0x450B50 +void backgroundSoundPause() +{ + if (gBackgroundSound != NULL) { + soundPause(gBackgroundSound); + } +} + +// 0x450B64 +void backgroundSoundResume() +{ + if (gBackgroundSound != NULL) { + soundResume(gBackgroundSound); + } +} + +// NOTE: Inlined. +// +// 0x450B78 +void speechDisable() +{ + if (gGameSoundInitialized) { + if (gSpeechEnabled) { + speechDelete(); + gSpeechEnabled = false; + } + } +} + +// NOTE: Inlined. +// +// 0x450BC0 +void speechEnable() +{ + if (gGameSoundInitialized) { + if (!gSpeechEnabled) { + gSpeechEnabled = true; + } + } +} + +// 0x450BE0 +int speechIsEnabled() +{ + return gSpeechEnabled; +} + +// 0x450BE8 +void speechSetVolume(int volume) +{ + if (!gGameSoundInitialized) { + return; + } + + if (volume < VOLUME_MIN || volume > VOLUME_MAX) { + if (gGameSoundDebugEnabled) { + debugPrint("Requested speech volume out of range.\n"); + } + return; + } + + gSpeechVolume = volume; + + if (gSpeechEnabled) { + if (gSpeechSound != NULL) { + soundSetVolume(gSpeechSound, (int)(volume * 0.69)); + } + } +} + +// 0x450C5C +int speechGetVolume() +{ + return gSpeechVolume; +} + +// 0x450C64 +int _gsound_speech_volume_get_set(int volume) +{ + int oldVolume = gSpeechVolume; + speechSetVolume(volume); + return oldVolume; +} + +// 0x450C74 +void speechSetEndCallback(SoundEndCallback* callback) +{ + gSpeechEndCallback = callback; +} + +// 0x450C94 +int speechGetDuration() +{ + return soundGetDuration(gSpeechSound); +} + +// 0x450CA0 +int speechLoad(const char* fname, int a2, int a3, int a4) +{ + char path[MAX_PATH + 1]; + + if (!gGameSoundInitialized) { + return -1; + } + + if (!gSpeechEnabled) { + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint("Loading speech sound file %s%s...", fname, ".ACM"); + } + + // uninline + speechDelete(); + + if (_gsound_background_allocate(&gSpeechSound, a3, a4)) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because sound could not be allocated.\n"); + } + gSpeechSound = NULL; + return -1; + } + + if (soundSetFileIO(gSpeechSound, &audioOpen, &audioClose, &audioRead, NULL, &audioSeek, &gameSoundFileTellNotImplemented, &audioGetSize)) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because file IO could not be set for compression.\n"); + } + soundDelete(gSpeechSound); + gSpeechSound = NULL; + return -1; + } + + if (gameSoundFindSpeechSoundPath(path, fname)) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because the file could not be found.\n"); + } + soundDelete(gSpeechSound); + gSpeechSound = NULL; + return -1; + } + + if (a4 == 16) { + if (soundSetLooping(gSpeechSound, 0xFFFF)) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because looping could not be set.\n"); + } + soundDelete(gSpeechSound); + gSpeechSound = NULL; + return -1; + } + } + + if (soundSetCallback(gSpeechSound, speechCallback, NULL)) { + if (gGameSoundDebugEnabled) { + debugPrint("soundSetCallback failed for speech sound\n"); + } + } + + if (a2 == 11) { + if (soundSetReadLimit(gSpeechSound, 0x40000)) { + if (gGameSoundDebugEnabled) { + debugPrint("unable to set read limit "); + } + } + } + + if (soundLoad(gSpeechSound, path)) { + if (gGameSoundDebugEnabled) { + debugPrint("failed on call to soundLoad.\n"); + } + soundDelete(gSpeechSound); + gSpeechSound = NULL; + return -1; + } + + if (a2 != 11) { + if (soundSetReadLimit(gSpeechSound, 0x40000)) { + if (gGameSoundDebugEnabled) { + debugPrint("unable to set read limit "); + } + } + } + + if (a2 == 10) { + return 0; + } + + if (speechPlay()) { + if (gGameSoundDebugEnabled) { + debugPrint("failed starting to play.\n"); + } + soundDelete(gSpeechSound); + gSpeechSound = NULL; + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint("succeeded.\n"); + } + + return 0; +} + +// 0x450F8C +int _gsound_speech_play_preloaded() +{ + if (!gGameSoundInitialized) { + return -1; + } + + if (!gSpeechEnabled) { + return -1; + } + + if (gSpeechSound == NULL) { + return -1; + } + + if (soundIsPlaying(gSpeechSound)) { + return -1; + } + + if (soundIsPaused(gSpeechSound)) { + return -1; + } + + if (_soundDone(gSpeechSound)) { + return -1; + } + + if (speechPlay() != 0) { + soundDelete(gSpeechSound); + gSpeechSound = NULL; + + return -1; + } + + return 0; +} + +// 0x451024 +void speechDelete() +{ + if (gGameSoundInitialized && gSpeechEnabled) { + if (gSpeechSound != NULL) { + soundDelete(gSpeechSound); + gSpeechSound = NULL; + } + } +} + +// 0x451054 +void speechPause() +{ + if (gSpeechSound != NULL) { + soundPause(gSpeechSound); + } +} + +// 0x451068 +void speechResume() +{ + if (gSpeechSound != NULL) { + soundResume(gSpeechSound); + } +} + +// 0x45108C +int _gsound_play_sfx_file_volume(const char* a1, int a2) +{ + Sound* v1; + + if (!gGameSoundInitialized) { + return -1; + } + + if (!gSoundEffectsEnabled) { + return -1; + } + + v1 = soundEffectLoadWithVolume(a1, NULL, a2); + if (v1 == NULL) { + return -1; + } + + soundPlay(v1); + + return 0; +} + +// 0x4510DC +Sound* soundEffectLoad(const char* name, Object* object) +{ + if (!gGameSoundInitialized) { + return NULL; + } + + if (!gSoundEffectsEnabled) { + return NULL; + } + + if (gGameSoundDebugEnabled) { + debugPrint("Loading sound file %s%s...", name, ".ACM"); + } + + if (_gsound_active_effect_counter >= SOUND_EFFECTS_MAX_COUNT) { + if (gGameSoundDebugEnabled) { + debugPrint("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 (gGameSoundDebugEnabled) { + debugPrint("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 (gGameSoundDebugEnabled) { + debugPrint("succeeded.\n"); + } + + return sound; + } + + if (object != NULL) { + if ((object->fid & 0xF000000) >> 24 == 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 (gGameSoundDebugEnabled) { + debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); + } + + if (soundLoad(sound, path) == 0) { + if (gGameSoundDebugEnabled) { + debugPrint("succeeded (with alias).\n"); + } + + return sound; + } + + if (v9 == 'F') { + sprintf(path, "%sHMXXXX%s%s", _sound_sfx_path, name + 6, ".ACM"); + + if (gGameSoundDebugEnabled) { + debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); + } + + if (soundLoad(sound, path) == 0) { + if (gGameSoundDebugEnabled) { + debugPrint("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 (gGameSoundDebugEnabled) { + debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); + } + + if (soundLoad(sound, path) == 0) { + if (gGameSoundDebugEnabled) { + debugPrint("succeeded (with alias).\n"); + } + + return sound; + } + } + + --_gsound_active_effect_counter; + + soundDelete(sound); + + if (gGameSoundDebugEnabled) { + debugPrint("failed.\n"); + } + + return NULL; +} + +// 0x45145C +Sound* soundEffectLoadWithVolume(const char* name, Object* object, int volume) +{ + Sound* sound = soundEffectLoad(name, object); + + if (sound != NULL) { + soundSetVolume(sound, (volume * gSoundEffectsVolume) / VOLUME_MAX); + } + + return sound; +} + +// 0x45148C +void soundEffectDelete(Sound* sound) +{ + if (!gGameSoundInitialized) { + return; + } + + if (!gSoundEffectsEnabled) { + return; + } + + if (soundIsPlaying(sound)) { + if (gGameSoundDebugEnabled) { + debugPrint("Trying to manually delete a sound effect after it has started playing.\n"); + } + return; + } + + if (soundDelete(sound) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("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) +{ + if (!gGameSoundInitialized) { + return 0; + } + + if (!gSoundEffectsEnabled) { + return 0; + } + + if (sound == NULL) { + return 0; + } + + soundPlay(sound); + + return 0; +} + +// 0x451510 +int soundEffectPlay(Sound* sound) +{ + if (!gGameSoundInitialized) { + return -1; + } + + if (!gSoundEffectsEnabled) { + 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 = (obj->fid & 0xF000000) >> 24; + if (type == 0 || type == 1 || type == 2) { + v7 = objectGetOwner(obj); + if (!v7) { + v7 = obj; + } + + objectGetRect(v7, &v14); + + windowGetRect(gIsoWindow, &iso_win_rect); + + if (rectIntersection(&v14, &iso_win_rect, &v12) == -1) { + distance = objectGetDistanceBetween(v7, gDude); + perception = critterGetStat(gDude, 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* sfxBuildCharName(Object* a1, int anim, int extra) +{ + char v7[13]; + char v8; + char v9; + + if (artCopyFileName((a1->fid & 0xF000000) >> 24, 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* gameSoundBuildAmbientSoundEffectName(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* gameSoundBuildInterfaceName(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* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target) +{ + int v6; + char weaponSoundCode; + char effectTypeCode; + char materialCode; + Proto* proto; + + weaponSoundCode = weaponGetSoundId(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; + } + + int damageType = weaponGetDamageType(NULL, weapon); + // TODO: Check damageType conditions. + if (effectTypeCode != 'H' || target == NULL || damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) { + materialCode = 'X'; + } else { + const int type = (target->fid & 0xF000000) >> 24; + int material; + switch (type) { + case OBJ_TYPE_ITEM: + protoGetProto(target->pid, &proto); + material = proto->item.material; + break; + case OBJ_TYPE_SCENERY: + protoGetProto(target->pid, &proto); + material = proto->scenery.field_2C; + break; + case OBJ_TYPE_WALL: + protoGetProto(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* sfxBuildSceneryName(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* sfxBuildOpenName(Object* object, int action) +{ + if ((object->fid & 0xF000000) >> 24 == OBJ_TYPE_SCENERY) { + char scenerySoundId; + Proto* proto; + if (protoGetProto(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; + protoGetProto(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) +{ + soundPlayFile("ib1p1xx1"); +} + +// 0x451978 +void _gsound_red_butt_release(int btn, int keyCode) +{ + soundPlayFile("ib1lu1x1"); +} + +// 0x451980 +void _gsound_toggle_butt_press_(int btn, int keyCode) +{ + soundPlayFile("toggle"); +} + +// 0x451988 +void _gsound_med_butt_press(int btn, int keyCode) +{ + soundPlayFile("ib2p1xx1"); +} + +// 0x451990 +void _gsound_med_butt_release(int btn, int keyCode) +{ + soundPlayFile("ib2lu1x1"); +} + +// 0x451998 +void _gsound_lrg_butt_press(int btn, int keyCode) +{ + soundPlayFile("ib3p1xx1"); +} + +// 0x4519A0 +void _gsound_lrg_butt_release(int btn, int keyCode) +{ + soundPlayFile("ib3lu1x1"); +} + +// 0x4519A8 +int soundPlayFile(const char* name) +{ + if (!gGameSoundInitialized) { + return -1; + } + + if (!gSoundEffectsEnabled) { + return -1; + } + + Sound* sound = soundEffectLoad(name, NULL); + if (sound == NULL) { + return -1; + } + + soundPlay(sound); + + return 0; +} + +// 0x451A00 +void _gsound_bkg_proc() +{ + soundContinueAll(); +} + +// 0x451A08 +int gameSoundFileOpen(const char* fname, int flags, ...) +{ + if ((flags & 2) != 0) { + return -1; + } + + File* stream = fileOpen(fname, "rb"); + if (stream == NULL) { + return -1; + } + + return (int)stream; +} + +// NOTE: Collapsed. +// +// 0x451A1C +long _gsound_write_() +{ + return -1; +} + +// NOTE: Uncollapsed 0x451A1C. +// +// The purpose of this function is unknown. It simply returns -1 without +// actually telling position. This function is used for all game sounds - +// background music, speech, and sound effects. There is another function +// [gameSoundFileTell] which actually provides position. +long gameSoundFileTellNotImplemented(int fileHandle) +{ + return _gsound_write_(); +} + +// NOTE: Uncollapsed 0x451A1C. +int gameSoundFileWrite(int fileHandle, const void* buf, unsigned int size) +{ + return _gsound_write_(); +} + +// 0x451A24 +int gameSoundFileClose(int fileHandle) +{ + if (fileHandle == -1) { + return -1; + } + + return fileClose((File*)fileHandle); +} + +// 0x451A30 +int gameSoundFileRead(int fileHandle, void* buffer, unsigned int size) +{ + if (fileHandle == -1) { + return -1; + } + + return fileRead(buffer, 1, size, (File*)fileHandle); +} + +// 0x451A4C +long gameSoundFileSeek(int fileHandle, long offset, int origin) +{ + if (fileHandle == -1) { + return -1; + } + + if (fileSeek((File*)fileHandle, offset, origin) != 0) { + return -1; + } + + return fileTell((File*)fileHandle); +} + +// 0x451A70 +long gameSoundFileTell(int handle) +{ + if (handle == -1) { + return -1; + } + + return fileTell((File*)handle); +} + +// 0x451A7C +long gameSoundFileGetSize(int handle) +{ + if (handle == -1) { + return -1; + } + + return fileGetSize((File*)handle); +} + +// 0x451A88 +bool gameSoundIsCompressed(char* filePath) +{ + return true; +} + +// 0x451A90 +void speechCallback(void* userData, int a2) +{ + if (a2 == 1) { + gSpeechSound = NULL; + + if (gSpeechEndCallback) { + gSpeechEndCallback(); + } + } +} + +// 0x451AB0 +void backgroundSoundCallback(void* userData, int a2) +{ + if (a2 == 1) { + gBackgroundSound = NULL; + + if (gBackgroundSoundEndCallback) { + gBackgroundSoundEndCallback(); + } + } +} + +// 0x451AD0 +void soundEffectCallback(void* userData, int a2) +{ + if (a2 == 1) { + --_gsound_active_effect_counter; + } +} + +// 0x451ADC +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 +int gameSoundFindBackgroundSoundPathWithCopy(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 (gGameSoundDebugEnabled) { + debugPrint("Full background path too long.\n"); + } + + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint(" 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 (gGameSoundDebugEnabled) { + debugPrint("by copy "); + } + + gameSoundDeleteOldMusicFile(); + + char inPath[MAX_PATH]; + sprintf(inPath, "%s%s%s", _sound_music_path2, src, ".ACM"); + + FILE* inStream = fopen(inPath, "rb"); + if (inStream == NULL) { + if (gGameSoundDebugEnabled) { + debugPrint("Unable to find music file %s to copy down.\n", src); + } + + return -1; + } + + FILE* outStream = fopen(outPath, "wb"); + if (outStream == NULL) { + if (gGameSoundDebugEnabled) { + debugPrint("Unable to open music file %s for copying to.", src); + } + + fclose(inStream); + + return -1; + } + + void* buffer = internal_malloc(0x2000); + if (buffer == NULL) { + if (gGameSoundDebugEnabled) { + debugPrint("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; + } + } + + internal_free(buffer); + fclose(outStream); + fclose(inStream); + + if (err) { + if (gGameSoundDebugEnabled) { + debugPrint("Background sound file copy failed on write -- "); + debugPrint("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 +int gameSoundFindBackgroundSoundPath(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 (gGameSoundDebugEnabled) { + debugPrint("Full background path too long.\n"); + } + + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint(" 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 (gGameSoundDebugEnabled) { + debugPrint("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 (gGameSoundDebugEnabled) { + debugPrint("-- find failed "); + } + + return -1; +} + +// 0x451F94 +int gameSoundFindSpeechSoundPath(char* dest, const char* src) +{ + char path[MAX_PATH]; + + if (strlen(_sound_speech_path) + strlen(".acm") > MAX_PATH) { + if (gGameSoundDebugEnabled) { + // FIXME: The message is wrong (notes background path, but here + // we're dealing with speech path). + debugPrint("Full background path too long.\n"); + } + + return -1; + } + + if (gGameSoundDebugEnabled) { + debugPrint(" finding speech sound "); + } + + sprintf(path, "%s%s%s", _sound_speech_path, src, ".ACM"); + + // Check for existence by getting file size. + int fileSize; + if (dbGetFileSize(path, &fileSize) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("-- find failed "); + } + + return -1; + } + + strncpy(dest, path, MAX_PATH); + dest[MAX_PATH] = '\0'; + + return 0; +} + +// delete old music file +// 0x452088 +void gameSoundDeleteOldMusicFile() +{ + 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 (gGameSoundDebugEnabled) { + debugPrint("Deleting old music file failed.\n"); + } + } + + _background_fname_copied[0] = '\0'; + } +} + +// 0x4520EC +int backgroundSoundPlay() +{ + int result; + + if (gGameSoundDebugEnabled) { + debugPrint(" playing "); + } + + if (_gsound_background_fade) { + soundSetVolume(gBackgroundSound, 1); + result = _soundFade(gBackgroundSound, 2000, (int)(gMusicVolume * 0.94)); + } else { + soundSetVolume(gBackgroundSound, (int)(gMusicVolume * 0.94)); + result = soundPlay(gBackgroundSound); + } + + if (result != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Unable to play background sound.\n"); + } + + result = -1; + } + + return result; +} + +// 0x45219C +int speechPlay() +{ + if (gGameSoundDebugEnabled) { + debugPrint(" playing "); + } + + soundSetVolume(gSpeechSound, (int)(gSpeechVolume * 0.69)); + + if (soundPlay(gSpeechSound) != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Unable to play speech sound.\n"); + } + + return -1; + } + + return 0; +} + +// 0x452208 +int _gsound_get_music_path(char** out_value, const char* key) +{ + int v3; + char* v4; + char* value; + + configGetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, out_value); + + value = *out_value; + v3 = strlen(value) + 1; + + if (*(value + v3 - 2) == '\\') { + return 0; + } + + v4 = internal_malloc(v3 - 1 + 2); + if (v4 == NULL) { + if (gGameSoundDebugEnabled) { + debugPrint("Out of memory in gsound_get_music_path.\n"); + } + return -1; + } + + strcpy(v4, value); + *(v4 + v3) = '\\'; + *(v4 + v3 + 1) = '\0'; + + if (configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, v4) != 1) { + if (gGameSoundDebugEnabled) { + debugPrint("config_set_string failed in gsound_music_path.\n"); + } + + return -1; + } + + if (configGetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, out_value)) { + internal_free(v4); + return 0; + } + + if (gGameSoundDebugEnabled) { + debugPrint("config_get_string failed in gsound_music_path.\n"); + } + + return -1; +} + +// 0x452378 +Sound* _gsound_get_sound_ready_for_effect() +{ + int rc; + + Sound* sound = soundAllocate(5, 10); + if (sound == NULL) { + if (gGameSoundDebugEnabled) { + debugPrint(" Can't allocate sound for effect. "); + } + + if (gGameSoundDebugEnabled) { + debugPrint("soundAllocate returned: %d, %s\n", 0, soundGetErrorDescription(0)); + } + + return NULL; + } + + if (soundEffectsCacheInitialized()) { + rc = soundSetFileIO(sound, soundEffectsCacheFileOpen, soundEffectsCacheFileClose, soundEffectsCacheFileRead, soundEffectsCacheFileWrite, soundEffectsCacheFileSeek, soundEffectsCacheFileTell, soundEffectsCacheFileLength); + } else { + rc = soundSetFileIO(sound, audioOpen, audioClose, audioRead, NULL, audioSeek, gameSoundFileTellNotImplemented, audioGetSize); + } + + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("Can't set file IO on sound effect.\n"); + } + + if (gGameSoundDebugEnabled) { + debugPrint("soundSetFileIO returned: %d, %s\n", rc, soundGetErrorDescription(rc)); + } + + soundDelete(sound); + + return NULL; + } + + rc = soundSetCallback(sound, soundEffectCallback, NULL); + if (rc != 0) { + if (gGameSoundDebugEnabled) { + debugPrint("failed because the callback could not be set.\n"); + } + + if (gGameSoundDebugEnabled) { + debugPrint("soundSetCallback returned: %d, %s\n", rc, soundGetErrorDescription(rc)); + } + + soundDelete(sound); + + return NULL; + } + + soundSetVolume(sound, gSoundEffectsVolume); + + return sound; +} + +// Check file for existence. +// +// 0x4524E0 +bool _gsound_file_exists_f(const char* fname) +{ + FILE* f = fopen(fname, "rb"); + if (f == NULL) { + return false; + } + + fclose(f); + + return true; +} + +// gsound_setup_paths +// 0x452518 +int _gsound_setup_paths() +{ + // TODO: Incomplete. + + return 0; +} + +// 0x452628 +int _gsound_sfx_q_start() +{ + return ambientSoundEffectEventProcess(0, NULL); +} + +// 0x452634 +int ambientSoundEffectEventProcess(Object* a1, void* data) +{ + _queue_clear_type(EVENT_TYPE_GSOUND_SFX_EVENT, NULL); + + AmbientSoundEffectEvent* soundEffectEvent = data; + int ambientSoundEffectIndex = -1; + if (soundEffectEvent != NULL) { + ambientSoundEffectIndex = soundEffectEvent->ambientSoundEffectIndex; + } else { + if (ambientSoundEffectGetLength() > 0) { + ambientSoundEffectIndex = ambientSoundEffectGetRandom(); + } + } + + AmbientSoundEffectEvent* nextSoundEffectEvent = internal_malloc(sizeof(*nextSoundEffectEvent)); + if (nextSoundEffectEvent == NULL) { + return -1; + } + + if (gMapHeader.name[0] == '\0') { + return 0; + } + + int delay = 10 * randomBetween(15, 20); + if (ambientSoundEffectGetLength() > 0) { + nextSoundEffectEvent->ambientSoundEffectIndex = ambientSoundEffectGetRandom(); + if (queueAddEvent(delay, NULL, nextSoundEffectEvent, EVENT_TYPE_GSOUND_SFX_EVENT) == -1) { + return -1; + } + } + + if (isInCombat()) { + ambientSoundEffectIndex = -1; + } + + if (ambientSoundEffectIndex != -1) { + char* fileName; + if (ambientSoundEffectGetName(ambientSoundEffectIndex, &fileName) == 0) { + int v7 = _get_bk_time(); + if (getTicksBetween(v7, _lastTime_1) >= 5000) { + if (soundPlayFile(fileName) == -1) { + debugPrint("\nGsound: playing ambient map sfx: %s. FAILED", fileName); + } else { + debugPrint("\nGsound: playing ambient map sfx: %s", fileName); + } + } + _lastTime_1 = v7; + } + } + + return 0; +} diff --git a/src/game_sound.h b/src/game_sound.h new file mode 100644 index 0000000..87aa6a1 --- /dev/null +++ b/src/game_sound.h @@ -0,0 +1,166 @@ +#ifndef GAME_SOUND_H +#define GAME_SOUND_H + +#include "obj_types.h" +#include "sound.h" + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +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)(); + +extern char _aSoundSfx[]; +extern char _aSoundMusic_0[]; +extern char _aSoundSpeech_0[]; + +extern bool gGameSoundInitialized; +extern bool gGameSoundDebugEnabled; +extern bool gMusicEnabled; +extern int dword_518E3; +extern int _gsound_background_fade; +extern bool gSpeechEnabled; +extern bool gSoundEffectsEnabled; +extern int _gsound_active_effect_counter; +extern Sound* gBackgroundSound; +extern Sound* gSpeechSound; +extern SoundEndCallback* gBackgroundSoundEndCallback; +extern SoundEndCallback* gSpeechEndCallback; +extern char _snd_lookup_weapon_type[WEAPON_SOUND_EFFECT_COUNT]; +extern char _snd_lookup_scenery_action[SCENERY_SOUND_EFFECT_COUNT]; +extern int _background_storage_requested; +extern int _background_loop_requested; +extern char* _sound_sfx_path; +extern char* _sound_music_path1; +extern char* _sound_music_path2; +extern char* _sound_speech_path; +extern int gMasterVolume; +extern int gMusicVolume; +extern int gSpeechVolume; +extern int gSoundEffectsVolume; +extern int _detectDevices; +extern int _lastTime_1; + +extern char _background_fname_copied[MAX_PATH]; +extern char _sfx_file_name[13]; +extern char gBackgroundSoundFileName[270]; + +int gameSoundInit(); +void gameSoundReset(); +int gameSoundExit(); +void soundEffectsEnable(); +void soundEffectsDisable(); +int soundEffectsIsEnabled(); +int gameSoundSetMasterVolume(int value); +int gameSoundGetMasterVolume(); +int soundEffectsSetVolume(int value); +int soundEffectsGetVolume(); +void backgroundSoundDisable(); +void backgroundSoundEnable(); +int backgroundSoundIsEnabled(); +void backgroundSoundSetVolume(int value); +int backgroundSoundGetVolume(); +int _gsound_background_volume_get_set(int a1); +void backgroundSoundSetEndCallback(SoundEndCallback* callback); +int backgroundSoundGetDuration(); +int backgroundSoundLoad(const char* fileName, int a2, int a3, int a4); +int _gsound_background_play_level_music(const char* a1, int a2); +void backgroundSoundDelete(); +void backgroundSoundRestart(int value); +void backgroundSoundPause(); +void backgroundSoundResume(); +void speechDisable(); +void speechEnable(); +int speechIsEnabled(); +void speechSetVolume(int value); +int speechGetVolume(); +int _gsound_speech_volume_get_set(int volume); +void speechSetEndCallback(SoundEndCallback* callback); +int speechGetDuration(); +int speechLoad(const char* fname, int a2, int a3, int a4); +int _gsound_speech_play_preloaded(); +void speechDelete(); +void speechPause(); +void speechResume(); +int _gsound_play_sfx_file_volume(const char* a1, int a2); +Sound* soundEffectLoad(const char* name, Object* a2); +Sound* soundEffectLoadWithVolume(const char* a1, Object* a2, int a3); +void soundEffectDelete(Sound* a1); +int _gsnd_anim_sound(Sound* a1); +int soundEffectPlay(Sound* a1); +int _gsound_compute_relative_volume(Object* obj); +char* sfxBuildCharName(Object* a1, int anim, int extra); +char* gameSoundBuildAmbientSoundEffectName(const char* a1); +char* gameSoundBuildInterfaceName(const char* a1); +char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target); +char* sfxBuildSceneryName(int actionType, int action, const char* name); +char* sfxBuildOpenName(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_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 soundPlayFile(const char* name); +void _gsound_bkg_proc(); +int gameSoundFileOpen(const char* fname, int access, ...); +long _gsound_write_(); +long gameSoundFileTellNotImplemented(int handle); +int gameSoundFileWrite(int handle, const void* buf, unsigned int size); +int gameSoundFileClose(int handle); +int gameSoundFileRead(int handle, void* buf, unsigned int size); +long gameSoundFileSeek(int handle, long offset, int origin); +long gameSoundFileTell(int handle); +long gameSoundFileGetSize(int handle); +bool gameSoundIsCompressed(char* filePath); +void speechCallback(void* userData, int a2); +void backgroundSoundCallback(void* userData, int a2); +void soundEffectCallback(void* userData, int a2); +int _gsound_background_allocate(Sound** out_s, int a2, int a3); +int gameSoundFindBackgroundSoundPathWithCopy(char* dest, const char* src); +int gameSoundFindBackgroundSoundPath(char* dest, const char* src); +int gameSoundFindSpeechSoundPath(char* dest, const char* src); +void gameSoundDeleteOldMusicFile(); +int backgroundSoundPlay(); +int speechPlay(); +int _gsound_get_music_path(char** out_value, const char* key); +Sound* _gsound_get_sound_ready_for_effect(); +bool _gsound_file_exists_f(const char* fname); +int _gsound_setup_paths(); +int _gsound_sfx_q_start(); +int ambientSoundEffectEventProcess(Object* a1, void* a2); + +#endif /* GAME_SOUND_H */ diff --git a/src/game_vars.h b/src/game_vars.h new file mode 100644 index 0000000..5751d5f --- /dev/null +++ b/src/game_vars.h @@ -0,0 +1,703 @@ +#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 */ diff --git a/src/geometry.c b/src/geometry.c new file mode 100644 index 0000000..5ca5e37 --- /dev/null +++ b/src/geometry.c @@ -0,0 +1,183 @@ +#include "geometry.h" + +#include "memory.h" + +#include + +// 0x51DEF4 +RectListNode* _rectList = NULL; + +// 0x4C6900 +void _GNW_rect_exit() +{ + while (_rectList != NULL) { + RectListNode* next = _rectList->next; + internal_free(_rectList); + _rectList = next; + } +} + +// 0x4C6924 +void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect) +{ + Rect v1; + rectCopy(&v1, rect); + + // NOTE: Original code is slightly different. + while (*rectListNodePtr != NULL) { + RectListNode* rectListNode = *rectListNodePtr; + if (v1.right >= rectListNode->rect.left + && v1.bottom >= rectListNode->rect.top + && v1.left <= rectListNode->rect.right + && v1.top <= rectListNode->rect.bottom) { + Rect v2; + rectCopy(&v2, &(rectListNode->rect)); + + *rectListNodePtr = rectListNode->next; + + rectListNode->next = _rectList; + _rectList = rectListNode; + + if (v2.top < v1.top) { + RectListNode* newRectListNode = _rect_malloc(); + if (newRectListNode == NULL) { + return; + } + + rectCopy(&(newRectListNode->rect), &v2); + newRectListNode->rect.bottom = v1.top - 1; + newRectListNode->next = *rectListNodePtr; + + *rectListNodePtr = newRectListNode; + rectListNodePtr = &(newRectListNode->next); + + v2.top = v1.top; + } + + if (v2.bottom > v1.bottom) { + RectListNode* newRectListNode = _rect_malloc(); + if (newRectListNode == NULL) { + return; + } + + rectCopy(&(newRectListNode->rect), &v2); + newRectListNode->rect.top = v1.bottom + 1; + newRectListNode->next = *rectListNodePtr; + + *rectListNodePtr = newRectListNode; + rectListNodePtr = &(newRectListNode->next); + + v2.bottom = v1.bottom; + } + + if (v2.left < v1.left) { + RectListNode* newRectListNode = _rect_malloc(); + if (newRectListNode == NULL) { + return; + } + + rectCopy(&(newRectListNode->rect), &v2); + newRectListNode->rect.right = v1.left - 1; + newRectListNode->next = *rectListNodePtr; + + *rectListNodePtr = newRectListNode; + rectListNodePtr = &(newRectListNode->next); + } + + if (v2.right > v1.right) { + RectListNode* newRectListNode = _rect_malloc(); + if (newRectListNode == NULL) { + return; + } + + rectCopy(&(newRectListNode->rect), &v2); + newRectListNode->rect.left = v1.right + 1; + newRectListNode->next = *rectListNodePtr; + + *rectListNodePtr = newRectListNode; + rectListNodePtr = &(newRectListNode->next); + } + } else { + rectListNodePtr = &(rectListNode->next); + } + } +} + +// 0x4C6BB8 +RectListNode* _rect_malloc() +{ + if (_rectList == NULL) { + for (int index = 0; index < 10; index++) { + RectListNode* rectListNode = internal_malloc(sizeof(*rectListNode)); + if (rectListNode == NULL) { + break; + } + + // NOTE: Uninline. + _rect_free(rectListNode); + } + } + + if (_rectList == NULL) { + return NULL; + } + + RectListNode* rectListNode = _rectList; + _rectList = _rectList->next; + + return rectListNode; +} + +// 0x4C6C04 +void _rect_free(RectListNode* rectListNode) +{ + rectListNode->next = _rectList; + _rectList = rectListNode; +} + +// Calculates a union of two source rectangles and places it into result +// rectangle. +// +// 0x4C6C18 +void rectUnion(const Rect* s1, const Rect* s2, Rect* r) +{ + r->left = min(s1->left, s2->left); + r->top = min(s1->top, s2->top); + r->right = max(s1->right, s2->right); + r->bottom = max(s1->bottom, s2->bottom); +} + +// 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 s1. +// +// 0x4C6C68 +int rectIntersection(const Rect* s1, const Rect* s2, Rect* r) +{ + r->left = s1->left; + r->top = s1->top; + r->right = s1->right; + r->bottom = s1->bottom; + + if (s1->left <= s2->right && s2->left <= s1->right && s2->bottom >= s1->top && s2->top <= s1->bottom) { + if (s2->left > s1->left) { + r->left = s2->left; + } + + if (s2->right < s1->right) { + r->right = s2->right; + } + + if (s2->top > s1->top) { + r->top = s2->top; + } + + if (s2->bottom < s1->bottom) { + r->bottom = s2->bottom; + } + + return 0; + } + + return -1; +} diff --git a/src/geometry.h b/src/geometry.h new file mode 100644 index 0000000..65c92e9 --- /dev/null +++ b/src/geometry.h @@ -0,0 +1,61 @@ +#ifndef GEOMETRY_H +#define GEOMETRY_H + +typedef struct Point { + int x; + int y; +} Point; + +typedef struct Size { + int width; + int height; +} Size; + +typedef struct Rect { + int left; + int top; + int right; + int bottom; +} Rect; + +typedef struct RectListNode { + Rect rect; + struct RectListNode* next; +} RectListNode; + +extern RectListNode* _rectList; + +void _GNW_rect_exit(); +void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect); +RectListNode* _rect_malloc(); +void _rect_free(RectListNode* entry); +void rectUnion(const Rect* s1, const Rect* s2, Rect* r); +int rectIntersection(const Rect* a1, const Rect* a2, Rect* a3); + +static inline void rectCopy(Rect* dest, const Rect* src) +{ + dest->left = src->left; + dest->top = src->top; + dest->right = src->right; + dest->bottom = src->bottom; +} + +static inline int rectGetWidth(const Rect* rect) +{ + return rect->right - rect->left + 1; +} + +static inline int rectGetHeight(const Rect* rect) +{ + return rect->bottom - rect->top + 1; +} + +static inline void rectOffset(Rect* rect, int dx, int dy) +{ + rect->left += dx; + rect->top += dy; + rect->right += dx; + rect->bottom += dy; +} + +#endif /* GEOMETRY_H */ diff --git a/src/graph_lib.c b/src/graph_lib.c new file mode 100644 index 0000000..da4f89f --- /dev/null +++ b/src/graph_lib.c @@ -0,0 +1,380 @@ +#include "graph_lib.h" + +#include "debug.h" +#include "memory.h" + +#include + +// 0x596E90 +int* _dad_2; + +// 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; + +// 0x44F250 +int graphCompress(unsigned char* a1, unsigned char* a2, int a3) +{ + _dad_2 = NULL; + _rson = NULL; + _lson = NULL; + _text_buf = NULL; + + // NOTE: Original code is slightly different, it uses deep nesting or a + // bunch of gotos. + _lson = internal_malloc(sizeof(*_lson) * 4104); + _rson = internal_malloc(sizeof(*_rson) * 4376); + _dad_2 = internal_malloc(sizeof(*_dad_2) * 4104); + _text_buf = internal_malloc(sizeof(*_text_buf) * 4122); + + if (_lson == NULL || _rson == NULL || _dad_2 == NULL || _text_buf == NULL) { + debugPrint("\nGRAPHLIB: Error allocating compression buffers!\n"); + + if (_dad_2 != NULL) { + internal_free(_dad_2); + } + + if (_rson != NULL) { + internal_free(_rson); + } + if (_lson != NULL) { + internal_free(_lson); + } + if (_text_buf != NULL) { + internal_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; + } + + internal_free(_lson); + internal_free(_rson); + internal_free(_dad_2); + internal_free(_text_buf); + + if (rc == -1) { + v4 = -1; + } + + return v4; +} + +// 0x44F5F0 +void _InitTree() +{ + for (int index = 4097; index < 4353; index++) { + _rson[index] = 4096; + } + + for (int index = 0; index < 4096; index++) { + _dad_2[index] = 4096; + } +} + +// 0x44F63C +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_2[a1] = v21; + return; + } + v21 = _lson[v6]; + } else { + if (_rson[v6] == 4096) { + _rson[v6] = a1; + _dad_2[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_2[a1] = _dad_2[v21]; + _lson[a1] = _lson[v21]; + _rson[a1] = _rson[v21]; + + _dad_2[_lson[v21]] = a1; + _dad_2[_rson[v21]] = a1; + + if (_rson[_dad_2[v21]] == v21) { + _rson[_dad_2[v21]] = a1; + } else { + _lson[_dad_2[v21]] = a1; + } + + _dad_2[v21] = 4096; +} + +// 0x44F7EC +void _DeleteNode(int a1) +{ + if (_dad_2[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_2[v5]] = _lson[v5]; + _dad_2[_lson[v5]] = _dad_2[v5]; + _lson[v5] = _lson[a1]; + _dad_2[_lson[a1]] = v5; + } + + _rson[v5] = _rson[a1]; + _dad_2[_rson[a1]] = v5; + } + } + + _dad_2[v5] = _dad_2[a1]; + + if (_rson[_dad_2[a1]] == a1) { + _rson[_dad_2[a1]] = v5; + } else { + _lson[_dad_2[a1]] = v5; + } + _dad_2[a1] = 4096; + } +} + +// 0x44F92C +int graphDecompress(unsigned char* src, unsigned char* dest, int length) +{ + _text_buf = internal_malloc(sizeof(*_text_buf) * 4122); + if (_text_buf == NULL) { + debugPrint("\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++; + } + } + + internal_free(_text_buf); + + return 0; +} diff --git a/src/graph_lib.h b/src/graph_lib.h new file mode 100644 index 0000000..93cde82 --- /dev/null +++ b/src/graph_lib.h @@ -0,0 +1,19 @@ +#ifndef GRAPH_LIB_H +#define GRAPH_LIB_H + +extern int* _dad_2; +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 graphCompress(unsigned char* a1, unsigned char* a2, int a3); +void _InitTree(); +void _InsertNode(int a1); +void _DeleteNode(int a1); +int graphDecompress(unsigned char* a1, unsigned char* a2, int a3); + +#endif /* GRAPH_LIB_H */ diff --git a/src/grayscale.c b/src/grayscale.c new file mode 100644 index 0000000..090eab1 --- /dev/null +++ b/src/grayscale.c @@ -0,0 +1,40 @@ +#include "grayscale.h" + +#include "color.h" + +// 0x596D90 +unsigned char _GreyTable[256]; + +// 0x44FA78 +void grayscalePaletteUpdate(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 grayscalePaletteApply(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; + } +} diff --git a/src/grayscale.h b/src/grayscale.h new file mode 100644 index 0000000..dd353c0 --- /dev/null +++ b/src/grayscale.h @@ -0,0 +1,9 @@ +#ifndef GRAYSCALE_H +#define GRAYSCALE_H + +extern unsigned char _GreyTable[256]; + +void grayscalePaletteUpdate(int a1, int a2); +void grayscalePaletteApply(unsigned char* surface, int width, int height, int pitch); + +#endif /* GRAYSCALE_H */ diff --git a/src/heap.c b/src/heap.c new file mode 100644 index 0000000..72f76a8 --- /dev/null +++ b/src/heap.c @@ -0,0 +1,1167 @@ +#include "heap.h" + +#include "debug.h" +#include "memory.h" + +#include +#include +#include + +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"); + +// An array of pointers to free heap blocks. +// +// 0x518E9C +unsigned char** gHeapFreeBlocks = NULL; + +// An array of moveable extents in heap. +// +// 0x518EA0 +HeapMoveableExtent* gHeapMoveableExtents = NULL; + +// An array of pointers to moveable heap blocks. +// +// 0x518EA4 +unsigned char** gHeapMoveableBlocks = NULL; + +// An array of indexes into [gHeapFreeBlocks] array to track which free blocks +// were already reserved for subsequent moving. +// +// 0x518EA8 +int* gHeapReservedFreeBlockIndexes = NULL; + +// The length of the [gHeapFreeBlocks] array. +// +// 0x518EAC +int gHeapFreeBlocksLength = 0; + +// The length of [gHeapMoveableExtents] array. +// +// 0x518EB0 +int gHeapMoveableExtentsLength = 0; + +// The length of [gHeapMoveableBlocks] array. +// +// 0x518EB4 +int gHeapMoveableBlocksLength = 0; + +// The length of [gHeapReservedFreeBlockIndexes] array. +// +// 0x518EB8 +int gHeapReservedFreeBlockIndexesLength = 0; + +// The number of heaps. +// +// This value is used to init/free internal temporary buffers +// needed for any heap. +// +// 0x518EBC +int gHeapsCount = 0; + +// 0x453304 +bool heapInternalsInit() +{ + // 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 { + gHeapFreeBlocks = internal_malloc(sizeof(*gHeapFreeBlocks) * HEAP_FREE_BLOCKS_INITIAL_LENGTH); + if (gHeapFreeBlocks == NULL) { + break; + } + + gHeapFreeBlocksLength = HEAP_FREE_BLOCKS_INITIAL_LENGTH; + + gHeapMoveableExtents = internal_malloc(sizeof(*gHeapMoveableExtents) * HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH); + if (gHeapMoveableExtents == NULL) { + break; + } + gHeapMoveableExtentsLength = HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH; + + gHeapMoveableBlocks = internal_malloc(sizeof(*gHeapMoveableBlocks) * HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH); + if (gHeapMoveableBlocks == NULL) { + break; + } + gHeapMoveableBlocksLength = HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH; + + gHeapReservedFreeBlockIndexes = internal_malloc(sizeof(*gHeapReservedFreeBlockIndexes) * HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH); + if (gHeapReservedFreeBlockIndexes == NULL) { + break; + } + gHeapReservedFreeBlockIndexesLength = HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH; + + return true; + } while (0); + + // NOTE: Original code frees them one by one without calling this function. + heapInternalsFree(); + + return false; +} + +// 0x4533A0 +void heapInternalsFree() +{ + if (gHeapReservedFreeBlockIndexes != NULL) { + internal_free(gHeapReservedFreeBlockIndexes); + gHeapReservedFreeBlockIndexes = NULL; + } + gHeapReservedFreeBlockIndexesLength = 0; + + if (gHeapMoveableBlocks != NULL) { + internal_free(gHeapMoveableBlocks); + gHeapMoveableBlocks = NULL; + } + gHeapMoveableBlocksLength = 0; + + if (gHeapMoveableExtents != NULL) { + internal_free(gHeapMoveableExtents); + gHeapMoveableExtents = NULL; + } + gHeapMoveableExtentsLength = 0; + + if (gHeapFreeBlocks != NULL) { + internal_free(gHeapFreeBlocks); + gHeapFreeBlocks = NULL; + } + gHeapFreeBlocksLength = 0; +} + +// 0x452974 +bool heapInit(Heap* heap, int a2) +{ + if (heap == NULL) { + return false; + } + + if (gHeapsCount == 0) { + if (!heapInternalsInit()) { + return false; + } + } + + memset(heap, 0, sizeof(*heap)); + + if (heapHandleListInit(heap)) { + int size = (a2 >> 10) + a2; + heap->data = internal_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; + + gHeapsCount++; + + return true; + } + } + + if (gHeapsCount == 0) { + heapInternalsFree(); + } + + return false; +} + +// 0x452A3C +bool heapFree(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) { + internal_free(handle->data); + } + } + + if (heap->handles != NULL) { + internal_free(heap->handles); + heap->handles = NULL; + heap->handlesLength = 0; + } + + if (heap->data != NULL) { + internal_free(heap->data); + } + + memset(heap, 0, sizeof(*heap)); + + gHeapsCount--; + if (gHeapsCount == 0) { + heapInternalsFree(); + } + + return true; +} + +// 0x453430 +bool heapHandleListInit(Heap* heap) +{ + heap->handles = internal_malloc(sizeof(*heap->handles) * HEAP_HANDLES_INITIAL_LENGTH); + if (heap->handles == NULL) { + debugPrint("Heap Error : Could not initialize handles.\n"); + return false; + } + + for (int index = 0; index < HEAP_HANDLES_INITIAL_LENGTH; index++) { + HeapHandle* handle = &(heap->handles[index]); + handle->state = HEAP_HANDLE_STATE_INVALID; + handle->data = NULL; + } + + heap->handlesLength = HEAP_HANDLES_INITIAL_LENGTH; + + return true; +} + +// 0x452AD0 +bool heapBlockAllocate(Heap* heap, int* handleIndexPtr, int size, int a4) +{ + if (heap == NULL || handleIndexPtr == NULL || size == 0) { + goto err; + } + + if (a4 != 0 && a4 != 1) { + a4 = 0; + } + + unsigned char* block; + if (!heapFindFreeBlock(heap, size, &block, a4)) { + goto err; + } + + HeapBlockHeader* blockHeader = (HeapBlockHeader*)block; + int state = blockHeader->state; + + int handleIndex; + if (!heapFindFreeHandle(heap, &handleIndex)) { + goto err_no_handle; + } + + int blockSize = blockHeader->size; + HeapHandle* 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 = 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*)(block + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); + blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; + + // Obtain beginning of the next block. + unsigned char* nextBlock = 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 = block; + + // Update heap stats + heap->freeBlocks--; + heap->moveableBlocks++; + heap->freeSize -= blockSize; + heap->moveableSize += blockSize; + + *handleIndexPtr = handleIndex; + + return true; + } + + handle->state = HEAP_HANDLE_STATE_INVALID; + handle->data = NULL; + + debugPrint("Heap Error: Unknown block state during allocation.\n"); + +err_no_handle: + + debugPrint("Heap Error: Could not acquire handle for new block.\n"); + if (state == HEAP_BLOCK_STATE_SYSTEM) { + internal_free(block); + } + +err: + + debugPrint("Heap Warning: Could not allocate block of %d bytes.\n", size); + return false; +} + +// heap_block_free +// 0x452CB4 +bool heapBlockDeallocate(Heap* heap, int* handleIndexPtr) +{ + if (heap == NULL || handleIndexPtr == NULL) { + debugPrint("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) { + debugPrint("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) { + debugPrint("Heap Error: Bad guard end detected during deallocate.\n"); + } + + if (handle->state != blockHeader->state) { + debugPrint("Heap Error: Mismatched block states detected during deallocate.\n"); + } + + if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) { + debugPrint("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; + + // Reset handle + handle->state = HEAP_HANDLE_STATE_INVALID; + handle->data = NULL; + + return true; + } + + if (handle->state == HEAP_BLOCK_STATE_SYSTEM) { + // Release system memory + internal_free(handle->data); + + // Update heap stats + heap->systemBlocks--; + heap->systemSize -= size; + + // Reset handle + handle->state = HEAP_HANDLE_STATE_INVALID; + handle->data = NULL; + + return true; + } + + debugPrint("Heap Error: Unknown block state during deallocation.\n"); + return false; +} + +// 0x452DE0 +bool heapLock(Heap* heap, int handleIndex, unsigned char** bufferPtr) +{ + if (heap == NULL) { + debugPrint("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) { + debugPrint("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) { + debugPrint("Heap Error: Bad guard end detected during lock.\n"); + return false; + } + + if (handle->state != blockHeader->state) { + debugPrint("Heap Error: Mismatched block states detected during lock.\n"); + return false; + } + + if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) { + debugPrint("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; + } + + debugPrint("Heap Error: Unknown block state during lock.\n"); + return false; +} + +// 0x452EE4 +bool heapUnlock(Heap* heap, int handleIndex) +{ + if (heap == NULL) { + debugPrint("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) { + debugPrint("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) { + debugPrint("Heap Error: Bad guard end detected during unlock.\n"); + } + + if (handle->state != blockHeader->state) { + debugPrint("Heap Error: Mismatched block states detected during unlock.\n"); + } + + if ((handle->state & HEAP_BLOCK_STATE_LOCKED) == 0) { + debugPrint("Heap Error: Attempt to unlock a previously unlocked block.\n"); + debugPrint("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; +} + +// 0x4532AC +bool heapPrintStats(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, + gHeapsCount); + + return true; +} + +// 0x4534B0 +bool heapFindFreeHandle(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 = internal_realloc(heap->handles, sizeof(*handles) * (heap->handlesLength + HEAP_HANDLES_INITIAL_LENGTH)); + if (handles == NULL) { + return false; + } + + heap->handles = handles; + + // Loop thru new handles and reset them to default state. + for (int index = heap->handlesLength; index < heap->handlesLength + HEAP_HANDLES_INITIAL_LENGTH; index++) { + HeapHandle* handle = &(heap->handles[index]); + handle->state = HEAP_HANDLE_STATE_INVALID; + handle->data = NULL; + } + + *handleIndexPtr = heap->handlesLength; + + heap->handlesLength += HEAP_HANDLES_INITIAL_LENGTH; + + return true; +} + +// heap_find_free_block +// 0x453588 +bool heapFindFreeBlock(Heap* heap, int size, void** blockPtr, int a4) +{ + if (!heapBuildFreeBlocksList(heap)) { + goto system; + } + + if (size > heap->freeSize) { + goto system; + } + + if (heap->freeBlocks > 1) { + qsort(gHeapFreeBlocks, heap->freeBlocks, sizeof(*gHeapFreeBlocks), heapBlockCompareBySize); + } + + // Take last free block (the biggest one). + unsigned char* biggestFreeBlock = gHeapFreeBlocks[heap->freeBlocks - 1]; + HeapBlockHeader* biggestFreeBlockHeader = (HeapBlockHeader*)biggestFreeBlock; + int 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 = gHeapFreeBlocks[index]; + HeapBlockHeader* blockHeader = (HeapBlockHeader*)block; + if (blockHeader->size >= size) { + break; + } + } + + *blockPtr = gHeapFreeBlocks[index]; + return true; + } + + int moveableExtentsCount; + int maxBlocksCount; + if (!heapBuildMoveableExtentsList(heap, &moveableExtentsCount, &maxBlocksCount)) { + goto system; + } + + // Ensure the length of [gHeapReservedFreeBlockIndexes] array is big enough + // to index all blocks for longest moveable extent. + if (maxBlocksCount > gHeapReservedFreeBlockIndexesLength) { + int* indexes = internal_realloc(gHeapReservedFreeBlockIndexes, sizeof(*gHeapReservedFreeBlockIndexes) * maxBlocksCount); + if (indexes == NULL) { + goto system; + } + + gHeapReservedFreeBlockIndexesLength = maxBlocksCount; + gHeapReservedFreeBlockIndexes = indexes; + } + + qsort(gHeapMoveableExtents, moveableExtentsCount, sizeof(*gHeapMoveableExtents), heapMoveableExtentsCompareBySize); + + 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 = &(gHeapMoveableExtents[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 (!heapBuildMoveableBlocksList(extentIndex)) { + continue; + } + + // Sort moveable blocks by size (smallest -> largest) + qsort(gHeapMoveableBlocks, extent->moveableBlocksLength, sizeof(*gHeapMoveableBlocks), heapBlockCompareBySize); + + 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 = gHeapMoveableBlocks[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 = gHeapFreeBlocks[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 == gHeapReservedFreeBlockIndexes[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. + gHeapReservedFreeBlockIndexes[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; + } + + HeapMoveableExtent* extent = &(gHeapMoveableExtents[extentIndex]); + int reservedFreeBlockIndex = 0; + for (int moveableBlockIndex = 0; moveableBlockIndex < extent->moveableBlocksLength; moveableBlockIndex++) { + unsigned char* moveableBlock = gHeapMoveableBlocks[moveableBlockIndex]; + HeapBlockHeader* moveableBlockHeader = (HeapBlockHeader*)moveableBlock; + int moveableBlockSize = moveableBlockHeader->size; + if (biggestFreeBlockSize < moveableBlockSize) { + continue; + } + + unsigned char* freeBlock = gHeapFreeBlocks[gHeapReservedFreeBlockIndexes[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. + HeapBlockHeader* 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; + + HeapBlockFooter* 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 (heapPrintStats(heap, stats)) { + debugPrint("\n%s\n", stats); + } + + if (a4 == 0) { + debugPrint("Allocating block from system memory...\n"); + unsigned char* block = internal_malloc(size + HEAP_BLOCK_OVERHEAD_SIZE); + if (block == NULL) { + debugPrint("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; +} + +// Build list of pointers to moveable blocks in given extent. +// +// 0x453E80 +bool heapBuildMoveableBlocksList(int extentIndex) +{ + HeapMoveableExtent* extent = &(gHeapMoveableExtents[extentIndex]); + if (extent->moveableBlocksLength > gHeapMoveableBlocksLength) { + unsigned char** moveableBlocks = internal_realloc(gHeapMoveableBlocks, sizeof(*gHeapMoveableBlocks) * extent->moveableBlocksLength); + if (moveableBlocks == NULL) { + return false; + } + + gHeapMoveableBlocks = moveableBlocks; + gHeapMoveableBlocksLength = 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) { + gHeapMoveableBlocks[moveableBlockIndex++] = ptr; + } + ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; + } + + return moveableBlockIndex == extent->moveableBlocksLength; +} + +// 0x453E74 +int heapMoveableExtentsCompareBySize(const void* a1, const void* a2) +{ + HeapMoveableExtent* v1 = (HeapMoveableExtent*)a1; + HeapMoveableExtent* v2 = (HeapMoveableExtent*)a2; + return v1->size - v2->size; +} + +// 0x453BC4 +bool heapBuildFreeBlocksList(Heap* heap) +{ + if (heap->freeBlocks == 0) { + return false; + } + + if (heap->freeBlocks > gHeapFreeBlocksLength) { + unsigned char** freeBlocks = internal_realloc(gHeapFreeBlocks, sizeof(*freeBlocks) * heap->freeBlocks); + if (freeBlocks == NULL) { + return false; + } + + gHeapFreeBlocks = (unsigned char**)freeBlocks; + gHeapFreeBlocksLength = 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--; + } + + gHeapFreeBlocks[freeBlockIndex++] = ptr; + } + + // Move pointer to the header of the next block. + ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; + + blocksLength--; + } + + return true; +} + +// 0x453CC4 +int heapBlockCompareBySize(const void* a1, const void* a2) +{ + HeapBlockHeader* header1 = *(HeapBlockHeader**)a1; + HeapBlockHeader* header2 = *(HeapBlockHeader**)a2; + return header1->size - header2->size; +} + +// 0x453CD0 +bool heapBuildMoveableExtentsList(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) { + debugPrint("<[couldn't build moveable list]>\n"); + return false; + } + + if (maxExtentsCount > gHeapMoveableExtentsLength) { + HeapMoveableExtent* moveableExtents = internal_realloc(gHeapMoveableExtents, sizeof(*gHeapMoveableExtents) * maxExtentsCount); + if (moveableExtents == NULL) { + return false; + } + + gHeapMoveableExtents = moveableExtents; + gHeapMoveableExtentsLength = 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 = &(gHeapMoveableExtents[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; +} + +// 0x452FC4 +bool heapValidate(Heap* heap) +{ + debugPrint("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) { + debugPrint("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) { + debugPrint("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)) { + debugPrint("Ran off end of heap during validate!\n"); + return false; + } + } + } + + if (freeBlocks != heap->freeBlocks) { + debugPrint("Invalid number of free blocks.\n"); + return false; + } + + if (freeSize != heap->freeSize) { + debugPrint("Invalid size of free blocks.\n"); + return false; + } + + if (moveableBlocks != heap->moveableBlocks) { + debugPrint("Invalid number of moveable blocks.\n"); + return false; + } + + if (moveableSize != heap->moveableSize) { + debugPrint("Invalid size of moveable blocks.\n"); + return false; + } + + if (lockedBlocks != heap->lockedBlocks) { + debugPrint("Invalid number of locked blocks.\n"); + return false; + } + + if (lockedSize != heap->lockedSize) { + debugPrint("Invalid size of locked blocks.\n"); + return false; + } + + debugPrint("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) { + debugPrint("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) { + debugPrint("Bad guard end detected in system block during validate.\n"); + return false; + } + + systemBlocks++; + systemSize += blockHeader->size; + } + } + + if (systemBlocks != heap->systemBlocks) { + debugPrint("Invalid number of system blocks.\n"); + return false; + } + + if (systemSize != heap->systemSize) { + debugPrint("Invalid size of system blocks.\n"); + return false; + } + + return true; +} diff --git a/src/heap.h b/src/heap.h new file mode 100644 index 0000000..740aed5 --- /dev/null +++ b/src/heap.h @@ -0,0 +1,116 @@ +#ifndef HEAP_H +#define HEAP_H + +#include + +#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 [gHeapFreeBlocks] array. +#define HEAP_FREE_BLOCKS_INITIAL_LENGTH (128) + +// The initial length of [gHeapMoveableExtents] array. +#define HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH (64) + +// The initial length of [gHeapMoveableBlocks] array. +#define HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH (64) + +// The initial length of [gHeapReservedFreeBlockIndexes] 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; + HeapBlockState state; + int handle_index; +} HeapBlockHeader; + +typedef struct HeapBlockFooter { + int guard; +} HeapBlockFooter; + +typedef struct HeapHandle { + HeapBlockState 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; + +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; + +extern unsigned char** gHeapFreeBlocks; +extern int gHeapFreeBlocksLength; +extern HeapMoveableExtent* gHeapMoveableExtents; +extern int gHeapMoveableExtentsLength; +extern unsigned char** gHeapMoveableBlocks; +extern int gHeapMoveableBlocksLength; +extern int* gHeapReservedFreeBlockIndexes; +extern int gHeapReservedFreeBlockIndexesLength; +extern int gHeapsCount; + +bool heapInternalsInit(); +void heapInternalsFree(); +bool heapInit(Heap* heap, int a2); +bool heapFree(Heap* heap); +bool heapHandleListInit(Heap* heap); +bool heapBlockAllocate(Heap* heap, int* handleIndexPtr, int size, int a3); +bool heapBlockDeallocate(Heap* heap, int* handleIndexPtr); +bool heapLock(Heap* heap, int handleIndex, unsigned char** bufferPtr); +bool heapUnlock(Heap* heap, int handleIndex); +bool heapPrintStats(Heap* heap, char* dest); +bool heapFindFreeHandle(Heap* heap, int* handleIndexPtr); +bool heapFindFreeBlock(Heap* heap, int size, void** blockPtr, int a4); +int heapBlockCompareBySize(const void* a1, const void* a2); +int heapMoveableExtentsCompareBySize(const void* a1, const void* a2); +bool heapBuildMoveableExtentsList(Heap* heap, int* moveableExtentsLengthPtr, int* maxBlocksLengthPtr); +bool heapBuildFreeBlocksList(Heap* heap); +bool heapBuildMoveableBlocksList(int extentIndex); +bool heapValidate(Heap* heap); + +#endif /* HEAP_H */ diff --git a/src/interface.c b/src/interface.c new file mode 100644 index 0000000..d928351 --- /dev/null +++ b/src/interface.c @@ -0,0 +1,2604 @@ +#include "interface.h" + +#include "animation.h" +#include "color.h" +#include "combat.h" +#include "config.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "debug.h" +#include "display_monitor.h" +#include "draw.h" +#include "endgame.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "item.h" +#include "memory.h" +#include "object.h" +#include "proto.h" +#include "proto_instance.h" +#include "proto_types.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "tile.h" +#include "window_manager.h" + +#include + +#define INDICATOR_BAR_X 0 +#define INDICATOR_BAR_Y 358 + +#define INDICATOR_BOX_WIDTH 130 +#define INDICATOR_BOX_HEIGHT 21 + +// 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 + +// 0x518F08 +bool gInterfaceBarInitialized = false; + +// 0x518F0C +bool gInterfaceBarSwapHandsInProgress = false; + +// 0x518F10 +bool gInterfaceBarEnabled = false; + +// 0x518F14 +bool _intfaceHidden = false; + +// 0x518F18 +int gInventoryButton = -1; + +// 0x518F1C +CacheEntry* gInventoryButtonUpFrmHandle = NULL; + +// 0x518F20 +CacheEntry* gInventoryButtonDownFrmHandle = NULL; + +// 0x518F24 +int gOptionsButton = -1; + +// 0x518F28 +CacheEntry* gOptionsButtonUpFrmHandle = NULL; + +// 0x518F2C +CacheEntry* gOptionsButtonDownFrmHandle = NULL; + +// 0x518F30 +int gSkilldexButton = -1; + +// 0x518F34 +CacheEntry* gSkilldexButtonUpFrmHandle = NULL; + +// 0x518F38 +CacheEntry* gSkilldexButtonDownFrmHandle = NULL; + +// 0x518F3C +CacheEntry* gSkilldexButtonMaskFrmHandle = NULL; + +// 0x518F40 +int gMapButton = -1; + +// 0x518F44 +CacheEntry* gMapButtonUpFrmHandle = NULL; + +// 0x518F48 +CacheEntry* gMapButtonDownFrmHandle = NULL; + +// 0x518F4C +CacheEntry* gMapButtonMaskFrmHandle = NULL; + +// 0x518F50 +int gPipboyButton = -1; + +// 0x518F54 +CacheEntry* gPipboyButtonUpFrmHandle = NULL; + +// 0x518F58 +CacheEntry* gPipboyButtonDownFrmHandle = NULL; + +// 0x518F5C +int gCharacterButton = -1; +// 0x518F60 +CacheEntry* gCharacterButtonUpFrmHandle = NULL; +// 0x518F64 +CacheEntry* gCharacterButtonDownFrmHandle = NULL; + +// 0x518F68 +int gSingleAttackButton = -1; + +// 0x518F6C +CacheEntry* gSingleAttackButtonUpHandle = NULL; + +// 0x518F70 +CacheEntry* gSingleAttackButtonDownHandle = NULL; + +// +CacheEntry* _itemButtonDisabledKey = NULL; + +// 0x518F78 +int gInterfaceCurrentHand = HAND_LEFT; + +// 0x518F7C +const Rect gInterfaceBarMainActionRect = { 267, 26, 455, 93 }; + +// 0x518F8C +int gChangeHandsButton = -1; + +// 0x518F90 +CacheEntry* gChangeHandsButtonUpFrmHandle = NULL; + +// 0x518F94 +CacheEntry* gChangeHandsButtonDownFrmHandle = NULL; + +// 0x518F98 +CacheEntry* gChangeHandsButtonMaskFrmHandle = NULL; + +// 0x518F9C +bool gInterfaceBarEndButtonsIsVisible = false; + +// Combat mode curtains rect. +// +// 0x518FA0 +const Rect gInterfaceBarEndButtonsRect = { 580, 38, 637, 96 }; + +// 0x518FB0 +int gEndTurnButton = -1; + +// 0x518FB4 +CacheEntry* gEndTurnButtonUpFrmHandle = NULL; + +// 0x518FB8 +CacheEntry* gEndTurnButtonDownFrmHandle = NULL; + +// 0x518FBC +int gEndCombatButton = -1; + +// 0x518FC0 +CacheEntry* gEndCombatButtonUpFrmHandle = NULL; + +// 0x518FC4 +CacheEntry* gEndCombatButtonDownFrmHandle = NULL; + +// 0x518FC8 +unsigned char* gGreenLightFrmData = NULL; + +// 0x518FCC +unsigned char* gYellowLightFrmData = NULL; + +// 0x518FD0 +unsigned char* gRedLightFrmData = NULL; + +// 0x518FD4 +const Rect gInterfaceBarActionPointsBarRect = { 316, 14, 406, 19 }; + +// 0x518FE4 +unsigned char* gNumbersFrmData = NULL; + +// 0x518FE8 +IndicatorDescription gIndicatorDescriptions[INDICATOR_COUNT] = { + { 102, true, NULL }, // ADDICT + { 100, false, NULL }, // SNEAK + { 101, false, NULL }, // LEVEL + { 103, true, NULL }, // POISONED + { 104, true, NULL }, // RADIATED +}; + +// 0x519024 +int gInterfaceBarWindow = -1; + +// 0x519028 +int gIndicatorBarWindow = -1; + +// Last hit points rendered in interface. +// +// Used to animate changes. +// +// 0x51902C +int gInterfaceLastRenderedHitPoints = 0; + +// Last color used to render hit points in interface. +// +// Used to animate changes. +// +// 0x519030 +int gInterfaceLastRenderedHitPointsColor = INTERFACE_NUMBERS_COLOR_RED; + +// Last armor class rendered in interface. +// +// Used to animate changes. +// +// 0x519034 +int gInterfaceLastRenderedArmorClass = 0; + +// Each slot contains one of indicators or -1 if slot is empty. +// +// 0x5970E0 +int gIndicatorSlots[INDICATOR_SLOTS_COUNT]; + +// 0x5970F8 +InterfaceItemState gInterfaceItemStates[HAND_COUNT]; + +// 0x597128 +CacheEntry* gYellowLightFrmHandle; + +// 0x59712C +CacheEntry* gRedLightFrmHandle; + +// 0x597130 +CacheEntry* gNumbersFrmHandle; + +// 0x597138 +bool gIndicatorBarIsVisible; + +// 0x59713C +unsigned char* gChangeHandsButtonUpFrmData; + +// 0x597140 +CacheEntry* gGreenLightFrmHandle; + +// 0x597144 +unsigned char* gEndCombatButtonUpFrmData; + +// 0x597148 +unsigned char* gEndCombatButtonDownFrmData; + +// 0x59714C +unsigned char* gChangeHandsButtonDownFrmData; + +// 0x597150 +unsigned char* gEndTurnButtonDownFrmData; + +// 0x597154 +unsigned char _itemButtonDown[188 * 67]; + +// 0x59A288 +unsigned char* gEndTurnButtonUpFrmData; + +// 0x59A28C +unsigned char* gChangeHandsButtonMaskFrmData; + +// 0x59A290 +unsigned char* gCharacterButtonUpFrmData; + +// 0x59A294 +unsigned char* gSingleAttackButtonUpData; + +// 0x59A298 +unsigned char* _itemButtonDisabled; + +// 0x59A29C +unsigned char* gMapButtonDownFrmData; + +// 0x59A2A0 +unsigned char* gPipboyButtonUpFrmData; + +// 0x59A2A4 +unsigned char* gCharacterButtonDownFrmData; + +// 0x59A2A8 +unsigned char* gSingleAttackButtonDownData; + +// 0x59A2AC +unsigned char* gPipboyButtonDownFrmData; + +// 0x59A2B0 +unsigned char* gMapButtonMaskFrmData; + +// 0x59A2B4 +unsigned char _itemButtonUp[188 * 67]; + +// 0x59D3E8 +unsigned char* gMapButtonUpFrmData; + +// 0x59D3EC +unsigned char* gSkilldexButtonMaskFrmData; + +// 0x59D3F0 +unsigned char* gSkilldexButtonDownFrmData; + +// 0x59D3F4 +unsigned char* gInterfaceWindowBuffer; + +// 0x59D3F8 +unsigned char* gInventoryButtonUpFrmData; + +// 0x59D3FC +unsigned char* gOptionsButtonUpFrmData; + +// 0x59D400 +unsigned char* gOptionsButtonDownFrmData; + +// 0x59D404 +unsigned char* gSkilldexButtonUpFrmData; + +// 0x59D408 +unsigned char* gInventoryButtonDownFrmData; + +// 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 +unsigned char gInterfaceActionPointsBarBackground[90 * 5]; + +// intface_init +// 0x45D880 +int interfaceInit() +{ + int fid; + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData; + + if (gInterfaceBarWindow != -1) { + return -1; + } + + gInterfaceBarInitialized = 1; + + gInterfaceBarWindow = windowCreate(0, 379, 640, 100, _colorTable[0], WINDOW_HIDDEN); + if (gInterfaceBarWindow == -1) { + goto err; + } + + gInterfaceWindowBuffer = windowGetBuffer(gInterfaceBarWindow); + if (gInterfaceWindowBuffer == NULL) { + goto err; + } + + fid = buildFid(6, 16, 0, 0, 0); + backgroundFrmData = artLockFrameData(fid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData == NULL) { + goto err; + } + + blitBufferToBuffer(backgroundFrmData, 640, 99, 640, gInterfaceWindowBuffer, 640); + artUnlock(backgroundFrmHandle); + + fid = buildFid(6, 47, 0, 0, 0); + gInventoryButtonUpFrmData = artLockFrameData(fid, 0, 0, &gInventoryButtonUpFrmHandle); + if (gInventoryButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 46, 0, 0, 0); + gInventoryButtonDownFrmData = artLockFrameData(fid, 0, 0, &gInventoryButtonDownFrmHandle); + if (gInventoryButtonDownFrmData == NULL) { + goto err; + } + + gInventoryButton = buttonCreate(gInterfaceBarWindow, 211, 41, 32, 21, -1, -1, -1, KEY_LOWERCASE_I, gInventoryButtonUpFrmData, gInventoryButtonDownFrmData, NULL, 0); + if (gInventoryButton == -1) { + goto err; + } + + buttonSetCallbacks(gInventoryButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 18, 0, 0, 0); + gOptionsButtonUpFrmData = artLockFrameData(fid, 0, 0, &gOptionsButtonUpFrmHandle); + if (gOptionsButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 17, 0, 0, 0); + gOptionsButtonDownFrmData = artLockFrameData(fid, 0, 0, &gOptionsButtonDownFrmHandle); + if (gOptionsButtonDownFrmData == NULL) { + goto err; + } + + gOptionsButton = buttonCreate(gInterfaceBarWindow, 210, 62, 34, 34, -1, -1, -1, KEY_LOWERCASE_O, gOptionsButtonUpFrmData, gOptionsButtonDownFrmData, NULL, 0); + if (gOptionsButton == -1) { + goto err; + } + + buttonSetCallbacks(gOptionsButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 6, 0, 0, 0); + gSkilldexButtonUpFrmData = artLockFrameData(fid, 0, 0, &gSkilldexButtonUpFrmHandle); + if (gSkilldexButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 7, 0, 0, 0); + gSkilldexButtonDownFrmData = artLockFrameData(fid, 0, 0, &gSkilldexButtonDownFrmHandle); + if (gSkilldexButtonDownFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 6, 0, 0, 0); + gSkilldexButtonMaskFrmData = artLockFrameData(fid, 0, 0, &gSkilldexButtonMaskFrmHandle); + if (gSkilldexButtonMaskFrmData == NULL) { + goto err; + } + + gSkilldexButton = buttonCreate(gInterfaceBarWindow, 523, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_S, gSkilldexButtonUpFrmData, gSkilldexButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (gSkilldexButton == -1) { + goto err; + } + + buttonSetMask(gSkilldexButton, gSkilldexButtonMaskFrmData); + buttonSetCallbacks(gSkilldexButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 13, 0, 0, 0); + gMapButtonUpFrmData = artLockFrameData(fid, 0, 0, &gMapButtonUpFrmHandle); + if (gMapButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 10, 0, 0, 0); + gMapButtonDownFrmData = artLockFrameData(fid, 0, 0, &gMapButtonDownFrmHandle); + if (gMapButtonDownFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 13, 0, 0, 0); + gMapButtonMaskFrmData = artLockFrameData(fid, 0, 0, &gMapButtonMaskFrmHandle); + if (gMapButtonMaskFrmData == NULL) { + goto err; + } + + gMapButton = buttonCreate(gInterfaceBarWindow, 526, 40, 41, 19, -1, -1, -1, KEY_TAB, gMapButtonUpFrmData, gMapButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (gMapButton == -1) { + goto err; + } + + buttonSetMask(gMapButton, gMapButtonMaskFrmData); + buttonSetCallbacks(gMapButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 59, 0, 0, 0); + gPipboyButtonUpFrmData = artLockFrameData(fid, 0, 0, &gPipboyButtonUpFrmHandle); + if (gPipboyButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 58, 0, 0, 0); + gPipboyButtonDownFrmData = artLockFrameData(fid, 0, 0, &gPipboyButtonDownFrmHandle); + if (gPipboyButtonDownFrmData == NULL) { + goto err; + } + + gPipboyButton = buttonCreate(gInterfaceBarWindow, 526, 78, 41, 19, -1, -1, -1, KEY_LOWERCASE_P, gPipboyButtonUpFrmData, gPipboyButtonDownFrmData, NULL, 0); + if (gPipboyButton == -1) { + goto err; + } + + buttonSetMask(gPipboyButton, gMapButtonMaskFrmData); + buttonSetCallbacks(gPipboyButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 57, 0, 0, 0); + gCharacterButtonUpFrmData = artLockFrameData(fid, 0, 0, &gCharacterButtonUpFrmHandle); + if (gCharacterButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 56, 0, 0, 0); + gCharacterButtonDownFrmData = artLockFrameData(fid, 0, 0, &gCharacterButtonDownFrmHandle); + if (gCharacterButtonDownFrmData == NULL) { + goto err; + } + + gCharacterButton = buttonCreate(gInterfaceBarWindow, 526, 59, 41, 19, -1, -1, -1, KEY_LOWERCASE_C, gCharacterButtonUpFrmData, gCharacterButtonDownFrmData, NULL, 0); + if (gCharacterButton == -1) { + goto err; + } + + buttonSetMask(gCharacterButton, gMapButtonMaskFrmData); + buttonSetCallbacks(gCharacterButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 32, 0, 0, 0); + gSingleAttackButtonUpData = artLockFrameData(fid, 0, 0, &gSingleAttackButtonUpHandle); + if (gSingleAttackButtonUpData == NULL) { + goto err; + } + + fid = buildFid(6, 31, 0, 0, 0); + gSingleAttackButtonDownData = artLockFrameData(fid, 0, 0, &gSingleAttackButtonDownHandle); + if (gSingleAttackButtonDownData == NULL) { + goto err; + } + + fid = buildFid(6, 73, 0, 0, 0); + _itemButtonDisabled = artLockFrameData(fid, 0, 0, &_itemButtonDisabledKey); + if (_itemButtonDisabled == NULL) { + goto err; + } + + memcpy(_itemButtonUp, gSingleAttackButtonUpData, sizeof(_itemButtonUp)); + memcpy(_itemButtonDown, gSingleAttackButtonDownData, sizeof(_itemButtonDown)); + + gSingleAttackButton = buttonCreate(gInterfaceBarWindow, 267, 26, 188, 67, -1, -1, -1, -20, _itemButtonUp, _itemButtonDown, NULL, BUTTON_FLAG_TRANSPARENT); + if (gSingleAttackButton == -1) { + goto err; + } + + buttonSetRightMouseCallbacks(gSingleAttackButton, -1, KEY_LOWERCASE_N, NULL, NULL); + buttonSetCallbacks(gSingleAttackButton, _gsound_lrg_butt_press, _gsound_lrg_butt_release); + + fid = buildFid(6, 6, 0, 0, 0); + gChangeHandsButtonUpFrmData = artLockFrameData(fid, 0, 0, &gChangeHandsButtonUpFrmHandle); + if (gChangeHandsButtonUpFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 7, 0, 0, 0); + gChangeHandsButtonDownFrmData = artLockFrameData(fid, 0, 0, &gChangeHandsButtonDownFrmHandle); + if (gChangeHandsButtonDownFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 6, 0, 0, 0); + gChangeHandsButtonMaskFrmData = artLockFrameData(fid, 0, 0, &gChangeHandsButtonMaskFrmHandle); + if (gChangeHandsButtonMaskFrmData == NULL) { + goto err; + } + + // Swap hands button + gChangeHandsButton = buttonCreate(gInterfaceBarWindow, 218, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_B, gChangeHandsButtonUpFrmData, gChangeHandsButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + if (gChangeHandsButton == -1) { + goto err; + } + + buttonSetMask(gChangeHandsButton, gChangeHandsButtonMaskFrmData); + buttonSetCallbacks(gChangeHandsButton, _gsound_med_butt_press, _gsound_med_butt_release); + + fid = buildFid(6, 82, 0, 0, 0); + gNumbersFrmData = artLockFrameData(fid, 0, 0, &gNumbersFrmHandle); + if (gNumbersFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 83, 0, 0, 0); + gGreenLightFrmData = artLockFrameData(fid, 0, 0, &gGreenLightFrmHandle); + if (gGreenLightFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 84, 0, 0, 0); + gYellowLightFrmData = artLockFrameData(fid, 0, 0, &gYellowLightFrmHandle); + if (gYellowLightFrmData == NULL) { + goto err; + } + + fid = buildFid(6, 85, 0, 0, 0); + gRedLightFrmData = artLockFrameData(fid, 0, 0, &gRedLightFrmHandle); + if (gRedLightFrmData == NULL) { + goto err; + } + + blitBufferToBuffer(gInterfaceWindowBuffer + 640 * 14 + 316, 90, 5, 640, gInterfaceActionPointsBarBackground, 90); + + if (indicatorBarInit() == -1) { + goto err; + } + + gInterfaceCurrentHand = HAND_LEFT; + + // FIXME: For unknown reason these values initialized with -1. It's never + // checked for -1, so I have no explanation for this. + gInterfaceItemStates[HAND_LEFT].item = (Object*)-1; + gInterfaceItemStates[HAND_RIGHT].item = (Object*)-1; + + displayMonitorInit(); + + gInterfaceBarEnabled = true; + gInterfaceBarInitialized = false; + _intfaceHidden = 1; + + return 0; + +err: + + interfaceFree(); + + return -1; +} + +// 0x45E3D0 +void interfaceReset() +{ + interfaceBarEnable(); + + if (gInterfaceBarWindow != -1 && !_intfaceHidden) { + windowHide(gInterfaceBarWindow); + _intfaceHidden = 1; + } + + indicatorBarRefresh(); + displayMonitorReset(); + + // NOTE: Uninline a seemingly inlined routine. + indicatorBarReset(); + + gInterfaceCurrentHand = 0; +} + +// 0x45E440 +void interfaceFree() +{ + if (gInterfaceBarWindow != -1) { + displayMonitorExit(); + + if (gRedLightFrmData != NULL) { + artUnlock(gRedLightFrmHandle); + gRedLightFrmData = NULL; + } + + if (gYellowLightFrmData != NULL) { + artUnlock(gYellowLightFrmHandle); + gYellowLightFrmData = NULL; + } + + if (gGreenLightFrmData != NULL) { + artUnlock(gGreenLightFrmHandle); + gGreenLightFrmData = NULL; + } + + if (gNumbersFrmData != NULL) { + artUnlock(gNumbersFrmHandle); + gNumbersFrmData = NULL; + } + + if (gChangeHandsButton != -1) { + buttonDestroy(gChangeHandsButton); + gChangeHandsButton = -1; + } + + if (gChangeHandsButtonMaskFrmData != NULL) { + artUnlock(gChangeHandsButtonMaskFrmHandle); + gChangeHandsButtonMaskFrmHandle = NULL; + gChangeHandsButtonMaskFrmData = NULL; + } + + if (gChangeHandsButtonDownFrmData != NULL) { + artUnlock(gChangeHandsButtonDownFrmHandle); + gChangeHandsButtonDownFrmHandle = NULL; + gChangeHandsButtonDownFrmData = NULL; + } + + if (gChangeHandsButtonUpFrmData != NULL) { + artUnlock(gChangeHandsButtonUpFrmHandle); + gChangeHandsButtonUpFrmHandle = NULL; + gChangeHandsButtonUpFrmData = NULL; + } + + if (gSingleAttackButton != -1) { + buttonDestroy(gSingleAttackButton); + gSingleAttackButton = -1; + } + + if (_itemButtonDisabled != NULL) { + artUnlock(_itemButtonDisabledKey); + _itemButtonDisabledKey = NULL; + _itemButtonDisabled = NULL; + } + + if (gSingleAttackButtonDownData != NULL) { + artUnlock(gSingleAttackButtonDownHandle); + gSingleAttackButtonDownHandle = NULL; + gSingleAttackButtonDownData = NULL; + } + + if (gSingleAttackButtonUpData != NULL) { + artUnlock(gSingleAttackButtonUpHandle); + gSingleAttackButtonUpHandle = NULL; + gSingleAttackButtonUpData = NULL; + } + + if (gCharacterButton != -1) { + buttonDestroy(gCharacterButton); + gCharacterButton = -1; + } + + if (gCharacterButtonDownFrmData != NULL) { + artUnlock(gCharacterButtonDownFrmHandle); + gCharacterButtonDownFrmHandle = NULL; + gCharacterButtonDownFrmData = NULL; + } + + if (gCharacterButtonUpFrmData != NULL) { + artUnlock(gCharacterButtonUpFrmHandle); + gCharacterButtonUpFrmHandle = NULL; + gCharacterButtonUpFrmData = NULL; + } + + if (gPipboyButton != -1) { + buttonDestroy(gPipboyButton); + gPipboyButton = -1; + } + + if (gPipboyButtonDownFrmData != NULL) { + artUnlock(gPipboyButtonDownFrmHandle); + gPipboyButtonDownFrmHandle = NULL; + gPipboyButtonDownFrmData = NULL; + } + + if (gPipboyButtonUpFrmData != NULL) { + artUnlock(gPipboyButtonUpFrmHandle); + gPipboyButtonUpFrmHandle = NULL; + gPipboyButtonUpFrmData = NULL; + } + + if (gMapButton != -1) { + buttonDestroy(gMapButton); + gMapButton = -1; + } + + if (gMapButtonMaskFrmData != NULL) { + artUnlock(gMapButtonMaskFrmHandle); + gMapButtonMaskFrmHandle = NULL; + gMapButtonMaskFrmData = NULL; + } + + if (gMapButtonDownFrmData != NULL) { + artUnlock(gMapButtonDownFrmHandle); + gMapButtonDownFrmHandle = NULL; + gMapButtonDownFrmData = NULL; + } + + if (gMapButtonUpFrmData != NULL) { + artUnlock(gMapButtonUpFrmHandle); + gMapButtonUpFrmHandle = NULL; + gMapButtonUpFrmData = NULL; + } + + if (gSkilldexButton != -1) { + buttonDestroy(gSkilldexButton); + gSkilldexButton = -1; + } + + if (gSkilldexButtonMaskFrmData != NULL) { + artUnlock(gSkilldexButtonMaskFrmHandle); + gSkilldexButtonMaskFrmHandle = NULL; + gSkilldexButtonMaskFrmData = NULL; + } + + if (gSkilldexButtonDownFrmData != NULL) { + artUnlock(gSkilldexButtonDownFrmHandle); + gSkilldexButtonDownFrmHandle = NULL; + gSkilldexButtonDownFrmData = NULL; + } + + if (gSkilldexButtonUpFrmData != NULL) { + artUnlock(gSkilldexButtonUpFrmHandle); + gSkilldexButtonUpFrmHandle = NULL; + gSkilldexButtonUpFrmData = NULL; + } + + if (gOptionsButton != -1) { + buttonDestroy(gOptionsButton); + gOptionsButton = -1; + } + + if (gOptionsButtonDownFrmData != NULL) { + artUnlock(gOptionsButtonDownFrmHandle); + gOptionsButtonDownFrmHandle = NULL; + gOptionsButtonDownFrmData = NULL; + } + + if (gOptionsButtonUpFrmData != NULL) { + artUnlock(gOptionsButtonUpFrmHandle); + gOptionsButtonUpFrmHandle = NULL; + gOptionsButtonUpFrmData = NULL; + } + + if (gInventoryButton != -1) { + buttonDestroy(gInventoryButton); + gInventoryButton = -1; + } + + if (gInventoryButtonDownFrmData != NULL) { + artUnlock(gInventoryButtonDownFrmHandle); + gInventoryButtonDownFrmHandle = NULL; + gInventoryButtonDownFrmData = NULL; + } + + if (gInventoryButtonUpFrmData != NULL) { + artUnlock(gInventoryButtonUpFrmHandle); + gInventoryButtonUpFrmHandle = NULL; + gInventoryButtonUpFrmData = NULL; + } + + if (gInterfaceBarWindow != -1) { + windowDestroy(gInterfaceBarWindow); + gInterfaceBarWindow = -1; + } + } + + interfaceBarFree(); +} + +// 0x45E860 +int interfaceLoad(File* stream) +{ + if (gInterfaceBarWindow == -1) { + if (interfaceInit() == -1) { + return -1; + } + } + + int interfaceBarEnabled; + if (fileReadInt32(stream, &interfaceBarEnabled) == -1) return -1; + + int v2; + if (fileReadInt32(stream, &v2) == -1) return -1; + + int interfaceCurrentHand; + if (fileReadInt32(stream, &interfaceCurrentHand) == -1) return -1; + + int interfaceBarEndButtonsIsVisible; + if (fileReadInt32(stream, &interfaceBarEndButtonsIsVisible) == -1) return -1; + + if (!gInterfaceBarEnabled) { + interfaceBarEnable(); + } + + if (v2) { + if (gInterfaceBarWindow != -1 && !_intfaceHidden) { + windowHide(gInterfaceBarWindow); + _intfaceHidden = 1; + } + indicatorBarRefresh(); + } else { + _intface_show(); + } + + interfaceRenderHitPoints(false); + interfaceRenderArmorClass(false); + + gInterfaceCurrentHand = interfaceCurrentHand; + + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + + if (interfaceBarEndButtonsIsVisible != gInterfaceBarEndButtonsIsVisible) { + if (interfaceBarEndButtonsIsVisible) { + interfaceBarEndButtonsShow(false); + } else { + interfaceBarEndButtonsHide(false); + } + } + + if (!interfaceBarEnabled) { + interfaceBarDisable(); + } + + indicatorBarRefresh(); + + windowRefresh(gInterfaceBarWindow); + + return 0; +} + +// 0x45E988 +int interfaceSave(File* stream) +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + if (fileWriteInt32(stream, gInterfaceBarEnabled) == -1) return -1; + if (fileWriteInt32(stream, _intfaceHidden) == -1) return -1; + if (fileWriteInt32(stream, gInterfaceCurrentHand) == -1) return -1; + if (fileWriteInt32(stream, gInterfaceBarEndButtonsIsVisible) == -1) return -1; + + return 0; +} + +// 0x45EA10 +void _intface_show() +{ + if (gInterfaceBarWindow != -1) { + if (_intfaceHidden) { + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + interfaceRenderHitPoints(false); + interfaceRenderArmorClass(false); + windowUnhide(gInterfaceBarWindow); + _intfaceHidden = false; + } + } + indicatorBarRefresh(); +} + +// 0x45EA64 +void interfaceBarEnable() +{ + if (!gInterfaceBarEnabled) { + buttonEnable(gInventoryButton); + buttonEnable(gOptionsButton); + buttonEnable(gSkilldexButton); + buttonEnable(gMapButton); + buttonEnable(gPipboyButton); + buttonEnable(gCharacterButton); + + if (gInterfaceItemStates[gInterfaceCurrentHand].isDisabled == 0) { + buttonEnable(gSingleAttackButton); + } + + buttonEnable(gEndTurnButton); + buttonEnable(gEndCombatButton); + displayMonitorEnable(); + + gInterfaceBarEnabled = true; + } +} + +// 0x45EAFC +void interfaceBarDisable() +{ + if (gInterfaceBarEnabled) { + displayMonitorDisable(); + buttonDisable(gInventoryButton); + buttonDisable(gOptionsButton); + buttonDisable(gSkilldexButton); + buttonDisable(gMapButton); + buttonDisable(gPipboyButton); + buttonDisable(gCharacterButton); + if (gInterfaceItemStates[gInterfaceCurrentHand].isDisabled == 0) { + buttonDisable(gSingleAttackButton); + } + buttonDisable(gEndTurnButton); + buttonDisable(gEndCombatButton); + gInterfaceBarEnabled = false; + } +} + +// 0x45EB90 +bool interfaceBarEnabled() +{ + return gInterfaceBarEnabled; +} + +// 0x45EB98 +void interfaceBarRefresh() +{ + if (gInterfaceBarWindow != -1) { + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + interfaceRenderHitPoints(false); + interfaceRenderArmorClass(false); + indicatorBarRefresh(); + windowRefresh(gInterfaceBarWindow); + } + indicatorBarRefresh(); +} + +// Render hit points. +// +// 0x45EBD8 +void interfaceRenderHitPoints(bool animate) +{ + if (gInterfaceBarWindow == -1) { + return; + } + + int hp = critterGetHitPoints(gDude); + int maxHp = critterGetStat(gDude, 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] = gInterfaceLastRenderedHitPoints; + v2[0] = gInterfaceLastRenderedHitPointsColor; + + if (gInterfaceLastRenderedHitPointsColor != color) { + if (hp >= gInterfaceLastRenderedHitPoints) { + if (gInterfaceLastRenderedHitPoints < red && hp >= red) { + v1[count] = red; + v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW; + count += 1; + } + + if (gInterfaceLastRenderedHitPoints < yellow && hp >= yellow) { + v1[count] = yellow; + v2[count] = INTERFACE_NUMBERS_COLOR_WHITE; + count += 1; + } + } else { + if (gInterfaceLastRenderedHitPoints >= yellow && hp < yellow) { + v1[count] = yellow; + v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW; + count += 1; + } + + if (gInterfaceLastRenderedHitPoints >= red && hp < red) { + v1[count] = red; + v2[count] = INTERFACE_NUMBERS_COLOR_RED; + count += 1; + } + } + } + + v1[count] = hp; + + if (animate) { + int delay = 250 / (abs(gInterfaceLastRenderedHitPoints - hp) + 1); + for (int index = 0; index < count; index++) { + interfaceRenderCounter(473, 40, v1[index], v1[index + 1], v2[index], delay); + } + } else { + interfaceRenderCounter(473, 40, gInterfaceLastRenderedHitPoints, hp, color, 0); + } + + gInterfaceLastRenderedHitPoints = hp; + gInterfaceLastRenderedHitPointsColor = color; +} + +// Render armor class. +// +// 0x45EDA8 +void interfaceRenderArmorClass(bool animate) +{ + int armorClass = critterGetStat(gDude, STAT_ARMOR_CLASS); + + int delay = 0; + if (animate) { + delay = 250 / (abs(gInterfaceLastRenderedArmorClass - armorClass) + 1); + } + + interfaceRenderCounter(473, 75, gInterfaceLastRenderedArmorClass, armorClass, 0, delay); + + gInterfaceLastRenderedArmorClass = armorClass; +} + +// 0x45EE0C +void interfaceRenderActionPoints(int actionPointsLeft, int bonusActionPoints) +{ + unsigned char* frmData; + + if (gInterfaceBarWindow == -1) { + return; + } + + blitBufferToBuffer(gInterfaceActionPointsBarBackground, 90, 5, 90, gInterfaceWindowBuffer + 14 * 640 + 316, 640); + + if (actionPointsLeft == -1) { + frmData = gRedLightFrmData; + actionPointsLeft = 10; + bonusActionPoints = 0; + } else { + frmData = gGreenLightFrmData; + + 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++) { + blitBufferToBuffer(frmData, 5, 5, 5, gInterfaceWindowBuffer + 14 * 640 + 316 + index * 9, 640); + } + + for (; index < (actionPointsLeft + bonusActionPoints); index++) { + blitBufferToBuffer(gYellowLightFrmData, 5, 5, 5, gInterfaceWindowBuffer + 14 * 640 + 316 + index * 9, 640); + } + + if (!gInterfaceBarInitialized) { + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarActionPointsBarRect); + } +} + +// 0x45EF6C +int interfaceGetCurrentHitMode(int* hitMode, bool* aiming) +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + *aiming = false; + + switch (gInterfaceItemStates[gInterfaceCurrentHand].action) { + case INTERFACE_ITEM_ACTION_PRIMARY_AIMING: + *aiming = true; + // FALLTHROUGH + case INTERFACE_ITEM_ACTION_PRIMARY: + *hitMode = gInterfaceItemStates[gInterfaceCurrentHand].primaryHitMode; + return 0; + case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: + *aiming = true; + // FALLTHROUGH + case INTERFACE_ITEM_ACTION_SECONDARY: + *hitMode = gInterfaceItemStates[gInterfaceCurrentHand].secondaryHitMode; + return 0; + } + + return -1; +} + +// 0x45EFEC +int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction) +{ + if (isoIsDisabled()) { + animated = false; + } + + if (gInterfaceBarWindow == -1) { + return -1; + } + + Object* oldCurrentItem = gInterfaceItemStates[gInterfaceCurrentHand].item; + + InterfaceItemState* leftItemState = &(gInterfaceItemStates[HAND_LEFT]); + Object* item1 = critterGetItem1(gDude); + if (item1 == leftItemState->item && leftItemState->item != NULL) { + if (leftItemState->item != NULL) { + leftItemState->isDisabled = _can_use_weapon(item1); + leftItemState->itemFid = itemGetInventoryFid(item1); + } + } else { + leftItemState->item = item1; + if (item1 != NULL) { + leftItemState->isDisabled = _can_use_weapon(item1); + leftItemState->primaryHitMode = HIT_MODE_LEFT_WEAPON_PRIMARY; + leftItemState->secondaryHitMode = HIT_MODE_LEFT_WEAPON_SECONDARY; + leftItemState->isWeapon = itemGetType(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 = itemGetInventoryFid(item1); + } else { + leftItemState->isDisabled = 0; + leftItemState->isWeapon = 1; + leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; + leftItemState->itemFid = -1; + + int unarmed = skillGetValue(gDude, SKILL_UNARMED); + int agility = critterGetStat(gDude, STAT_AGILITY); + int strength = critterGetStat(gDude, STAT_STRENGTH); + int level = pcGetStat(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 = &(gInterfaceItemStates[HAND_RIGHT]); + + Object* item2 = critterGetItem2(gDude); + if (item2 == rightItemState->item && rightItemState->item != NULL) { + if (rightItemState->item != NULL) { + rightItemState->isDisabled = _can_use_weapon(rightItemState->item); + rightItemState->itemFid = itemGetInventoryFid(rightItemState->item); + } + } else { + rightItemState->item = item2; + + if (item2 != NULL) { + rightItemState->isDisabled = _can_use_weapon(item2); + rightItemState->primaryHitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY; + rightItemState->secondaryHitMode = HIT_MODE_RIGHT_WEAPON_SECONDARY; + rightItemState->isWeapon = itemGetType(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 = itemGetInventoryFid(item2); + } else { + rightItemState->isDisabled = 0; + rightItemState->isWeapon = 1; + rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; + rightItemState->itemFid = -1; + + int unarmed = skillGetValue(gDude, SKILL_UNARMED); + int agility = critterGetStat(gDude, STAT_AGILITY); + int strength = critterGetStat(gDude, STAT_STRENGTH); + int level = pcGetStat(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 = gInterfaceItemStates[gInterfaceCurrentHand].item; + if (newCurrentItem != oldCurrentItem) { + int animationCode = 0; + if (newCurrentItem != NULL) { + if (itemGetType(newCurrentItem) == ITEM_TYPE_WEAPON) { + animationCode = weaponGetAnimationCode(newCurrentItem); + } + } + + interfaceBarSwapHandsAnimatePutAwayTakeOutSequence((gDude->fid & 0xF000) >> 12, animationCode); + + return 0; + } + } + + interfaceBarRefreshMainAction(); + + return 0; +} + +// 0x45F404 +int interfaceBarSwapHands(bool animated) +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + gInterfaceCurrentHand = 1 - gInterfaceCurrentHand; + + if (animated) { + Object* item = gInterfaceItemStates[gInterfaceCurrentHand].item; + int animationCode = 0; + if (item != NULL) { + if (itemGetType(item) == ITEM_TYPE_WEAPON) { + animationCode = weaponGetAnimationCode(item); + } + } + + interfaceBarSwapHandsAnimatePutAwayTakeOutSequence((gDude->fid & 0xF000) >> 12, animationCode); + } else { + interfaceBarRefreshMainAction(); + } + + int mode = gameMouseGetMode(); + if (mode == GAME_MOUSE_MODE_CROSSHAIR || mode == GAME_MOUSE_MODE_USE_CROSSHAIR) { + gameMouseSetMode(GAME_MOUSE_MODE_MOVE); + } + + return 0; +} + +// 0x45F4B4 +int interfaceGetItemActions(int* leftItemAction, int* rightItemAction) +{ + *leftItemAction = gInterfaceItemStates[HAND_LEFT].action; + *rightItemAction = gInterfaceItemStates[HAND_RIGHT].action; + return 0; +} + +// 0x45F4E0 +int interfaceCycleItemAction() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + InterfaceItemState* itemState = &(gInterfaceItemStates[gInterfaceCurrentHand]); + + 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(gDude, itemState->primaryHitMode)) { + done = true; + } + break; + case INTERFACE_ITEM_ACTION_SECONDARY: + if (itemState->secondaryHitMode != HIT_MODE_PUNCH + && itemState->secondaryHitMode != HIT_MODE_KICK + && weaponGetAttackTypeForHitMode(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 + && weaponGetAttackTypeForHitMode(itemState->item, itemState->secondaryHitMode) != ATTACK_TYPE_NONE + && _item_w_called_shot(gDude, itemState->secondaryHitMode)) { + done = true; + } + break; + case INTERFACE_ITEM_ACTION_RELOAD: + if (ammoGetCapacity(itemState->item) != ammoGetQuantity(itemState->item)) { + done = true; + } + break; + case INTERFACE_ITEM_ACTION_COUNT: + itemState->action = INTERFACE_ITEM_ACTION_USE; + break; + } + } + } + + if (oldAction != itemState->action) { + interfaceBarRefreshMainAction(); + } + + return 0; +} + +// 0x45F5EC +void _intface_use_item() +{ + if (gInterfaceBarWindow == -1) { + return; + } + + InterfaceItemState* ptr = &(gInterfaceItemStates[gInterfaceCurrentHand]); + + if (ptr->isWeapon != 0) { + if (ptr->action == INTERFACE_ITEM_ACTION_RELOAD) { + if (isInCombat()) { + int hitMode = gInterfaceCurrentHand == HAND_LEFT + ? HIT_MODE_LEFT_WEAPON_RELOAD + : HIT_MODE_RIGHT_WEAPON_RELOAD; + + int actionPointsRequired = _item_mp_cost(gDude, hitMode, false); + if (actionPointsRequired <= gDude->data.critter.combat.ap) { + if (_intface_item_reload() == 0) { + if (actionPointsRequired > gDude->data.critter.combat.ap) { + gDude->data.critter.combat.ap = 0; + } else { + gDude->data.critter.combat.ap -= actionPointsRequired; + } + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + } + } else { + _intface_item_reload(); + } + } else { + gameMouseSetCursor(MOUSE_CURSOR_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_CROSSHAIR); + if (!isInCombat()) { + _combat(NULL); + } + } + } else if (_proto_action_can_use_on(ptr->item->pid)) { + gameMouseSetCursor(MOUSE_CURSOR_USE_CROSSHAIR); + gameMouseSetMode(GAME_MOUSE_MODE_USE_CROSSHAIR); + } else if (_obj_action_can_use(ptr->item)) { + if (isInCombat()) { + int actionPointsRequired = _item_mp_cost(gDude, ptr->secondaryHitMode, false); + if (actionPointsRequired <= gDude->data.critter.combat.ap) { + _obj_use_item(gDude, ptr->item); + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + if (actionPointsRequired > gDude->data.critter.combat.ap) { + gDude->data.critter.combat.ap = 0; + } else { + gDude->data.critter.combat.ap -= actionPointsRequired; + } + + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + } else { + _obj_use_item(gDude, ptr->item); + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } + } +} + +// 0x45F7FC +int interfaceGetCurrentHand() +{ + return gInterfaceCurrentHand; +} + +// 0x45F804 +int interfaceGetActiveItem(Object** itemPtr) +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + *itemPtr = gInterfaceItemStates[gInterfaceCurrentHand].item; + + return 0; +} + +// 0x45F838 +int _intface_update_ammo_lights() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + InterfaceItemState* p = &(gInterfaceItemStates[gInterfaceCurrentHand]); + + int ratio = 0; + + if (p->isWeapon != 0) { + // calls sub_478674 twice, probably because if min/max kind macro + int maximum = ammoGetCapacity(p->item); + if (maximum > 0) { + int current = ammoGetQuantity(p->item); + ratio = (int)((double)current / (double)maximum * 70.0); + } + } else { + if (itemGetType(p->item) == ITEM_TYPE_MISC) { + // calls sub_4793D0 twice, probably because if min/max kind macro + int maximum = miscItemGetMaxCharges(p->item); + if (maximum > 0) { + int current = miscItemGetCharges(p->item); + ratio = (int)((double)current / (double)maximum * 70.0); + } + } + } + + interfaceUpdateAmmoBar(463, ratio); + + return 0; +} + +// 0x45F96C +void interfaceBarEndButtonsShow(bool animated) +{ + if (gInterfaceBarWindow == -1) { + return; + } + + if (gInterfaceBarEndButtonsIsVisible) { + return; + } + + int fid = buildFid(6, 104, 0, 0, 0); + CacheEntry* handle; + Art* art = artLock(fid, &handle); + if (art == NULL) { + return; + } + + int frameCount = artGetFrameCount(art); + soundPlayFile("iciboxx1"); + + if (animated) { + unsigned int delay = 1000 / artGetFramesPerSecond(art); + int time = 0; + int frame = 0; + while (frame < frameCount) { + if (getTicksSince(time) >= delay) { + unsigned char* src = artGetFrameData(art, frame, 0); + if (src != NULL) { + blitBufferToBuffer(src, 57, 58, 57, gInterfaceWindowBuffer + 640 * 38 + 580, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + } + + time = _get_time(); + frame++; + } + gameMouseRefresh(); + } + } else { + unsigned char* src = artGetFrameData(art, frameCount - 1, 0); + blitBufferToBuffer(src, 57, 58, 57, gInterfaceWindowBuffer + 640 * 38 + 580, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + } + + artUnlock(handle); + + gInterfaceBarEndButtonsIsVisible = true; + endTurnButtonInit(); + endCombatButtonInit(); + interfaceBarEndButtonsRenderRedLights(); +} + +// 0x45FAC0 +void interfaceBarEndButtonsHide(bool animated) +{ + if (gInterfaceBarWindow == -1) { + return; + } + + if (!gInterfaceBarEndButtonsIsVisible) { + return; + } + + int fid = buildFid(6, 104, 0, 0, 0); + CacheEntry* handle; + Art* art = artLock(fid, &handle); + if (art == NULL) { + return; + } + + endTurnButtonFree(); + endCombatButtonFree(); + soundPlayFile("icibcxx1"); + + if (animated) { + unsigned int delay = 1000 / artGetFramesPerSecond(art); + unsigned int time = 0; + int frame = artGetFrameCount(art); + + while (frame != 0) { + if (getTicksSince(time) >= delay) { + unsigned char* src = artGetFrameData(art, frame - 1, 0); + unsigned char* dest = gInterfaceWindowBuffer + 640 * 38 + 580; + if (src != NULL) { + blitBufferToBuffer(src, 57, 58, 57, dest, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + } + + time = _get_time(); + frame--; + } + gameMouseRefresh(); + } + } else { + unsigned char* dest = gInterfaceWindowBuffer + 640 * 38 + 580; + unsigned char* src = artGetFrameData(art, 0, 0); + blitBufferToBuffer(src, 57, 58, 57, dest, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + } + + artUnlock(handle); + gInterfaceBarEndButtonsIsVisible = false; +} + +// 0x45FC04 +void interfaceBarEndButtonsRenderGreenLights() +{ + if (gInterfaceBarEndButtonsIsVisible) { + buttonEnable(gEndTurnButton); + buttonEnable(gEndCombatButton); + + // endltgrn.frm - green lights around end turn/combat window + int lightsFid = buildFid(6, 109, 0, 0, 0); + CacheEntry* lightsFrmHandle; + unsigned char* lightsFrmData = artLockFrameData(lightsFid, 0, 0, &lightsFrmHandle); + if (lightsFrmData == NULL) { + return; + } + + soundPlayFile("icombat2"); + blitBufferToBufferTrans(lightsFrmData, 57, 58, 57, gInterfaceWindowBuffer + 38 * 640 + 580, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + + artUnlock(lightsFrmHandle); + } +} + +// 0x45FC98 +void interfaceBarEndButtonsRenderRedLights() +{ + if (gInterfaceBarEndButtonsIsVisible) { + buttonDisable(gEndTurnButton); + buttonDisable(gEndCombatButton); + + CacheEntry* lightsFrmHandle; + // endltred.frm - red lights around end turn/combat window + int lightsFid = buildFid(6, 110, 0, 0, 0); + unsigned char* lightsFrmData = artLockFrameData(lightsFid, 0, 0, &lightsFrmHandle); + if (lightsFrmData == NULL) { + return; + } + + soundPlayFile("icombat1"); + blitBufferToBufferTrans(lightsFrmData, 57, 58, 57, gInterfaceWindowBuffer + 38 * 640 + 580, 640); + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarEndButtonsRect); + + artUnlock(lightsFrmHandle); + } +} + +// 0x45FD88 +int interfaceBarRefreshMainAction() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + buttonEnable(gSingleAttackButton); + + InterfaceItemState* itemState = &(gInterfaceItemStates[gInterfaceCurrentHand]); + int actionPoints = -1; + + if (itemState->isDisabled == 0) { + memcpy(_itemButtonUp, gSingleAttackButtonUpData, sizeof(_itemButtonUp)); + memcpy(_itemButtonDown, gSingleAttackButtonDownData, sizeof(_itemButtonDown)); + + if (itemState->isWeapon == 0) { + int fid; + if (_proto_action_can_use_on(itemState->item->pid)) { + // USE ON + fid = buildFid(6, 294, 0, 0, 0); + } else if (_obj_action_can_use(itemState->item)) { + // USE + fid = buildFid(6, 292, 0, 0, 0); + } else { + fid = -1; + } + + if (fid != -1) { + CacheEntry* useTextFrmHandle; + Art* useTextFrm = artLock(fid, &useTextFrmHandle); + if (useTextFrm != NULL) { + int width = artGetWidth(useTextFrm, 0, 0); + int height = artGetHeight(useTextFrm, 0, 0); + unsigned char* data = artGetFrameData(useTextFrm, 0, 0); + blitBufferToBufferTrans(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); + artUnlock(useTextFrmHandle); + } + + actionPoints = _item_mp_cost(gDude, 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 = buildFid(6, 288, 0, 0, 0); + // FALLTHROUGH + case INTERFACE_ITEM_ACTION_PRIMARY: + hitMode = itemState->primaryHitMode; + break; + case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: + bullseyeFid = buildFid(6, 288, 0, 0, 0); + // FALLTHROUGH + case INTERFACE_ITEM_ACTION_SECONDARY: + hitMode = itemState->secondaryHitMode; + break; + case INTERFACE_ITEM_ACTION_RELOAD: + actionPoints = _item_mp_cost(gDude, gInterfaceCurrentHand == HAND_LEFT ? HIT_MODE_LEFT_WEAPON_RELOAD : HIT_MODE_RIGHT_WEAPON_RELOAD, false); + primaryFid = buildFid(6, 291, 0, 0, 0); + break; + } + + if (bullseyeFid != -1) { + CacheEntry* bullseyeFrmHandle; + Art* bullseyeFrm = artLock(bullseyeFid, &bullseyeFrmHandle); + if (bullseyeFrm != NULL) { + int width = artGetWidth(bullseyeFrm, 0, 0); + int height = artGetHeight(bullseyeFrm, 0, 0); + unsigned char* data = artGetFrameData(bullseyeFrm, 0, 0); + blitBufferToBufferTrans(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); + artUnlock(bullseyeFrmHandle); + } + } + + if (hitMode != -1) { + actionPoints = _item_w_mp_cost(gDude, hitMode, bullseyeFid != -1); + + int id; + int anim = critterGetAnimationForHitMode(gDude, 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 = buildFid(6, id, 0, 0, 0); + } + + if (primaryFid != -1) { + CacheEntry* primaryFrmHandle; + Art* primaryFrm = artLock(primaryFid, &primaryFrmHandle); + if (primaryFrm != NULL) { + int width = artGetWidth(primaryFrm, 0, 0); + int height = artGetHeight(primaryFrm, 0, 0); + unsigned char* data = artGetFrameData(primaryFrm, 0, 0); + blitBufferToBufferTrans(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); + artUnlock(primaryFrmHandle); + } + } + } + } + + if (actionPoints >= 0 && actionPoints < 10) { + // movement point text + int fid = buildFid(6, 289, 0, 0, 0); + + CacheEntry* handle; + Art* art = artLock(fid, &handle); + if (art != NULL) { + int width = artGetWidth(art, 0, 0); + int height = artGetHeight(art, 0, 0); + unsigned char* data = artGetFrameData(art, 0, 0); + + blitBufferToBufferTrans(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); + artUnlock(handle); + + int offset = width + 7; + + // movement point numbers - ten numbers 0 to 9, each 10 pixels wide. + fid = buildFid(6, 290, 0, 0, 0); + art = artLock(fid, &handle); + if (art != NULL) { + width = artGetWidth(art, 0, 0); + height = artGetHeight(art, 0, 0); + data = artGetFrameData(art, 0, 0); + + blitBufferToBufferTrans(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); + + artUnlock(handle); + } + } + } else { + memcpy(_itemButtonUp, _itemButtonDisabled, sizeof(_itemButtonUp)); + memcpy(_itemButtonDown, _itemButtonDisabled, sizeof(_itemButtonDown)); + } + + if (itemState->itemFid != -1) { + CacheEntry* itemFrmHandle; + Art* itemFrm = artLock(itemState->itemFid, &itemFrmHandle); + if (itemFrm != NULL) { + int width = artGetWidth(itemFrm, 0, 0); + int height = artGetHeight(itemFrm, 0, 0); + unsigned char* data = artGetFrameData(itemFrm, 0, 0); + + int v46 = (188 - width) / 2; + int v47 = (67 - height) / 2 - 2; + + blitBufferToBufferTrans(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); + artUnlock(itemFrmHandle); + } + } + + if (!gInterfaceBarInitialized) { + _intface_update_ammo_lights(); + + windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarMainActionRect); + + if (itemState->isDisabled != 0) { + buttonDisable(gSingleAttackButton); + } else { + buttonEnable(gSingleAttackButton); + } + } + + return 0; +} + +// 0x460658 +int _intface_redraw_items_callback(Object* a1, Object* a2) +{ + interfaceBarRefreshMainAction(); + return 0; +} + +// 0x460660 +int _intface_change_fid_callback(Object* a1, Object* a2) +{ + gInterfaceBarSwapHandsInProgress = false; + return 0; +} + +// 0x46066C +void interfaceBarSwapHandsAnimatePutAwayTakeOutSequence(int previousWeaponAnimationCode, int weaponAnimationCode) +{ + gInterfaceBarSwapHandsInProgress = true; + + reg_anim_clear(gDude); + reg_anim_begin(2); + reg_anim_update_light(gDude, 4, 0); + + if (previousWeaponAnimationCode != 0) { + const char* sfx = sfxBuildCharName(gDude, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(gDude, sfx, 0); + reg_anim_animate(gDude, ANIM_PUT_AWAY, 0); + } + + reg_anim_11_1(NULL, NULL, _intface_redraw_items_callback, -1); + + Object* item = gInterfaceItemStates[gInterfaceCurrentHand].item; + if (item != NULL && item->lightDistance > 4) { + reg_anim_update_light(gDude, item->lightDistance, 0); + } + + if (weaponAnimationCode != 0) { + reg_anim_18(gDude, weaponAnimationCode, -1); + } else { + int fid = buildFid(1, gDude->fid & 0xFFF, ANIM_STAND, 0, gDude->rotation + 1); + reg_anim_17(gDude, fid, -1); + } + + reg_anim_11_1(NULL, NULL, _intface_change_fid_callback, -1); + + if (reg_anim_end() == -1) { + return; + } + + bool interfaceBarWasEnabled = gInterfaceBarEnabled; + + interfaceBarDisable(); + _gmouse_disable(0); + + gameMouseSetCursor(MOUSE_CURSOR_WAIT_WATCH); + + while (gInterfaceBarSwapHandsInProgress) { + if (_game_user_wants_to_quit) { + break; + } + + _get_input(); + } + + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + _gmouse_enable(); + + if (interfaceBarWasEnabled) { + interfaceBarEnable(); + } +} + +// 0x4607E0 +int endTurnButtonInit() +{ + int fid; + + if (gInterfaceBarWindow == -1) { + return -1; + } + + if (!gInterfaceBarEndButtonsIsVisible) { + return -1; + } + + fid = buildFid(6, 105, 0, 0, 0); + gEndTurnButtonUpFrmData = artLockFrameData(fid, 0, 0, &gEndTurnButtonUpFrmHandle); + if (gEndTurnButtonUpFrmData == NULL) { + return -1; + } + + fid = buildFid(6, 106, 0, 0, 0); + gEndTurnButtonDownFrmData = artLockFrameData(fid, 0, 0, &gEndTurnButtonDownFrmHandle); + if (gEndTurnButtonDownFrmData == NULL) { + return -1; + } + + gEndTurnButton = buttonCreate(gInterfaceBarWindow, 590, 43, 38, 22, -1, -1, -1, 32, gEndTurnButtonUpFrmData, gEndTurnButtonDownFrmData, NULL, 0); + if (gEndTurnButton == -1) { + return -1; + } + + _win_register_button_disable(gEndTurnButton, gEndTurnButtonUpFrmData, gEndTurnButtonUpFrmData, gEndTurnButtonUpFrmData); + buttonSetCallbacks(gEndTurnButton, _gsound_med_butt_press, _gsound_med_butt_release); + + return 0; +} + +// 0x4608C4 +int endTurnButtonFree() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + if (gEndTurnButton != -1) { + buttonDestroy(gEndTurnButton); + gEndTurnButton = -1; + } + + if (gEndTurnButtonDownFrmData) { + artUnlock(gEndTurnButtonDownFrmHandle); + gEndTurnButtonDownFrmHandle = NULL; + gEndTurnButtonDownFrmData = NULL; + } + + if (gEndTurnButtonUpFrmData) { + artUnlock(gEndTurnButtonUpFrmHandle); + gEndTurnButtonUpFrmHandle = NULL; + gEndTurnButtonUpFrmData = NULL; + } + + return 0; +} + +// 0x460940 +int endCombatButtonInit() +{ + int fid; + + if (gInterfaceBarWindow == -1) { + return -1; + } + + if (!gInterfaceBarEndButtonsIsVisible) { + return -1; + } + + fid = buildFid(6, 107, 0, 0, 0); + gEndCombatButtonUpFrmData = artLockFrameData(fid, 0, 0, &gEndCombatButtonUpFrmHandle); + if (gEndCombatButtonUpFrmData == NULL) { + return -1; + } + + fid = buildFid(6, 108, 0, 0, 0); + gEndCombatButtonDownFrmData = artLockFrameData(fid, 0, 0, &gEndCombatButtonDownFrmHandle); + if (gEndCombatButtonDownFrmData == NULL) { + return -1; + } + + gEndCombatButton = buttonCreate(gInterfaceBarWindow, 590, 65, 38, 22, -1, -1, -1, 13, gEndCombatButtonUpFrmData, gEndCombatButtonDownFrmData, NULL, 0); + if (gEndCombatButton == -1) { + return -1; + } + + _win_register_button_disable(gEndCombatButton, gEndCombatButtonUpFrmData, gEndCombatButtonUpFrmData, gEndCombatButtonUpFrmData); + buttonSetCallbacks(gEndCombatButton, _gsound_med_butt_press, _gsound_med_butt_release); + + return 0; +} + +// 0x460A24 +int endCombatButtonFree() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + if (gEndCombatButton != -1) { + buttonDestroy(gEndCombatButton); + gEndCombatButton = -1; + } + + if (gEndCombatButtonDownFrmData != NULL) { + artUnlock(gEndCombatButtonDownFrmHandle); + gEndCombatButtonDownFrmHandle = NULL; + gEndCombatButtonDownFrmData = NULL; + } + + if (gEndCombatButtonUpFrmData != NULL) { + artUnlock(gEndCombatButtonUpFrmHandle); + gEndCombatButtonUpFrmHandle = NULL; + gEndCombatButtonUpFrmData = NULL; + } + + return 0; +} + +// 0x460AA0 +void interfaceUpdateAmmoBar(int x, int ratio) +{ + if ((ratio & 1) != 0) { + ratio -= 1; + } + + unsigned char* dest = gInterfaceWindowBuffer + 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 (!gInterfaceBarInitialized) { + Rect rect; + rect.left = x; + rect.top = 26; + rect.right = x + 1; + rect.bottom = 26 + 70; + windowRefreshRect(gInterfaceBarWindow, &rect); + } +} + +// 0x460B20 +int _intface_item_reload() +{ + if (gInterfaceBarWindow == -1) { + return -1; + } + + bool v0 = false; + while (_item_w_try_reload(gDude, gInterfaceItemStates[gInterfaceCurrentHand].item) != -1) { + v0 = true; + } + + interfaceCycleItemAction(); + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + + if (!v0) { + return -1; + } + + const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, gInterfaceItemStates[gInterfaceCurrentHand].item, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL); + soundPlayFile(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 +void interfaceRenderCounter(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 = gNumbersFrmData + offset; + unsigned char* dest = gInterfaceWindowBuffer + 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 (gInterfaceBarInitialized || 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; + + blitBufferToBuffer(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640); + blitBufferToBuffer(numbers + 9 * tens, 9, 17, 360, tensDest, 640); + blitBufferToBuffer(numbers + 9 * ones, 9, 17, 360, onesDest, 640); + blitBufferToBuffer(normalizedSign >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640); + + if (!gInterfaceBarInitialized) { + Rect numbersRect = { x, y, x + 33, y + 17 }; + windowRefreshRect(gInterfaceBarWindow, &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; + } + + blitBufferToBuffer(upSrc, 9, 17, 360, onesDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + + ones += v49; + + if (ones > 9 || ones < 0) { + blitBufferToBuffer(upSrc, 9, 17, 360, tensDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + + tens += v49; + ones -= 10 * v49; + if (tens == 10 || tens == -1) { + blitBufferToBuffer(upSrc, 9, 17, 360, hundredsDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + + hundreds += v49; + tens -= 10 * v49; + if (hundreds == 10 || hundreds == -1) { + hundreds -= 10 * v49; + } + + blitBufferToBuffer(downSrc, 9, 17, 360, hundredsDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + } + + blitBufferToBuffer(downSrc, 9, 17, 360, tensDest, 640); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + } + + blitBufferToBuffer(downSrc, 9, 17, 360, onesDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + + previousValue += change; + + blitBufferToBuffer(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640); + blitBufferToBuffer(numbers + 9 * tens, 9, 17, 360, tensDest, 640); + blitBufferToBuffer(numbers + 9 * ones, 9, 17, 360, onesDest, 640); + + blitBufferToBuffer(previousValue >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640); + _mouse_info(); + gameMouseRefresh(); + coreDelay(delay); + windowRefreshRect(gInterfaceBarWindow, &numbersRect); + } + } + } +} + +// 0x461134 +int indicatorBarInit() +{ + int oldFont = fontGetCurrent(); + + if (gIndicatorBarWindow != -1) { + return 0; + } + + MessageList messageList; + MessageListItem messageListItem; + int rc = 0; + if (!messageListInit(&messageList)) { + rc = -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "intrface.msg"); + + if (rc != -1) { + if (!messageListLoad(&messageList, path)) { + rc = -1; + } + } + + if (rc == -1) { + debugPrint("\nINTRFACE: Error indicator box messages! **\n"); + return -1; + } + + CacheEntry* indicatorBoxFrmHandle; + int width; + int height; + int indicatorBoxFid = buildFid(6, 126, 0, 0, 0); + unsigned char* indicatorBoxFrmData = artLockFrameDataReturningSize(indicatorBoxFid, &indicatorBoxFrmHandle, &width, &height); + if (indicatorBoxFrmData == NULL) { + debugPrint("\nINTRFACE: Error initializing indicator box graphics! **\n"); + messageListFree(&messageList); + return -1; + } + + for (int index = 0; index < INDICATOR_COUNT; index++) { + IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[index]); + + indicatorDescription->data = internal_malloc(INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT); + if (indicatorDescription->data == NULL) { + debugPrint("\nINTRFACE: Error initializing indicator box graphics! **"); + + while (--index >= 0) { + internal_free(gIndicatorDescriptions[index].data); + } + + messageListFree(&messageList); + artUnlock(indicatorBoxFrmHandle); + + return -1; + } + } + + fontSetCurrent(101); + + for (int index = 0; index < INDICATOR_COUNT; index++) { + IndicatorDescription* indicator = &(gIndicatorDescriptions[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 - fontGetLineHeight()) / 2; + int x = (INDICATOR_BOX_WIDTH - fontGetStringWidth(text)) / 2; + fontDrawText(indicator->data + INDICATOR_BOX_WIDTH * y + x, text, INDICATOR_BOX_WIDTH, INDICATOR_BOX_WIDTH, color); + } + + gIndicatorBarIsVisible = true; + indicatorBarRefresh(); + + messageListFree(&messageList); + artUnlock(indicatorBoxFrmHandle); + fontSetCurrent(oldFont); + + return 0; +} + +// 0x461454 +void interfaceBarFree() +{ + if (gIndicatorBarWindow != -1) { + windowDestroy(gIndicatorBarWindow); + gIndicatorBarWindow = -1; + } + + for (int index = 0; index < INDICATOR_COUNT; index++) { + IndicatorDescription* indicatorBoxDescription = &(gIndicatorDescriptions[index]); + if (indicatorBoxDescription->data != NULL) { + internal_free(indicatorBoxDescription->data); + indicatorBoxDescription->data = NULL; + } + } +} + +// NOTE: This function is not referenced in the original code. +// +// 0x4614A0 +void indicatorBarReset() +{ + if (gIndicatorBarWindow != -1) { + windowDestroy(gIndicatorBarWindow); + gIndicatorBarWindow = -1; + } + + gIndicatorBarIsVisible = true; +} + +// Updates indicator bar. +// +// 0x4614CC +int indicatorBarRefresh() +{ + if (gInterfaceBarWindow != -1 && gIndicatorBarIsVisible && !_intfaceHidden) { + for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) { + gIndicatorSlots[index] = -1; + } + + int count = 0; + + if (dudeHasState(DUDE_STATE_SNEAKING)) { + if (indicatorBarAdd(INDICATOR_SNEAK)) { + ++count; + } + } + + if (dudeHasState(DUDE_STATE_LEVEL_UP_AVAILABLE)) { + if (indicatorBarAdd(INDICATOR_LEVEL)) { + ++count; + } + } + + if (dudeHasState(DUDE_STATE_ADDICTED)) { + if (indicatorBarAdd(INDICATOR_ADDICT)) { + ++count; + } + } + + if (critterGetPoison(gDude) > POISON_INDICATOR_THRESHOLD) { + if (indicatorBarAdd(INDICATOR_POISONED)) { + ++count; + } + } + + if (critterGetRadiation(gDude) > RADATION_INDICATOR_THRESHOLD) { + if (indicatorBarAdd(INDICATOR_RADIATED)) { + ++count; + } + } + + if (count > 1) { + qsort(gIndicatorSlots, count, sizeof(*gIndicatorSlots), indicatorBoxCompareByPosition); + } + + if (gIndicatorBarWindow != -1) { + windowDestroy(gIndicatorBarWindow); + gIndicatorBarWindow = -1; + } + + if (count != 0) { + gIndicatorBarWindow = windowCreate(INDICATOR_BAR_X, + INDICATOR_BAR_Y, + (INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, + INDICATOR_BOX_HEIGHT, + _colorTable[0], + 0); + indicatorBarRender(count); + windowRefresh(gIndicatorBarWindow); + } + + return count; + } + + if (gIndicatorBarWindow != -1) { + windowDestroy(gIndicatorBarWindow); + gIndicatorBarWindow = -1; + } + + return 0; +} + +// 0x461624 +int indicatorBoxCompareByPosition(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 +void indicatorBarRender(int count) +{ + if (gIndicatorBarWindow == -1) { + return; + } + + if (count == 0) { + return; + } + + int windowWidth = windowGetWidth(gIndicatorBarWindow); + unsigned char* windowBuffer = windowGetBuffer(gIndicatorBarWindow); + + // 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 = gIndicatorSlots[index]; + IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[indicator]); + + blitBufferToBufferTrans(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 +bool indicatorBarAdd(int indicator) +{ + for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) { + if (gIndicatorSlots[index] == -1) { + gIndicatorSlots[index] = indicator; + return true; + } + } + + debugPrint("\nINTRFACE: no free bar box slots!\n"); + + return false; +} + +// 0x461740 +bool indicatorBarShow() +{ + bool oldIsVisible = gIndicatorBarIsVisible; + gIndicatorBarIsVisible = true; + + indicatorBarRefresh(); + + return oldIsVisible; +} + +// 0x461760 +bool indicatorBarHide() +{ + bool oldIsVisible = gIndicatorBarIsVisible; + gIndicatorBarIsVisible = false; + + indicatorBarRefresh(); + + return oldIsVisible; +} diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 0000000..828c4f0 --- /dev/null +++ b/src/interface.h @@ -0,0 +1,222 @@ +#ifndef INTERFACE_H +#define INTERFACE_H + +#include "art.h" +#include "db.h" +#include "geometry.h" +#include "obj_types.h" + +#include + +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; + +// 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; + +// 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; + +extern bool gInterfaceBarInitialized; +extern bool gInterfaceBarSwapHandsInProgress; +extern bool gInterfaceBarEnabled; +extern bool _intfaceHidden; +extern int gInventoryButton; +extern CacheEntry* gInventoryButtonUpFrmHandle; +extern CacheEntry* gInventoryButtonDownFrmHandle; +extern int gOptionsButton; +extern CacheEntry* gOptionsButtonUpFrmHandle; +extern CacheEntry* gOptionsButtonDownFrmHandle; +extern int gSkilldexButton; +extern CacheEntry* gSkilldexButtonUpFrmHandle; +extern CacheEntry* gSkilldexButtonDownFrmHandle; +extern CacheEntry* gSkilldexButtonMaskFrmHandle; +extern int gMapButton; +extern CacheEntry* gMapButtonUpFrmHandle; +extern CacheEntry* gMapButtonDownFrmHandle; +extern CacheEntry* gMapButtonMaskFrmHandle; +extern int gPipboyButton; +extern CacheEntry* gPipboyButtonUpFrmHandle; +extern CacheEntry* gPipboyButtonDownFrmHandle; +extern int gCharacterButton; +extern CacheEntry* gCharacterButtonUpFrmHandle; +extern CacheEntry* gCharacterButtonDownFrmHandle; +extern int gSingleAttackButton; +extern CacheEntry* gSingleAttackButtonUpHandle; +extern CacheEntry* gSingleAttackButtonDownHandle; +extern CacheEntry* _itemButtonDisabledKey; +extern int gInterfaceCurrentHand; +extern const Rect gInterfaceBarMainActionRect; +extern int gChangeHandsButton; +extern CacheEntry* gChangeHandsButtonUpFrmHandle; +extern CacheEntry* gChangeHandsButtonDownFrmHandle; +extern CacheEntry* gChangeHandsButtonMaskFrmHandle; +extern bool gInterfaceBarEndButtonsIsVisible; +extern const Rect gInterfaceBarEndButtonsRect; +extern int gEndTurnButton; +extern CacheEntry* gEndTurnButtonUpFrmHandle; +extern CacheEntry* gEndTurnButtonDownFrmHandle; +extern int gEndCombatButton; +extern CacheEntry* gEndCombatButtonUpFrmHandle; +extern CacheEntry* gEndCombatButtonDownFrmHandle; +extern unsigned char* gGreenLightFrmData; +extern unsigned char* gYellowLightFrmData; +extern unsigned char* gRedLightFrmData; +extern const Rect gInterfaceBarActionPointsBarRect; +extern unsigned char* gNumbersFrmData; +extern IndicatorDescription gIndicatorDescriptions[INDICATOR_COUNT]; +extern int gInterfaceBarWindow; +extern int gIndicatorBarWindow; +extern int gInterfaceLastRenderedHitPoints; +extern int gInterfaceLastRenderedHitPointsColor; +extern int gInterfaceLastRenderedArmorClass; + +extern int gIndicatorSlots[INDICATOR_SLOTS_COUNT]; +extern InterfaceItemState gInterfaceItemStates[HAND_COUNT]; +extern CacheEntry* gYellowLightFrmHandle; +extern CacheEntry* gRedLightFrmHandle; +extern CacheEntry* gNumbersFrmHandle; +extern bool gIndicatorBarIsVisible; +extern unsigned char* gChangeHandsButtonUpFrmData; +extern CacheEntry* gGreenLightFrmHandle; +extern unsigned char* gEndCombatButtonUpFrmData; +extern unsigned char* gEndCombatButtonDownFrmData; +extern unsigned char* gChangeHandsButtonDownFrmData; +extern unsigned char* gEndTurnButtonDownFrmData; +extern unsigned char _itemButtonDown[12596]; +extern unsigned char* gEndTurnButtonUpFrmData; +extern unsigned char* gChangeHandsButtonMaskFrmData; +extern unsigned char* gCharacterButtonUpFrmData; +extern unsigned char* gSingleAttackButtonUpData; +extern unsigned char* _itemButtonDisabled; +extern unsigned char* gMapButtonDownFrmData; +extern unsigned char* gPipboyButtonUpFrmData; +extern unsigned char* gCharacterButtonDownFrmData; +extern unsigned char* gSingleAttackButtonDownData; +extern unsigned char* gPipboyButtonDownFrmData; +extern unsigned char* gMapButtonMaskFrmData; +extern unsigned char _itemButtonUp[12596]; +extern unsigned char* gMapButtonUpFrmData; +extern unsigned char* gSkilldexButtonMaskFrmData; +extern unsigned char* gSkilldexButtonDownFrmData; +extern unsigned char* gInterfaceWindowBuffer; +extern unsigned char* gInventoryButtonUpFrmData; +extern unsigned char* gOptionsButtonUpFrmData; +extern unsigned char* gOptionsButtonDownFrmData; +extern unsigned char* gSkilldexButtonUpFrmData; +extern unsigned char* gInventoryButtonDownFrmData; +extern unsigned char gInterfaceActionPointsBarBackground[450]; + +int interfaceInit(); +void interfaceReset(); +void interfaceFree(); +int interfaceLoad(File* stream); +int interfaceSave(File* stream); +void _intface_show(); +void interfaceBarEnable(); +void interfaceBarDisable(); +bool interfaceBarEnabled(); +void interfaceBarRefresh(); +void interfaceRenderHitPoints(bool animate); +void interfaceRenderArmorClass(bool animate); +void interfaceRenderActionPoints(int actionPointsLeft, int bonusActionPoints); +int interfaceGetCurrentHitMode(int* hitMode, bool* aiming); +int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction); +int interfaceBarSwapHands(bool animated); +int interfaceGetItemActions(int* leftItemAction, int* rightItemAction); +int interfaceCycleItemAction(); +void _intface_use_item(); +int interfaceGetCurrentHand(); +int interfaceGetActiveItem(Object** a1); +int _intface_update_ammo_lights(); +void interfaceBarEndButtonsShow(bool animated); +void interfaceBarEndButtonsHide(bool animated); +void interfaceBarEndButtonsRenderGreenLights(); +void interfaceBarEndButtonsRenderRedLights(); +int interfaceBarRefreshMainAction(); +int _intface_redraw_items_callback(Object* a1, Object* a2); +int _intface_change_fid_callback(Object* a1, Object* a2); +void interfaceBarSwapHandsAnimatePutAwayTakeOutSequence(int previousWeaponAnimationCode, int weaponAnimationCode); +int endTurnButtonInit(); +int endTurnButtonFree(); +int endCombatButtonInit(); +int endCombatButtonFree(); +void interfaceUpdateAmmoBar(int x, int ratio); +int _intface_item_reload(); +void interfaceRenderCounter(int x, int y, int previousValue, int value, int offset, int delay); +int indicatorBarInit(); +void interfaceBarFree(); +void indicatorBarReset(); +int indicatorBarRefresh(); +int indicatorBoxCompareByPosition(const void* a, const void* b); +void indicatorBarRender(int count); +bool indicatorBarAdd(int indicator); +bool indicatorBarShow(); +bool indicatorBarHide(); + +#endif /* INTERFACE_H */ diff --git a/src/interpreter.c b/src/interpreter.c new file mode 100644 index 0000000..9686cfb --- /dev/null +++ b/src/interpreter.c @@ -0,0 +1,3503 @@ +#include "interpreter.h" + +#include "core.h" +#include "db.h" +#include "debug.h" +#include "export.h" +#include "interpreter_lib.h" +#include "memory_manager.h" + +#include +#include +#include +#include +#include + +// 0x50942C +char _aCouldnTFindPro[] = ""; + +// sayTimeoutMsg +// 0x519038 +int _TimeOut = 0; + +// 0x51903C +int _Enabled = 1; + +// 0x519040 +int (*_timerFunc)() = _defaultTimerFunc; + +// 0x519044 +int _timerTick = 1000; + +// 0x519048 +char* (*_filenameFunc)(char*) = _defaultFilename_; + +// 0x51904C +int (*_outputFunc)(char*) = _outputStr; + +// 0x519050 +int _cpuBurstSize = 10; + +// 0x59E230 +OpcodeHandler* gInterpreterOpcodeHandlers[342]; + +// 0x59E78C +Program* gInterpreterCurrentProgram; + +// 0x59E790 +ProgramListNode* gInterpreterProgramListHead; +int _suspendEvents; +int _busy; + +// 0x4670A0 +int _defaultTimerFunc() +{ + return _get_time(); +} + +// 0x4670B4 +char* _defaultFilename_(char* s) +{ + return s; +} + +// 0x4670B8 +char* _interpretMangleName(char* s) +{ + return _filenameFunc(s); +} + +// 0x4670C0 +int _outputStr(char* a1) +{ + return 1; +} + +// 0x4670C8 +int _checkWait(Program* program) +{ + return 1000 * _timerFunc() / _timerTick <= program->field_70; +} + +// 0x4670FC +void _interpretOutputFunc(int (*func)(char*)) +{ + _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); + + debugPrint(string); + + return rc; +} + +// 0x467160 +char* programGetCurrentProcedureName(Program* program) +{ + int procedureCount = stackReadInt32(program->procedures, 0); + unsigned char* ptr = program->procedures + 4; + + int procedureOffset = stackReadInt32(ptr, 16); + int identifierOffset = stackReadInt32(ptr, 0); + + for (int index = 0; index < procedureCount; index++) { + int nextProcedureOffset = stackReadInt32(ptr + 24, 16); + if (program->instructionPointer >= procedureOffset && program->instructionPointer < nextProcedureOffset) { + return (char*)(program->identifiers + identifierOffset); + } + + ptr += 24; + identifierOffset = stackReadInt32(ptr, 0); + } + + return _aCouldnTFindPro; +} + +// 0x4671F0 +__declspec(noreturn) void programFatalError(const char* format, ...) +{ + char string[260]; + + va_list argptr; + va_start(argptr, format); + vsprintf(string, format, argptr); + va_end(argptr); + + debugPrint("\nError during execution: %s\n", string); + + if (gInterpreterCurrentProgram == NULL) { + debugPrint("No current script"); + } else { + char* procedureName = programGetCurrentProcedureName(gInterpreterCurrentProgram); + debugPrint("Current script: %s, procedure %s", gInterpreterCurrentProgram->name, procedureName); + } + + if (gInterpreterCurrentProgram) { + longjmp(gInterpreterCurrentProgram->env, 1); + } +} + +// 0x467290 +opcode_t stackReadInt16(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 +int stackReadInt32(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 +void stackWriteInt16(int value, unsigned char* stack, int pos) +{ + stack[pos++] = (value >> 8) & 0xFF; + stack[pos] = value & 0xFF; +} + +// NOTE: Inlined. +// +// 0x4672E8 +void stackWriteInt32(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 +void stackPushInt16(unsigned char* data, int* pointer, int value) +{ + if (*pointer + 2 >= 0x1000) { + programFatalError("pushShortStack: Stack overflow."); + } + + stackWriteInt16(value, data, *pointer); + + *pointer += 2; +} + +// pushLongStack +// 0x46736C +void stackPushInt32(unsigned char* data, int* pointer, int value) +{ + int v1; + + if (*pointer + 4 >= 0x1000) { + // FIXME: Should be pushLongStack. + programFatalError("pushShortStack: Stack overflow."); + } + + v1 = *pointer; + stackWriteInt16(value >> 16, data, v1); + stackWriteInt16(value & 0xFFFF, data, v1 + 2); + *pointer = v1 + 4; +} + +// popStackLong +// 0x4673C4 +int stackPopInt32(unsigned char* data, int* pointer) +{ + if (*pointer < 4) { + programFatalError("\nStack underflow long."); + } + + *pointer -= 4; + + return stackReadInt32(data, *pointer); +} + +// popStackShort +// 0x4673F0 +opcode_t stackPopInt16(unsigned char* data, int* pointer) +{ + if (*pointer < 2) { + programFatalError("\nStack underflow short."); + } + + *pointer -= 2; + + // NOTE: uninline + return stackReadInt16(data, *pointer); +} + +// 0x467440 +void programPopString(Program* program, opcode_t opcode, int value) +{ + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + char* string = (char*)(program->dynamicStrings + 4 + value); + short* refcountPtr = (short*)(string - 2); + + if (*refcountPtr != 0) { + *refcountPtr -= 1; + } else { + debugPrint("Reference count zero for %s!\n", string); + } + + if (*refcountPtr < 0) { + debugPrint("String ref went negative, this shouldn\'t ever happen\n"); + } + } +} + +// 0x46748C +void programStackPushInt16(Program* program, int value) +{ + int v1, v2; + unsigned char* v3; + + stackPushInt16(program->stack, &(program->stackPointer), value); + + if (value == VALUE_TYPE_DYNAMIC_STRING) { + v1 = program->stackPointer; + if (v1 >= 6) { + v2 = stackReadInt32(program->stack, v1 - 6); + v3 = program->dynamicStrings + 4 + v2 - 2; + *(short*)v3 = *(short*)v3 + 1; + } + } +} + +// 0x4674DC +void programStackPushInt32(Program* program, int value) +{ + stackPushInt32(program->stack, &(program->stackPointer), value); +} + +// 0x4674F0 +opcode_t programStackPopInt16(Program* program) +{ + return stackPopInt16(program->stack, &(program->stackPointer)); +} + +// 0x467500 +int programStackPopInt32(Program* program) +{ + return stackPopInt32(program->stack, &(program->stackPointer)); +} + +// 0x467510 +void programReturnStackPushInt16(Program* program, int value) +{ + stackPushInt16(program->returnStack, &(program->returnStackPointer), value); + + if (value == VALUE_TYPE_DYNAMIC_STRING && program->stackPointer >= 6) { + int v4 = stackReadInt32(program->returnStack, program->returnStackPointer - 6); + *(short*)(program->dynamicStrings + 4 + v4 - 2) += 1; + } +} + +// 0x467574 +opcode_t programReturnStackPopInt16(Program* program) +{ + opcode_t type; + int v5; + + type = stackPopInt16(program->returnStack, &(program->returnStackPointer)); + if (type == VALUE_TYPE_DYNAMIC_STRING && program->stackPointer >= 4) { + v5 = stackReadInt32(program->returnStack, program->returnStackPointer - 4); + programPopString(program, type, v5); + } + + return type; +} + +// 0x4675B8 +int programReturnStackPopInt32(Program* program) +{ + return stackPopInt32(program->returnStack, &(program->returnStackPointer)); +} + +// NOTE: Inlined. +// +// 0x4675C8 +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 +void _purgeProgram(Program* program) +{ + if (!program->exited) { + _removeProgramReferences_(program); + program->exited = true; + } +} + +// 0x467614 +void programFree(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) { + internal_free_safe(program->dynamicStrings, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 429 + } + + if (program->data != NULL) { + internal_free_safe(program->data, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 430 + } + + if (program->name != NULL) { + internal_free_safe(program->name, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 431 + } + + if (program->stack != NULL) { + internal_free_safe(program->stack, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 432 + } + + if (program->returnStack != NULL) { + internal_free_safe(program->returnStack, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 433 + } + + internal_free_safe(program, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 435 +} + +// 0x467734 +Program* programCreateByPath(const char* path) +{ + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + char err[260]; + sprintf(err, "Couldn't open %s for read\n", path); + programFatalError(err); + return NULL; + } + + int fileSize = fileGetSize(stream); + unsigned char* data = internal_malloc_safe(fileSize, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 458 + + fileRead(data, 1, fileSize, stream); + fileClose(stream); + + Program* program = internal_malloc_safe(sizeof(Program), __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 463 + memset(program, 0, sizeof(Program)); + + program->name = internal_malloc_safe(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 = internal_calloc_safe(1, 4096, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 472 + program->exited = false; + program->basePointer = -1; + program->framePointer = -1; + program->returnStack = internal_calloc_safe(1, 4096, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 473 + program->data = data; + program->procedures = data + 42; + program->identifiers = 24 * stackReadInt32(program->procedures, 0) + program->procedures + 4; + program->staticStrings = program->identifiers + stackReadInt32(program->identifiers, 0) + 4; + + return program; +} + +// 0x4678E0 +char* programGetString(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* programGetIdentifier(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 +void programMarkHeap(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 { + debugPrint("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 programPushString(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 = internal_malloc_safe(8, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 631 + *(int*)(program->dynamicStrings) = 0; + *(short*)(program->dynamicStrings + 4) = 0x8000; + *(short*)(program->dynamicStrings + 6) = 1; + } + + program->dynamicStrings = internal_realloc_safe(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) { + programFatalError("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'; + *(short*)(v23 + 4) = 0x8000; + *(short*)(v23 + 6) = 1; + + return v20 + 4 - (program->dynamicStrings + 4); +} + +// 0x467C90 +void opNoop(Program* program) +{ +} + +// 0x467C94 +void opPush(Program* program) +{ + int pos = program->instructionPointer; + program->instructionPointer = pos + 4; + + int value = stackReadInt32(program->data, pos); + stackPushInt32(program->stack, &(program->stackPointer), value); + programStackPushInt16(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 +void opPushBase(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int value = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, value); + } + + stackPushInt32(program->returnStack, &(program->returnStackPointer), program->framePointer); + programReturnStackPushInt16(program, VALUE_TYPE_INT); + + program->framePointer = program->stackPointer - 6 * value; +} + +// pop_base +// 0x467D3C +void opPopBase(Program* program) +{ + opcode_t opcode = programReturnStackPopInt16(program); + int data = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + if (opcode != VALUE_TYPE_INT) { + char err[260]; + sprintf(err, "Invalid type given to pop_base: %x", opcode); + programFatalError(err); + } + + program->framePointer = data; +} + +// 0x467D94 +void opPopToBase(Program* program) +{ + while (program->stackPointer != program->framePointer) { + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + } +} + +// 0x467DE0 +void op802C(Program* program) +{ + program->basePointer = program->stackPointer; +} + +// 0x467DEC +void opDump(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (opcode != VALUE_TYPE_INT) { + char err[256]; + sprintf(err, "Invalid type given to dump, %x", opcode); + programFatalError(err); + } + + // NOTE: Original code is slightly different - it goes backwards to -1. + for (int index = 0; index < data; index++) { + opcode = stackPopInt16(program->stack, &(program->stackPointer)); + data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + } +} + +// 0x467EA4 +void opDelayedCall(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = stackPopInt16(program->stack, &(program->stackPointer)); + data[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if (arg == 0) { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure type given to call"); + } + } else if (arg == 1) { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid time given to call"); + } + } + } + + unsigned char* procedure_ptr = program->procedures + 4 + 24 * data[0]; + + int delay = 1000 * data[1]; + + if (!_suspendEvents) { + delay += 1000 * _timerFunc() / _timerTick; + } + + int flags = stackReadInt32(procedure_ptr, 4); + + stackWriteInt32(delay, procedure_ptr, 8); + stackWriteInt32(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, 4); +} + +// 0x468034 +void opConditionalCall(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = stackPopInt16(program->stack, &(program->stackPointer)); + data[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure type given to conditional call"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid address given to conditional call"); + } + + unsigned char* procedure_ptr = program->procedures + 4 + 24 * data[0]; + int flags = stackReadInt32(procedure_ptr, 4); + + stackWriteInt32(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, 4); + stackWriteInt32(data[1], procedure_ptr, 12); +} + +// 0x46817C +void opWait(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid type given to wait\n"); + } + + program->field_74 = 1000 * _timerFunc() / _timerTick; + program->field_70 = program->field_74 + data; + program->field_7C = _checkWait; + program->flags |= PROGRAM_FLAG_0x10; +} + +// 0x468218 +void opCancel(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("invalid type given to cancel"); + } + + if (data >= stackReadInt32(program->procedures, 0)) { + programFatalError("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 +void opCancelAll(Program* program) +{ + int procedureCount = stackReadInt32(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 +void opIf(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (data) { + opcode = stackPopInt16(program->stack, &(program->stackPointer)); + data = stackPopInt32(program->stack, &(program->stackPointer)); + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + } else { + opcode = stackPopInt16(program->stack, &(program->stackPointer)); + data = stackPopInt32(program->stack, &(program->stackPointer)); + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + program->instructionPointer = data; + } +} + +// 0x4684A4 +void opWhile(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (data == 0) { + opcode = stackPopInt16(program->stack, &(program->stackPointer)); + data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + program->instructionPointer = data; + } +} + +// 0x468518 +void opStore(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] = stackPopInt16(program->stack, &(program->stackPointer)); + data[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = stackReadInt16(program->stack, var_address + 4); + int var_value = stackReadInt32(program->stack, var_address); + + if (var_type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, var_type, var_value); + } + + // TODO: Original code is different, check. + stackWriteInt32(data[1], program->stack, var_address); + + stackWriteInt16(opcode[1], program->stack, var_address + 4); + + if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { + // increment ref count + *(short*)(program->dynamicStrings + 4 - 2 + data[1]) += 1; + } +} + +// fetch +// 0x468678 +void opFetch(Program* program) +{ + char err[256]; + + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (opcode != VALUE_TYPE_INT) { + sprintf(err, "Invalid type given to fetch, %x", opcode); + programFatalError(err); + } + + // NOTE: original code is a bit different + int variableAddress = program->framePointer + 6 * data; + int variableType = stackReadInt16(program->stack, variableAddress + 4); + int variableValue = stackReadInt32(program->stack, variableAddress); + programStackPushInt32(program, variableValue); + programStackPushInt16(program, variableType); +} + +// 0x46873C +void opConditionalOperatorNotEqual(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] = stackPopInt16(program->stack, &(program->stackPointer)); + data[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + switch (opcode[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, opcode[1], data[1]); + + switch (opcode[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x468AA8 +void opConditionalOperatorEqual(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[arg], value[arg]); + } + } + + switch (type[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, type[1], value[1]); + + switch (type[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x468E14 +void opConditionalOperatorLessThanEquals(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[arg], value[arg]); + } + } + + switch (type[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, type[1], value[1]); + + switch (type[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x469180 +void opConditionalOperatorGreaterThanEquals(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[arg], value[arg]); + } + } + + switch (type[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, type[1], value[1]); + + switch (type[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x4694EC +void opConditionalOperatorLessThan(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] = stackPopInt16(program->stack, &(program->stackPointer)); + values[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcodes[arg], values[arg]); + } + } + + switch (opcodes[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, opcodes[1], values[1]); + + switch (opcodes[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x469858 +void opConditionalOperatorGreaterThan(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[arg], value[arg]); + } + } + + switch (type[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, type[1], value[1]); + + switch (type[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[0] = programGetString(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] = programGetString(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] = programGetString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), res); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x469BC4 +void opAdd(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] = stackPopInt16(program->stack, &(program->stackPointer)); + values[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcodes[arg], values[arg]); + } + } + + switch (opcodes[1]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + str_ptr[1] = programGetString(program, opcodes[1], values[1]); + + switch (opcodes[0]) { + case VALUE_TYPE_STRING: + case VALUE_TYPE_DYNAMIC_STRING: + t = programGetString(program, opcodes[0], values[0]); + str_ptr[0] = internal_malloc_safe(strlen(t) + 1, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1002 + strcpy(str_ptr[0], t); + break; + case VALUE_TYPE_FLOAT: + str_ptr[0] = internal_malloc_safe(80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1011 + sprintf(str_ptr[0], "%.5f", floats[0]); + break; + case VALUE_TYPE_INT: + str_ptr[0] = internal_malloc_safe(80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1007 + sprintf(str_ptr[0], "%d", values[0]); + break; + } + + t = internal_malloc_safe(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]); + + stackPushInt32(program->stack, &(program->stackPointer), programPushString(program, t)); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); + + internal_free_safe(str_ptr[0], __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1019 + internal_free_safe(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] = programGetString(program, opcodes[0], values[0]); + t = internal_malloc_safe(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1039 + sprintf(t, "%.5f", floats[1]); + strcat(t, str_ptr[0]); + + stackPushInt32(program->stack, &(program->stackPointer), programPushString(program, t)); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); + + internal_free_safe(t, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1044 + break; + case VALUE_TYPE_FLOAT: + resf = floats[1] + floats[0]; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + case VALUE_TYPE_INT: + resf = floats[1] + (float)values[0]; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(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] = programGetString(program, opcodes[0], values[0]); + t = internal_malloc_safe(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1070 + sprintf(t, "%d", values[1]); + strcat(t, str_ptr[0]); + + stackPushInt32(program->stack, &(program->stackPointer), programPushString(program, t)); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); + + internal_free_safe(t, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1075 + break; + case VALUE_TYPE_FLOAT: + resf = (float)values[1] + floats[0]; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(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])) { + stackPushInt32(program->stack, &(program->stackPointer), values[1] + values[0]); + programStackPushInt16(program, VALUE_TYPE_INT); + } else { + resf = (float)values[1] + (float)values[0]; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + } + break; + } + break; + } +} + +// 0x46A1D8 +void opSubtract(Program* program) +{ + opcode_t type[2]; + int value[2]; + float* floats = (float*)&value; + float resf; + + for (int arg = 0; arg < 2; arg++) { + type[arg] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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; + } + + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + case VALUE_TYPE_INT: + switch (type[0]) { + case VALUE_TYPE_FLOAT: + resf = value[1] - floats[0]; + + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + default: + stackPushInt32(program->stack, &(program->stackPointer), value[1] - value[0]); + programStackPushInt16(program, VALUE_TYPE_INT); + break; + } + break; + } +} + +// 0x46A300 +void opMultiply(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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; + } + + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + case VALUE_TYPE_INT: + switch (type[0]) { + case VALUE_TYPE_FLOAT: + resf = value[1] * floats[0]; + + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&resf); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + default: + stackPushInt32(program->stack, &(program->stackPointer), value[0] * value[1]); + programStackPushInt16(program, VALUE_TYPE_INT); + break; + } + break; + } +} + +// 0x46A424 +void opDivide(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] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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]; + } + + if ((int)divisor & 0x7FFFFFFF) { + programFatalError("Division (DIV) by zero"); + } + + result = float_value[1] / divisor; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&result); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + break; + case VALUE_TYPE_INT: + if (type[0] == VALUE_TYPE_FLOAT) { + divisor = float_value[0]; + + if ((int)divisor & 0x7FFFFFFF) { + programFatalError("Division (DIV) by zero"); + } + + result = (float)value[1] / divisor; + stackPushInt32(program->stack, &(program->stackPointer), *(int*)&result); + programStackPushInt16(program, VALUE_TYPE_FLOAT); + } else { + if (value[0] == 0) { + programFatalError("Division (DIV) by zero"); + } + + stackPushInt32(program->stack, &(program->stackPointer), value[1] / value[0]); + programStackPushInt16(program, VALUE_TYPE_INT); + } + break; + } +} + +// 0x46A5B8 +void opModulo(Program* program) +{ + opcode_t type[2]; + int value[2]; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[1], value[1]); + } + + if (type[1] == VALUE_TYPE_FLOAT) { + programFatalError("Trying to MOD a float"); + } + + if (type[1] != VALUE_TYPE_INT) { + return; + } + + if (type[0] == VALUE_TYPE_FLOAT) { + programFatalError("Trying to MOD with a float"); + } + + if (value[0] == 0) { + programFatalError("Division (MOD) by zero"); + } + + stackPushInt32(program->stack, &(program->stackPointer), value[1] % value[0]); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46A6B4 +void opLogicalOperatorAnd(Program* program) +{ + opcode_t type[2]; + int value[2]; + int result; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46A8D8 +void opLogicalOperatorOr(Program* program) +{ + opcode_t type[2]; + int value[2]; + int result; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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"); + } + + stackPushInt32(program->stack, &(program->stackPointer), result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46AACC +void opLogicalOperatorNot(Program* program) +{ + opcode_t type; + int value; + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + programStackPushInt32(program, value == 0); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46AB2C +void opUnaryMinus(Program* program) +{ + opcode_t type; + int value; + + type = stackPopInt16(program->stack, &(program->stackPointer)); + value = stackPopInt32(program->stack, &(program->stackPointer)); + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + stackPushInt32(program->stack, &(program->stackPointer), -value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46AB84 +void opBitwiseOperatorNot(Program* program) +{ + opcode_t type; + int value; + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + programStackPushInt32(program, ~value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// floor +// 0x46ABDC +void opFloor(Program* program) +{ + opcode_t type = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, data); + } + + if (type == VALUE_TYPE_STRING) { + programFatalError("Invalid arg given to floor()"); + } else if (type == VALUE_TYPE_FLOAT) { + type = VALUE_TYPE_INT; + data = (int)(*((float*)&data)); + } + + stackPushInt32(program->stack, &(program->stackPointer), data); + programStackPushInt16(program, type); +} + +// 0x46AC78 +void opBitwiseOperatorAnd(Program* program) +{ + opcode_t type[2]; + int value[2]; + int result; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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; + } + + stackPushInt32(program->stack, &(program->stackPointer), result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46ADA4 +void opBitwiseOperatorOr(Program* program) +{ + opcode_t type[2]; + int value[2]; + int result; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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; + } + + stackPushInt32(program->stack, &(program->stackPointer), result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46AED0 +void opBitwiseOperatorXor(Program* program) +{ + opcode_t type[2]; + int value[2]; + int result; + + type[0] = stackPopInt16(program->stack, &(program->stackPointer)); + value[0] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[0], value[0]); + } + + type[1] = stackPopInt16(program->stack, &(program->stackPointer)); + value[1] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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; + } + + stackPushInt32(program->stack, &(program->stackPointer), result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x46AFFC +void opSwapReturnStack(Program* program) +{ + opcode_t v1; + int v5; + opcode_t a2; + int v10; + + v1 = programReturnStackPopInt16(program); + v5 = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + a2 = programReturnStackPopInt16(program); + v10 = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + stackPushInt32(program->returnStack, &(program->returnStackPointer), v5); + programReturnStackPushInt16(program, v1); + + stackPushInt32(program->returnStack, &(program->returnStackPointer), v10); + programReturnStackPushInt16(program, a2); +} + +// 0x46B070 +void opLeaveCriticalSection(Program* program) +{ + program->flags &= ~PROGRAM_FLAG_CRITICAL_SECTION; +} + +// 0x46B078 +void opEnterCriticalSection(Program* program) +{ + program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; +} + +// 0x46B080 +void opJump(Program* program) +{ + opcode_t type; + int value; + char err[260]; + + type = stackPopInt16(program->stack, &(program->stackPointer)); + value = stackPopInt32(program->stack, &(program->stackPointer)); + + // NOTE: comparing shorts (0x46B0B1) + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + // NOTE: comparing ints (0x46B0D3) + if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + sprintf(err, "Invalid type given to jmp, %x", value); + programFatalError(err); + } + + program->instructionPointer = value; +} + +// 0x46B108 +void opCall(Program* program) +{ + opcode_t type = stackPopInt16(program->stack, &(program->stackPointer)); + int value = stackPopInt32(program->stack, &(program->stackPointer)); + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + if ((type & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid address given to call"); + } + + unsigned char* ptr = program->procedures + 4 + 24 * value; + + int flags = stackReadInt32(ptr, 4); + if ((flags & 4) != 0) { + // TODO: Incomplete. + } else { + program->instructionPointer = stackReadInt32(ptr, 16); + if ((flags & 0x10) != 0) { + program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; + } + } +} + +// 0x46B590 +void op801F(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = stackPopInt16(program->stack, &(program->stackPointer)); + data[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + program->field_84 = data[0]; + program->field_7C = (int (*)(Program*))data[1]; + program->flags = data[2] & 0xFFFF; +} + +// pop stack 2 -> set program address +// 0x46B63C +void op801C(Program* program) +{ + programReturnStackPopInt16(program); + program->instructionPointer = stackPopInt32(program->returnStack, &(program->returnStackPointer)); +} + +// 0x46B658 +void op801D(Program* program) +{ + programReturnStackPopInt16(program); + program->instructionPointer = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + program->flags |= PROGRAM_FLAG_0x40; +} + +// 0x46B67C +void op8020(Program* program) +{ + op801F(program); + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); +} + +// 0x46B698 +void op8021(Program* program) +{ + op801F(program); + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); + program->flags |= PROGRAM_FLAG_0x40; +} + +// 0x46B6BC +void op8025(Program* program) +{ + opcode_t type; + int value; + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + op801F(program); + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); + program->flags |= PROGRAM_FLAG_0x40; + programStackPushInt32(program, value); + programStackPushInt16(program, type); +} + +// 0x46B73C +void op8026(Program* program) +{ + opcode_t type; + int value; + Program* v1; + + type = stackPopInt16(program->stack, &(program->stackPointer)); + value = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + op801F(program); + + programReturnStackPopInt16(program); + v1 = (Program*)stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->field_7C = (int (*)(Program*))stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->flags = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + program->instructionPointer = programReturnStackPopInt32(program); + + program->flags |= PROGRAM_FLAG_0x40; + + stackPushInt32(program->stack, &(program->stackPointer), value); + programStackPushInt16(program, type); +} + +// 0x46B808 +void op8022(Program* program) +{ + Program* v1; + + op801F(program); + + programReturnStackPopInt16(program); + v1 = (Program*)stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->field_7C = (int (*)(Program*))stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->flags = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); +} + +// 0x46B86C +void op8023(Program* program) +{ + Program* v1; + + op801F(program); + + programReturnStackPopInt16(program); + v1 = (Program*)stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->field_7C = (int (*)(Program*))stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v1->flags = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); + + program->flags |= 0x40; +} + +// pop value from stack 1 and push it to script popped from stack 2 +// 0x46B8D8 +void op8024(Program* program) +{ + opcode_t type; + int value; + Program* v10; + char* str; + + type = stackPopInt16(program->stack, &(program->stackPointer)); + value = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + op801F(program); + + programReturnStackPopInt16(program); + v10 = (Program*)stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v10->field_7C = (int (*)(Program*))stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + programReturnStackPopInt16(program); + v10->flags = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + if ((type & 0xF7FF) == VALUE_TYPE_STRING) { + str = programGetString(program, type, value); + stackPushInt32(v10->stack, &(v10->stackPointer), programPushString(v10, str)); + type = VALUE_TYPE_DYNAMIC_STRING; + } else { + stackPushInt32(v10->stack, &(v10->stackPointer), value); + } + + programStackPushInt16(v10, type); + + if (v10->flags & 0x80) { + program->flags &= ~0x80; + } + + programReturnStackPopInt16(program); + program->instructionPointer = programReturnStackPopInt32(program); + + programReturnStackPopInt16(v10); + v10->instructionPointer = programReturnStackPopInt32(program); +} + +// 0x46BA10 +void op801E(Program* program) +{ + programReturnStackPopInt16(program); + programReturnStackPopInt32(program); +} + +// 0x46BA2C +void opAtoD(Program* program) +{ + opcode_t opcode = programReturnStackPopInt16(program); + int data = stackPopInt32(program->returnStack, &(program->returnStackPointer)); + + stackPushInt32(program->stack, &(program->stackPointer), data); + programStackPushInt16(program, opcode); +} + +// 0x46BA68 +void opDtoA(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + stackPushInt32(program->returnStack, &(program->returnStackPointer), data); + programReturnStackPushInt16(program, opcode); +} + +// 0x46BAC0 +void opExitProgram(Program* program) +{ + program->flags |= PROGRAM_FLAG_EXITED; +} + +// 0x46BAC8 +void opStopProgram(Program* program) +{ + program->flags |= PROGRAM_FLAG_STOPPED; +} + +// 0x46BAD0 +void opFetchGlobalVariable(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + // TODO: Check. + int addr = program->basePointer + 6 * data; + int v8 = stackReadInt32(program->stack, addr); + opcode_t varType = stackReadInt16(program->stack, addr + 4); + + programStackPushInt32(program, v8); + // TODO: Check. + programStackPushInt16(program, varType); +} + +// 0x46BB5C +void opStoreGlobalVariable(Program* program) +{ + opcode_t type[2]; + int value[2]; + + for (int arg = 0; arg < 2; arg++) { + type[arg] = stackPopInt16(program->stack, &(program->stackPointer)); + value[arg] = stackPopInt32(program->stack, &(program->stackPointer)); + + if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type[arg], value[arg]); + } + } + + int var_address = program->basePointer + 6 * value[0]; + + opcode_t var_type = stackReadInt16(program->stack, var_address + 4); + int var_value = stackReadInt32(program->stack, var_address); + + if (var_type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, var_type, var_value); + } + + // TODO: Check offsets. + stackWriteInt32(value[1], program->stack, var_address); + + // TODO: Check endianness. + stackWriteInt16(type[1], program->stack, var_address + 4); + + if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { + *(short*)(program->dynamicStrings + 4 + value[1] - 2) += 1; + } +} + +// 0x46BCAC +void opSwapStack(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 2; arg++) { + programStackPushInt32(program, data[arg]); + programStackPushInt16(program, opcode[arg]); + } +} + +// fetch_proc_address +// 0x46BD60 +void opFetchProcedureAddress(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (opcode != VALUE_TYPE_INT) { + char err[256]; + sprintf(err, "Invalid type given to fetch_proc_address, %x", opcode); + programFatalError(err); + } + + int procedureIndex = data; + + int address = stackReadInt32(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 16); + stackPushInt32(program->stack, &(program->stackPointer), address); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// Pops value from stack and throws it away. +// +// 0x46BE10 +void opPop(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } +} + +// 0x46BE4C +void opDuplicate(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + programStackPushInt32(program, data); + programStackPushInt16(program, opcode); + + programStackPushInt32(program, data); + programStackPushInt16(program, opcode); +} + +// 0x46BEC8 +void opStoreExternalVariable(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + const char* identifier = programGetIdentifier(program, data[0]); + + if (externalVariableSetValue(program, identifier, opcode[1], data[1])) { + char err[256]; + sprintf(err, "External variable %s does not exist\n", identifier); + programFatalError(err); + } +} + +// 0x46BF90 +void opFetchExternalVariable(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + const char* identifier = programGetIdentifier(program, data); + + opcode_t variableOpcode; + int variableData; + if (externalVariableGetValue(program, identifier, &variableOpcode, &variableData) != 0) { + char err[256]; + sprintf(err, "External variable %s does not exist\n", identifier); + programFatalError(err); + } + + programStackPushInt32(program, variableData); + programStackPushInt16(program, variableOpcode); +} + +// 0x46C044 +void opExportProcedure(Program* program) +{ + opcode_t type; + int value; + int proc_index; + unsigned char* proc_ptr; + char* v9; + int v10; + char err[256]; + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + proc_index = value; + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + proc_ptr = program->procedures + 4 + sizeof(Procedure) * proc_index; + + v9 = (char*)(program->identifiers + stackReadInt32(proc_ptr, 0)); + v10 = stackReadInt32(proc_ptr, 16); + + if (externalProcedureCreate(program, v9, v10, value) != 0) { + sprintf(err, "Error exporting procedure %s", v9); + programFatalError(err); + } +} + +// 0x46C120 +void opExportVariable(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (externalVariableCreate(program, programGetIdentifier(program, data))) { + char err[256]; + sprintf(err, "External variable %s already exists", programGetIdentifier(program, data)); + programFatalError(err); + } +} + +// 0x46C1A0 +void opExit(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 +void opDetach(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 +void opCallStart(Program* program) +{ + opcode_t type; + int value; + char* name; + char err[260]; + + if (program->child) { + programFatalError("Error, already have a child process\n"); + } + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + if ((type & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid type given to callstart"); + } + + program->flags |= PROGRAM_FLAG_0x20; + + name = programGetString(program, type, value); + + name = _interpretMangleName(name); + program->child = programCreateByPath(name); + if (program->child == NULL) { + sprintf(err, "Error spawning child %s", programGetString(program, type, value)); + programFatalError(err); + } + + programListNodeCreate(program->child); + _interpret(program->child, 24); + + program->child->parent = program; + program->child->field_84 = program->field_84; +} + +// spawn +// 0x46C344 +void opSpawn(Program* program) +{ + opcode_t type; + int value; + char* name; + char err[256]; + + if (program->child) { + programFatalError("Error, already have a child process\n"); + } + + type = programStackPopInt16(program); + value = programStackPopInt32(program); + + if (type == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, type, value); + } + + if ((type & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("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; + } + + name = _interpretMangleName(name); + program->child = programCreateByPath(name); + if (program->child == NULL) { + sprintf(err, "Error spawning child %s", programGetString(program, type, value)); + programFatalError(err); + } + + programListNodeCreate(program->child); + _interpret(program->child, 24); + + program->child->parent = program; + program->child->field_84 = program->field_84; + + if ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0) { + program->child->flags |= PROGRAM_FLAG_CRITICAL_SECTION; + _interpret(program->child, -1); + } +} + +// fork +// 0x46C490 +Program* forkProgram(Program* program) +{ + opcode_t opcode = stackPopInt16(program->stack, &(program->stackPointer)); + int data = stackPopInt32(program->stack, &(program->stackPointer)); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + char* name = programGetString(program, opcode, data); + name = _interpretMangleName(name); + Program* forked = programCreateByPath(name); + + if (forked == NULL) { + char err[256]; + sprintf(err, "couldn't fork script '%s'", programGetString(program, opcode, data)); + programFatalError(err); + } + + programListNodeCreate(forked); + + _interpret(forked, 24); + + forked->field_84 = program->field_84; + + return forked; +} + +// NOTE: Uncollapsed 0x46C490 with different signature. +// +// 0x46C490 +void opFork(Program* program) +{ + forkProgram(program); +} + +// 0x46C574 +void opExec(Program* program) +{ + Program* parent = program->parent; + Program* fork = forkProgram(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 +void opCheckProcedureArgumentCount(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + int expectedArgumentCount = data[0]; + int procedureIndex = data[1]; + + int actualArgumentCount = stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 20); + if (actualArgumentCount != expectedArgumentCount) { + const char* identifier = programGetIdentifier(program, stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 0)); + char err[260]; + sprintf(err, "Wrong number of args to procedure %s\n", identifier); + programFatalError(err); + } +} + +// lookup_string_proc +// 0x46C6B4 +void opLookupStringProc(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Wrong type given to lookup_string_proc\n"); + } + + const char* procedureNameToLookup = programGetString(program, opcode, data); + + int procedureCount = stackReadInt32(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 = stackReadInt32(procedurePtr, 0); + const char* procedureName = programGetIdentifier(program, offset); + if (stricmp(procedureName, procedureNameToLookup) == 0) { + programStackPushInt32(program, index); + programStackPushInt16(program, VALUE_TYPE_INT); + return; + } + + procedurePtr += sizeof(Procedure); + } + + char err[260]; + sprintf(err, "Couldn't find string procedure %s\n", procedureNameToLookup); + programFatalError(err); +} + +// 0x46C7DC +void interpreterRegisterOpcodeHandlers() +{ + _Enabled = 1; + + // NOTE: The original code has different sorting. + interpreterRegisterOpcode(OPCODE_NOOP, opNoop); + interpreterRegisterOpcode(OPCODE_PUSH, opPush); + interpreterRegisterOpcode(OPCODE_ENTER_CRITICAL_SECTION, opEnterCriticalSection); + interpreterRegisterOpcode(OPCODE_LEAVE_CRITICAL_SECTION, opLeaveCriticalSection); + interpreterRegisterOpcode(OPCODE_JUMP, opJump); + interpreterRegisterOpcode(OPCODE_CALL, opCall); + interpreterRegisterOpcode(OPCODE_CALL_AT, opDelayedCall); + interpreterRegisterOpcode(OPCODE_CALL_WHEN, opConditionalCall); + interpreterRegisterOpcode(OPCODE_CALLSTART, opCallStart); + interpreterRegisterOpcode(OPCODE_EXEC, opExec); + interpreterRegisterOpcode(OPCODE_SPAWN, opSpawn); + interpreterRegisterOpcode(OPCODE_FORK, opFork); + interpreterRegisterOpcode(OPCODE_A_TO_D, opAtoD); + interpreterRegisterOpcode(OPCODE_D_TO_A, opDtoA); + interpreterRegisterOpcode(OPCODE_EXIT, opExit); + interpreterRegisterOpcode(OPCODE_DETACH, opDetach); + interpreterRegisterOpcode(OPCODE_EXIT_PROGRAM, opExitProgram); + interpreterRegisterOpcode(OPCODE_STOP_PROGRAM, opStopProgram); + interpreterRegisterOpcode(OPCODE_FETCH_GLOBAL, opFetchGlobalVariable); + interpreterRegisterOpcode(OPCODE_STORE_GLOBAL, opStoreGlobalVariable); + interpreterRegisterOpcode(OPCODE_FETCH_EXTERNAL, opFetchExternalVariable); + interpreterRegisterOpcode(OPCODE_STORE_EXTERNAL, opStoreExternalVariable); + interpreterRegisterOpcode(OPCODE_EXPORT_VARIABLE, opExportVariable); + interpreterRegisterOpcode(OPCODE_EXPORT_PROCEDURE, opExportProcedure); + interpreterRegisterOpcode(OPCODE_SWAP, opSwapStack); + interpreterRegisterOpcode(OPCODE_SWAPA, opSwapReturnStack); + interpreterRegisterOpcode(OPCODE_POP, opPop); + interpreterRegisterOpcode(OPCODE_DUP, opDuplicate); + interpreterRegisterOpcode(OPCODE_POP_RETURN, op801C); + interpreterRegisterOpcode(OPCODE_POP_EXIT, op801D); + interpreterRegisterOpcode(OPCODE_POP_ADDRESS, op801E); + interpreterRegisterOpcode(OPCODE_POP_FLAGS, op801F); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN, op8020); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT, op8021); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_EXTERN, op8022); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT_EXTERN, op8023); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXTERN, op8024); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT, op8025); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN, op8026); + interpreterRegisterOpcode(OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT, opCheckProcedureArgumentCount); + interpreterRegisterOpcode(OPCODE_LOOKUP_PROCEDURE_BY_NAME, opLookupStringProc); + interpreterRegisterOpcode(OPCODE_POP_BASE, opPopBase); + interpreterRegisterOpcode(OPCODE_POP_TO_BASE, opPopToBase); + interpreterRegisterOpcode(OPCODE_PUSH_BASE, opPushBase); + interpreterRegisterOpcode(OPCODE_SET_GLOBAL, op802C); + interpreterRegisterOpcode(OPCODE_FETCH_PROCEDURE_ADDRESS, opFetchProcedureAddress); + interpreterRegisterOpcode(OPCODE_DUMP, opDump); + interpreterRegisterOpcode(OPCODE_IF, opIf); + interpreterRegisterOpcode(OPCODE_WHILE, opWhile); + interpreterRegisterOpcode(OPCODE_STORE, opStore); + interpreterRegisterOpcode(OPCODE_FETCH, opFetch); + interpreterRegisterOpcode(OPCODE_EQUAL, opConditionalOperatorEqual); + interpreterRegisterOpcode(OPCODE_NOT_EQUAL, opConditionalOperatorNotEqual); + interpreterRegisterOpcode(OPCODE_LESS_THAN_EQUAL, opConditionalOperatorLessThanEquals); + interpreterRegisterOpcode(OPCODE_GREATER_THAN_EQUAL, opConditionalOperatorGreaterThanEquals); + interpreterRegisterOpcode(OPCODE_LESS_THAN, opConditionalOperatorLessThan); + interpreterRegisterOpcode(OPCODE_GREATER_THAN, opConditionalOperatorGreaterThan); + interpreterRegisterOpcode(OPCODE_ADD, opAdd); + interpreterRegisterOpcode(OPCODE_SUB, opSubtract); + interpreterRegisterOpcode(OPCODE_MUL, opMultiply); + interpreterRegisterOpcode(OPCODE_DIV, opDivide); + interpreterRegisterOpcode(OPCODE_MOD, opModulo); + interpreterRegisterOpcode(OPCODE_AND, opLogicalOperatorAnd); + interpreterRegisterOpcode(OPCODE_OR, opLogicalOperatorOr); + interpreterRegisterOpcode(OPCODE_BITWISE_AND, opBitwiseOperatorAnd); + interpreterRegisterOpcode(OPCODE_BITWISE_OR, opBitwiseOperatorOr); + interpreterRegisterOpcode(OPCODE_BITWISE_XOR, opBitwiseOperatorXor); + interpreterRegisterOpcode(OPCODE_BITWISE_NOT, opBitwiseOperatorNot); + interpreterRegisterOpcode(OPCODE_FLOOR, opFloor); + interpreterRegisterOpcode(OPCODE_NOT, opLogicalOperatorNot); + interpreterRegisterOpcode(OPCODE_NEGATE, opUnaryMinus); + interpreterRegisterOpcode(OPCODE_WAIT, opWait); + interpreterRegisterOpcode(OPCODE_CANCEL, opCancel); + interpreterRegisterOpcode(OPCODE_CANCEL_ALL, opCancelAll); + interpreterRegisterOpcode(OPCODE_START_CRITICAL, opEnterCriticalSection); + interpreterRegisterOpcode(OPCODE_END_CRITICAL, opLeaveCriticalSection); + + _initIntlib(); + _initExport(); +} + +// 0x46CC68 +void _interpretClose() +{ + externalVariablesClear(); + _intlibClose(); +} + +// 0x46CCA4 +void _interpret(Program* program, int a2) +{ + char err[260]; + + Program* oldCurrentProgram = gInterpreterCurrentProgram; + + 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; + } + + gInterpreterCurrentProgram = program; + + if (setjmp(program->env)) { + gInterpreterCurrentProgram = 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_FLAG_0x10) != 0) { + _busy = 1; + + if (program->field_7C != NULL) { + if (!program->field_7C(program)) { + _busy = 0; + continue; + } + } + + _busy = 0; + program->field_7C = NULL; + program->flags &= ~PROGRAM_FLAG_0x10; + } + + int instructionPointer = program->instructionPointer; + program->instructionPointer = instructionPointer + 2; + + opcode_t opcode = stackReadInt16(program->data, instructionPointer); + // 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); + programFatalError(err); + } + + unsigned int opcodeIndex = opcode & 0x3FF; + OpcodeHandler* handler = gInterpreterOpcodeHandlers[opcodeIndex]; + if (handler == NULL) { + sprintf(err, "Undefined opcode %x.", opcode); + programFatalError(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; + gInterpreterCurrentProgram = oldCurrentProgram; + + programMarkHeap(program); +} + +// Prepares program stacks for executing proc at [address]. +// +// 0x46CED0 +void _setupCallWithReturnVal(Program* program, int address, int returnAddress) +{ + // Save current instruction pointer + stackPushInt32(program->returnStack, &(program->returnStackPointer), program->instructionPointer); + programReturnStackPushInt16(program, VALUE_TYPE_INT); + + // Save return address + stackPushInt32(program->returnStack, &(program->returnStackPointer), returnAddress); + programReturnStackPushInt16(program, VALUE_TYPE_INT); + + // Save program flags + stackPushInt32(program->stack, &(program->stackPointer), program->flags & 0xFFFF); + programStackPushInt16(program, VALUE_TYPE_INT); + + stackPushInt32(program->stack, &(program->stackPointer), (intptr_t)program->field_7C); + programStackPushInt16(program, VALUE_TYPE_INT); + + stackPushInt32(program->stack, &(program->stackPointer), program->field_84); + programStackPushInt16(program, VALUE_TYPE_INT); + + program->flags &= ~0xFFFF; + program->instructionPointer = address; +} + +// 0x46CF9C +void _setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4) +{ + stackPushInt32(program2->returnStack, &(program2->returnStackPointer), program2->instructionPointer); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->returnStack, &(program2->returnStackPointer), program1->flags & 0xFFFF); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1->field_7C); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->returnStack, &(program2->returnStackPointer), a4); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->stack, &(program2->stackPointer), program2->flags & 0xFFFF); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->stack, &(program2->stackPointer), (intptr_t)program2->field_7C); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + stackPushInt32(program2->stack, &(program2->stackPointer), program2->field_84); + programReturnStackPushInt16(program2, VALUE_TYPE_INT); + + program2->flags &= ~0xFFFF; + program2->instructionPointer = address; + program2->field_84 = program1->field_84; + + program1->flags |= PROGRAM_FLAG_0x20; +} + +// 0x46DB58 +void _executeProc(Program* program, int procedure_index) +{ + Program* external_program; + char* identifier; + int address; + int arguments_count; + unsigned char* procedure_ptr; + int flags; + char err[256]; + Program* v12; + + procedure_ptr = program->procedures + 4 + 24 * procedure_index; + flags = stackReadInt32(procedure_ptr, 4); + if (!(flags & PROCEDURE_FLAG_IMPORTED)) { + address = stackReadInt32(procedure_ptr, 16); + + _setupCallWithReturnVal(program, address, 20); + + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); + + if (!(flags & PROCEDURE_FLAG_CRITICAL)) { + return; + } + + program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; + v12 = program; + } else { + identifier = programGetIdentifier(program, stackReadInt32(procedure_ptr, 0)); + external_program = externalProcedureGetProgram(identifier, &address, &arguments_count); + if (external_program == NULL) { + sprintf(err, "External procedure %s not found\n", identifier); + // TODO: Incomplete. + // _interpretOutput(err); + return; + } + + if (arguments_count != 0) { + sprintf(err, "External procedure cannot take arguments in interrupt context"); + // TODO: Incomplete. + // _interpretOutput(err); + return; + } + + _setupExternalCallWithReturnVal(program, external_program, address, 28); + + programStackPushInt32(external_program, 0); + programStackPushInt16(external_program, VALUE_TYPE_INT); + + procedure_ptr = external_program->procedures + 4 + 24 * procedure_index; + flags = stackReadInt32(procedure_ptr, 4); + + if (!(flags & PROCEDURE_FLAG_CRITICAL)) { + return; + } + + external_program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; + v12 = external_program; + } + + _interpret(v12, 0); +} + +// Returns index of the procedure with specified name or -1 if no such +// procedure exists. +// +// 0x46DCD0 +int programFindProcedure(Program* program, const char* name) +{ + int procedureCount = stackReadInt32(program->procedures, 0); + + unsigned char* ptr = program->procedures + 4; + for (int index = 0; index < procedureCount; index++) { + int identifierOffset = stackReadInt32(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 procedure_index) +{ + Program* external_program; + char* identifier; + int address; + int arguments_count; + unsigned char* procedure_ptr; + int flags; + char err[256]; + jmp_buf jmp_buf; + Program* v13; + + procedure_ptr = program->procedures + 4 + 24 * procedure_index; + flags = stackReadInt32(procedure_ptr, 4); + + if (flags & 0x04) { + identifier = programGetIdentifier(program, stackReadInt32(procedure_ptr, 0)); + external_program = externalProcedureGetProgram(identifier, &address, &arguments_count); + if (external_program == NULL) { + sprintf(err, "External procedure %s not found\n", identifier); + // TODO: Incomplete. + // _interpretOutput(err); + return; + } + + if (arguments_count != 0) { + sprintf(err, "External procedure cannot take arguments in interrupt context"); + // TODO: Incomplete. + // _interpretOutput(err); + return; + } + + _setupExternalCallWithReturnVal(program, external_program, address, 32); + + programStackPushInt32(external_program, 0); + programStackPushInt16(external_program, VALUE_TYPE_INT); + + memcpy(jmp_buf, program->env, sizeof(jmp_buf)); + + v13 = external_program; + } else { + address = stackReadInt32(procedure_ptr, 16); + + _setupCallWithReturnVal(program, address, 24); + + // Push number of arguments. It's always zero for built-in procs. This + // number is consumed by 0x802B. + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); + + memcpy(jmp_buf, program->env, sizeof(jmp_buf)); + + v13 = program; + } + + _interpret(v13, -1); + + memcpy(v13->env, jmp_buf, sizeof(jmp_buf)); +} + +// 0x46DEE4 +void _doEvents() +{ + // TODO: Incomplete. +} + +// 0x46E10C +void programListNodeFree(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 { + gInterpreterProgramListHead = programListNode->next; + } + + programFree(programListNode->program); + internal_free_safe(programListNode, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 2923 +} + +// 0x46E154 +void programListNodeCreate(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x02; + + ProgramListNode* programListNode = internal_malloc_safe(sizeof(*programListNode), __FILE__, __LINE__); // .\\int\\INTRPRET.C, 2907 + programListNode->program = program; + programListNode->next = gInterpreterProgramListHead; + programListNode->prev = NULL; + + if (gInterpreterProgramListHead != NULL) { + gInterpreterProgramListHead->prev = programListNode; + } + + gInterpreterProgramListHead = programListNode; +} + +// 0x46E1EC +void _updatePrograms() +{ + ProgramListNode* curr = gInterpreterProgramListHead; + while (curr != NULL) { + ProgramListNode* next = curr->next; + if (curr->program != NULL) { + _interpret(curr->program, _cpuBurstSize); + } + if (curr->program->exited) { + programListNodeFree(curr); + } + curr = next; + } + _doEvents(); + _updateIntLib(); +} + +// 0x46E238 +void programListFree() +{ + ProgramListNode* curr = gInterpreterProgramListHead; + while (curr != NULL) { + ProgramListNode* next = curr->next; + programListNodeFree(curr); + curr = next; + } +} + +// 0x46E368 +void interpreterRegisterOpcode(int opcode, OpcodeHandler* handler) +{ + int index = opcode & 0x3FFF; + if (index >= OPCODE_MAX_COUNT) { + printf("Too many opcodes!\n"); + exit(1); + } + + gInterpreterOpcodeHandlers[index] = handler; +} + +// 0x46E5EC +void interpreterPrintStats() +{ + ProgramListNode* programListNode = gInterpreterProgramListHead; + while (programListNode != NULL) { + Program* program = programListNode->program; + if (program != NULL) { + int total = 0; + + if (program->dynamicStrings != NULL) { + debugPrint("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)); + debugPrint("Size: %d, ref: %d, string %s\n", size, refcount, (char*)(heap + sizeof(short) + sizeof(short))); + } else { + debugPrint("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; + } + + debugPrint("Total length of heap %d, stored length %d\n", total, *(int*)(program->dynamicStrings)); + } else { + debugPrint("No string heap for program %s\n", program->name); + } + } + + programListNode = programListNode->next; + } +} diff --git a/src/interpreter.h b/src/interpreter.h new file mode 100644 index 0000000..ef92cfe --- /dev/null +++ b/src/interpreter.h @@ -0,0 +1,309 @@ +#ifndef INTERPRETER_H +#define INTERPRETER_H + +#include +#include + +// The maximum number of opcodes. +#define OPCODE_MAX_COUNT (342) + +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_FLAG_0x10 = 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; + +// 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; + int field_70; // end time of timer (field_74 + wait time) + int field_74; // time when wait was called + int field_78; // time when program begin execution (for the first time)?, -1 - program never executed + int (*field_7C)(struct Program* s); // proc to check timer + int flags; // flags + int field_84; + bool exited; +} Program; + +typedef void OpcodeHandler(Program* program); + +typedef struct ProgramListNode { + Program* program; + struct ProgramListNode* next; // next + struct ProgramListNode* prev; // prev +} ProgramListNode; + +extern int _TimeOut; +extern int _Enabled; +extern int (*_timerFunc)(); +extern int _timerTick; +extern char* (*_filenameFunc)(char*); +extern int (*_outputFunc)(char*); +extern int _cpuBurstSize; + +extern OpcodeHandler* gInterpreterOpcodeHandlers[OPCODE_MAX_COUNT]; +extern Program* gInterpreterCurrentProgram; +extern ProgramListNode* gInterpreterProgramListHead; +extern int _suspendEvents; +extern int _busy; + +int _defaultTimerFunc(); +char* _defaultFilename_(char* s); +char* _interpretMangleName(char* s); +int _outputStr(char* a1); +int _checkWait(Program* program); +void _interpretOutputFunc(int (*func)(char*)); +int _interpretOutput(const char* format, ...); +char* programGetCurrentProcedureName(Program* s); +__declspec(noreturn) void programFatalError(const char* str, ...); +opcode_t stackReadInt16(unsigned char* data, int pos); +int stackReadInt32(unsigned char* a1, int a2); +void stackWriteInt16(int value, unsigned char* a2, int a3); +void stackWriteInt32(int value, unsigned char* stack, int pos); +void stackPushInt16(unsigned char* a1, int* a2, int value); +void stackPushInt32(unsigned char* a1, int* a2, int value); +int stackPopInt32(unsigned char* a1, int* a2); +opcode_t stackPopInt16(unsigned char* a1, int* a2); +void programPopString(Program* program, opcode_t a2, int a3); +void programStackPushInt16(Program* program, int value); +void programStackPushInt32(Program* program, int value); +opcode_t programStackPopInt16(Program* program); +int programStackPopInt32(Program* program); +void programReturnStackPushInt16(Program* program, int value); +opcode_t programReturnStackPopInt16(Program* program); +int programReturnStackPopInt32(Program* program); +void _detachProgram(Program* program); +void _purgeProgram(Program* program); +void programFree(Program* program); +Program* programCreateByPath(const char* path); +char* programGetString(Program* program, opcode_t opcode, int offset); +char* programGetIdentifier(Program* program, int offset); +void programMarkHeap(Program* program); +int programPushString(Program* program, char* string); +void opNoop(Program* program); +void opPush(Program* program); +void opPushBase(Program* program); +void opPopBase(Program* program); +void opPopToBase(Program* program); +void op802C(Program* program); +void opDump(Program* program); +void opDelayedCall(Program* program); +void opConditionalCall(Program* program); +void opWait(Program* program); +void opCancel(Program* program); +void opCancelAll(Program* program); +void opIf(Program* program); +void opWhile(Program* program); +void opStore(Program* program); +void opFetch(Program* program); +void opConditionalOperatorNotEqual(Program* program); +void opConditionalOperatorEqual(Program* program); +void opConditionalOperatorLessThanEquals(Program* program); +void opConditionalOperatorGreaterThanEquals(Program* program); +void opConditionalOperatorLessThan(Program* program); +void opConditionalOperatorGreaterThan(Program* program); +void opAdd(Program* program); +void opSubtract(Program* program); +void opMultiply(Program* program); +void opDivide(Program* program); +void opModulo(Program* program); +void opLogicalOperatorAnd(Program* program); +void opLogicalOperatorOr(Program* program); +void opLogicalOperatorNot(Program* program); +void opUnaryMinus(Program* program); +void opBitwiseOperatorNot(Program* program); +void opFloor(Program* program); +void opBitwiseOperatorAnd(Program* program); +void opBitwiseOperatorOr(Program* program); +void opBitwiseOperatorXor(Program* program); +void opSwapReturnStack(Program* program); +void opLeaveCriticalSection(Program* program); +void opEnterCriticalSection(Program* program); +void opJump(Program* program); +void opCall(Program* program); +void op801F(Program* program); +void op801C(Program* program); +void op801D(Program* program); +void op8020(Program* program); +void op8021(Program* program); +void op8025(Program* program); +void op8026(Program* program); +void op8022(Program* program); +void op8023(Program* program); +void op8024(Program* program); +void op801E(Program* program); +void opAtoD(Program* program); +void opDtoA(Program* program); +void opExitProgram(Program* program); +void opStopProgram(Program* program); +void opFetchGlobalVariable(Program* program); +void opStoreGlobalVariable(Program* program); +void opSwapStack(Program* program); +void opFetchProcedureAddress(Program* program); +void opPop(Program* program); +void opDuplicate(Program* program); +void opStoreExternalVariable(Program* program); +void opFetchExternalVariable(Program* program); +void opExportProcedure(Program* program); +void opExportVariable(Program* program); +void opExit(Program* program); +void opDetach(Program* program); +void opCallStart(Program* program); +void opSpawn(Program* program); +Program* forkProgram(Program* program); +void opFork(Program* program); +void opExec(Program* program); +void opCheckProcedureArgumentCount(Program* program); +void opLookupStringProc(Program* program); +void interpreterRegisterOpcodeHandlers(); +void _interpretClose(); +void _interpret(Program* program, int a2); +void _setupCallWithReturnVal(Program* program, int address, int a3); +void _setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4); +void _executeProc(Program* program, int procedure_index); +int programFindProcedure(Program* prg, const char* name); +void _executeProcedure(Program* program, int procedure_index); +void _doEvents(); +void programListNodeFree(ProgramListNode* programListNode); +void programListNodeCreate(Program* program); +void _updatePrograms(); +void programListFree(); +void interpreterRegisterOpcode(int opcode, OpcodeHandler* handler); +void interpreterPrintStats(); + +#endif /* INTERPRETER_H */ diff --git a/src/interpreter_extra.c b/src/interpreter_extra.c new file mode 100644 index 0000000..c0be24e --- /dev/null +++ b/src/interpreter_extra.c @@ -0,0 +1,6872 @@ +#include "interpreter_extra.h" + +#include "actions.h" +#include "animation.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "debug.h" +#include "dialog.h" +#include "display_monitor.h" +#include "endgame.h" +#include "game.h" +#include "game_config.h" +#include "game_dialog.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "light.h" +#include "loadsave.h" +#include "map.h" +#include "object.h" +#include "palette.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "queue.h" +#include "random.h" +#include "reaction.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_object.h" +#include "tile.h" +#include "trait.h" +#include "world_map.h" + +#include +#include + +// 0x504B04 +char _Error_0[] = "Error"; + +// 0x504B0C +char _aCritter[] = ""; + +// 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. +// +// See [opSetLightLevel] for math. +// +// 0x453F90 +const int dword_453F90[3] = { + 0x4000, + 0xA000, + 0x10000, +}; + +// 0x453F9C +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, +}; + +// 0x453FC0 +Rect stru_453FC0 = { 0, 0, 640, 480 }; + +// 0x518EC0 +const char* _dbg_error_strs[SCRIPT_ERROR_COUNT] = { + "unimped", + "obj is NULL", + "can't match program to sid", + "follows", +}; + +// 0x518ED0 +const 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, +}; + +// 0x518EFC +char* _errStr = _Error_0; + +// Last message type during op_float_msg sequential. +// +// 0x518F00 +int _last_color = 1; + +// 0x518F04 +char* _strName = _aCritter; + +// NOTE: This value is a little bit odd. It's used to handle 2 operations: +// [opStartGameDialog] and [opGameDialogReaction]. It's not used outside those +// functions. +// +// When used inside [opStartGameDialog] this value stores [Fidget] constant +// (1 - Good, 4 - Neutral, 7 - Bad). +// +// When used inside [opGameDialogReaction] this value contains specified +// reaction (-1 - Good, 0 - Neutral, 1 - Bad). +// +// 0x5970D0 +int gGameDialogReactionOrFidget; + +// 0x453FD0 +void scriptPredefinedError(Program* program, const char* name, int error) +{ + char string[260]; + + sprintf(string, "Script Error: %s: op_%s: %s", program->name, name, _dbg_error_strs[error]); + + debugPrint(string); +} + +// 0x45400C +void scriptError(const char* format, ...) +{ + char string[260]; + + va_list argptr; + va_start(argptr, format); + vsprintf(string, format, argptr); + va_end(argptr); + + debugPrint(string); +} + +// 0x45404C +int tileIsVisible(int tile) +{ + if (abs(gCenterTile - tile) % 200 < 5) { + return 1; + } + + if (abs(gCenterTile - tile) / 200 < 5) { + return 1; + } + + return 0; +} + +// 0x45409C +int _correctFidForRemovedItem(Object* a1, Object* a2, int flags) +{ + if (a1 == gDude) { + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(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 == gDude) { + if (interfaceGetCurrentHand()) { + if ((flags & 0x02000000) != 0) { + v8 = 0; + } + } else { + if ((flags & 0x01000000) != 0) { + v8 = 0; + } + } + } else { + if ((flags & 0x02000000) != 0) { + v8 = 0; + } + } + + if (v8 == 0) { + newFid = buildFid((fid & 0xF000000) >> 24, fid & 0xFFF, (fid & 0xFF0000) >> 16, 0, (fid & 0x70000000) >> 28); + } + } else { + if (a1 == gDude) { + newFid = buildFid((fid & 0xF000000) >> 24, _art_vault_guy_num, (fid & 0xFF0000) >> 16, v8, (fid & 0x70000000) >> 28); + } + + _adjust_ac(a1, a2, NULL); + } + + if (newFid != -1) { + Rect rect; + objectSetFid(a1, newFid, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + + return 0; +} + +// give_exp_points +// 0x4541C8 +void opGiveExpPoints(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to give_exp_points", program->name); + } + + if (pcAddExperience(data) != 0) { + scriptError("\nScript Error: %s: op_give_exp_points: stat_pc_set failed"); + } +} + +// scr_return +// 0x454238 +void opScrReturn(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to scr_return", program->name); + } + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + script->field_28 = data; + } +} + +// play_sfx +// 0x4542AC +void opPlaySfx(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("script error: %s: invalid arg to play_sfx", program->name); + } + + char* name = programGetString(program, opcode, data); + soundPlayFile(name); +} + +// set_map_start +// 0x454314 +void opSetMapStart(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 (mapSetElevation(elevation) != 0) { + scriptError("\nScript Error: %s: op_set_map_start: map_set_elevation failed", program->name); + return; + } + + int tile = 200 * y + x; + if (tileSetCenter(tile, TILE_SET_CENTER_FLAG_0x01 | TILE_SET_CENTER_FLAG_0x02) != 0) { + scriptError("\nScript Error: %s: op_set_map_start: tile_set_center failed", program->name); + return; + } + + mapSetStart(tile, elevation, rotation); +} + +// override_map_start +// 0x4543F4 +void opOverrideMapStart(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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); + debugPrint(text); + + int tile = 200 * y + x; + int previousTile = gCenterTile; + if (tile != -1) { + if (objectSetRotation(gDude, rotation, NULL) != 0) { + scriptError("\nError: %s: obj_set_rotation failed in override_map_start!", program->name); + } + + if (objectSetLocation(gDude, tile, elevation, NULL) != 0) { + scriptError("\nError: %s: obj_move_to_tile failed in override_map_start!", program->name); + + if (objectSetLocation(gDude, previousTile, elevation, NULL) != 0) { + scriptError("\nError: %s: obj_move_to_tile RECOVERY Also failed!"); + exit(1); + } + } + + tileSetCenter(tile, TILE_SET_CENTER_FLAG_0x01); + tileWindowRefresh(); + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// has_skill +// 0x454568 +void opHasSkill(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + result = skillGetValue(object, skill); + } + } else { + scriptPredefinedError(program, "has_skill", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// using_skill +// 0x454634 +void opUsingSkill(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 == gDude) { + result = dudeHasState(DUDE_STATE_SNEAKING); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// roll_vs_skill +// 0x4546E8 +void opRollVsSkill(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + roll = skillRoll(object, skill, modifier, &(script->howMuch)); + } + } + } else { + scriptPredefinedError(program, "roll_vs_skill", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, roll); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// skill_contest +// 0x4547D4 +void opSkillContest(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to skill_contest", program->name, arg); + } + } + + scriptPredefinedError(program, "skill_contest", SCRIPT_ERROR_NOT_IMPLEMENTED); + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// do_check +// 0x454890 +void opDoCheck(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = scriptGetSid(program); + + Script* script; + if (scriptGetScript(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 = statRoll(object, stat, mod, &(script->howMuch)); + break; + default: + scriptError("\nScript Error: %s: op_do_check: Stat out of range", program->name); + break; + } + } + } else { + scriptPredefinedError(program, "do_check", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, roll); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// success +// 0x4549A8 +void opSuccess(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critical +// 0x454A44 +void opCritical(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// how_much +// 0x454AD0 +void opHowMuch(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to how_much", program->name); + } + + int result = 0; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + result = script->howMuch; + } else { + scriptPredefinedError(program, "how_much", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// mark_area_known +// 0x454B6C +void opMarkAreaKnown(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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]); + } +} + +// reaction_influence +// 0x454C34 +void opReactionInfluence(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to reaction_influence", program->name, arg); + } + } + + int result = _reaction_influence_(); + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// random +// 0x454CD4 +void opRandom(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to random", program->name, arg); + } + } + + int result; + if (_vcr_status() == 2) { + result = randomBetween(data[1], data[0]); + } else { + result = (data[0] - data[1]) / 2; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// roll_dice +// 0x454D88 +void opRollDice(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to roll_dice", program->name, arg); + } + } + + scriptPredefinedError(program, "roll_dice", SCRIPT_ERROR_NOT_IMPLEMENTED); + + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// move_to +// 0x454E28 +void opMoveTo(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 == gDude) { + bool v1 = _tile_get_scroll_limiting(); + bool v2 = _tile_get_scroll_blocking(); + + if (v1) { + _tile_disable_scroll_limiting(); + } + + if (v2) { + _tile_disable_scroll_blocking(); + } + + Rect rect; + newTile = objectSetLocation(object, tile, elevation, &rect); + if (newTile != -1) { + tileSetCenter(object->tile, TILE_SET_CENTER_FLAG_0x01); + } + + if (v1) { + _tile_enable_scroll_limiting(); + } + + if (v2) { + _tile_enable_scroll_blocking(); + } + } else { + Rect before; + objectGetRect(object, &before); + + if (object->elevation != elevation && (object->pid >> 24) == OBJ_TYPE_CRITTER) { + _combat_delete_critter(object); + } + + Rect after; + newTile = objectSetLocation(object, tile, elevation, &after); + if (newTile != -1) { + rectUnion(&before, &after, &before); + tileWindowRefreshRect(&before, gElevation); + } + } + } else { + scriptPredefinedError(program, "move_to", SCRIPT_ERROR_OBJECT_IS_NULL); + newTile = -1; + } + + programStackPushInt32(program, newTile); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// create_object_sid +// 0x454FA8 +void opCreateObject(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + debugPrint("\nError: attempt to Create critter in load/save-game: %s!", program->name); + goto out; + } + + if (pid == 0) { + debugPrint("\nError: attempt to Create critter With PID of 0: %s!", program->name); + goto out; + } + + Proto* proto; + if (protoGetProto(pid, &proto) != -1) { + if (objectCreateWithFidPid(&object, proto->fid, pid) != -1) { + if (tile == -1) { + tile = 0; + } + + Rect rect; + if (objectSetLocation(object, tile, elevation, &rect) != -1) { + tileWindowRefreshRect(&rect, object->elevation); + } + } + } + + if (sid != -1) { + int scriptType = 0; + switch (object->pid >> 24) { + 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) { + scriptRemove(object->sid); + object->sid = -1; + } + + if (scriptAdd(&(object->sid), scriptType) == -1) { + goto out; + } + + Script* script; + if (scriptGetScript(object->sid, &script) == -1) { + goto out; + } + + script->field_14 = sid - 1; + + if (scriptType == SCRIPT_TYPE_SPATIAL) { + script->sp.built_tile = ((object->elevation << 29) & 0xE0000000) | object->tile; + script->sp.radius = 3; + } + + object->id = scriptsNewObjectId(); + script->field_1C = object->id; + script->owner = object; + _scr_find_str_run_info(sid - 1, &(script->field_50), object->sid); + }; + +out: + + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// destroy_object +// 0x4551E4 +void opDestroyObject(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to destroy_object", program->name); + } + + Object* object = (Object*)data; + + if (object == NULL) { + scriptPredefinedError(program, "destroy_object", SCRIPT_ERROR_OBJECT_IS_NULL); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + if (_isLoadingGame()) { + debugPrint("\nError: attempt to destroy critter in load/save-game: %s!", program->name); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + } + + bool isSelf = object == scriptGetSelf(program); + + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + _combat_delete_critter(object); + } + + Object* owner = objectGetOwner(object); + if (owner != NULL) { + int quantity = _item_count(owner, object); + itemRemove(owner, object, quantity); + + if (owner == gDude) { + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(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 { + reg_anim_clear(object); + objectDestroy(object, NULL); + } + } else { + reg_anim_clear(object); + + Rect rect; + objectDestroy(object, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + + program->flags &= ~PROGRAM_FLAG_0x20; + + if (isSelf) { + program->flags |= PROGRAM_FLAG_0x0100; + } +} + +// display_msg +// 0x455388 +void opDisplayMsg(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { + programFatalError("script error: %s: invalid arg to display_msg", program->name); + } + + char* string = programGetString(program, opcode, data); + displayMonitorAddMessage(string); + + bool showScriptMessages = false; + configGetBool(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); + + if (showScriptMessages) { + debugPrint("\n"); + debugPrint(string); + } +} + +// script_overrides +// 0x455430 +void opScriptOverrides(Program* program) +{ + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + script->scriptOverrides = 1; + } else { + scriptPredefinedError(program, "script_overrides", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } +} + +// obj_is_carrying_obj_pid +// 0x455470 +void opObjectIsCarryingObjectWithPid(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = objectGetCarriedQuantityByPid(obj, pid); + } else { + scriptPredefinedError(program, "obj_is_carrying_obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// tile_contains_obj_pid +// 0x455534 +void opTileContainsObjectWithPid(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = objectFindFirstAtLocation(elevation, tile); + while (object) { + if (object->pid == pid) { + result = 1; + break; + } + object = objectFindNextAtLocation(); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// self_obj +// 0x455600 +void opGetSelf(Program* program) +{ + Object* object = scriptGetSelf(program); + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// source_obj +// 0x455624 +void opGetSource(Program* program) +{ + Object* object = NULL; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + object = script->source; + } else { + scriptPredefinedError(program, "source_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// target_obj +// 0x455678 +void opGetTarget(Program* program) +{ + Object* object = NULL; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + object = script->target; + } else { + scriptPredefinedError(program, "target_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// dude_obj +// 0x4556CC +void opGetDude(Program* program) +{ + programStackPushInt32(program, (int)gDude); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// NOTE: The implementation is the same as in [opGetTarget]. +// +// obj_being_used_with +// 0x4556EC +void opGetObjectBeingUsed(Program* program) +{ + Object* object = NULL; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + object = script->target; + } else { + scriptPredefinedError(program, "obj_being_used_with", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// local_var +// 0x455740 +void opGetLocalVar(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + // FIXME: The error message is wrong. + programFatalError("script error: %s: invalid arg to op_global_var", program->name); + } + + int value = -1; + + int sid = scriptGetSid(program); + scriptGetLocalVar(sid, data, &value); + + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// set_local_var +// 0x4557C8 +void opSetLocalVar(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to set_local_var", program->name, arg); + } + } + + int variable = data[1]; + int value = data[0]; + + int sid = scriptGetSid(program); + scriptSetLocalVar(sid, variable, value); +} + +// map_var +// 0x455858 +void opGetMapVar(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to op_map_var", program->name); + } + + int value = mapGetGlobalVar(data); + + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// set_map_var +// 0x4558C8 +void opSetMapVar(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to set_map_var", program->name, arg); + } + } + + int variable = data[1]; + int value = data[0]; + + mapSetGlobalVar(variable, value); +} + +// global_var +// 0x455950 +void opGetGlobalVar(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to op_global_var", program->name); + } + + int value = -1; + if (gGameGlobalVarsLength != 0) { + value = gameGetGlobalVar(data); + } else { + scriptError("\nScript Error: %s: op_global_var: no global vars found!", program->name); + } + + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// set_global_var +// 0x4559EC +// 0x80C6 +void opSetGlobalVar(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to set_global_var", program->name, arg); + } + } + + int variable = data[1]; + int value = data[0]; + + if (gGameGlobalVarsLength != 0) { + gameSetGlobalVar(variable, value); + } else { + scriptError("\nScript Error: %s: op_set_global_var: no global vars found!", program->name); + } +} + +// script_action +// 0x455A90 +void opGetScriptAction(Program* program) +{ + int action = 0; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + action = script->action; + } else { + scriptPredefinedError(program, "script_action", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, action); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_type +// 0x455AE4 +void opGetObjectType(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to op_obj_type", program->name); + } + + Object* object = (Object*)data; + + int objectType = -1; + if (object != NULL) { + objectType = (object->fid & 0xF000000) >> 24; + } + + programStackPushInt32(program, objectType); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_item_subtype +// 0x455B6C +void opGetItemType(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to op_item_subtype", program->name); + } + + Object* obj = (Object*)data; + + int itemType = -1; + if (obj != NULL) { + if ((obj->pid >> 24) == OBJ_TYPE_ITEM) { + Proto* proto; + if (protoGetProto(obj->pid, &proto) != -1) { + itemType = itemGetType(obj); + } + } + } + + programStackPushInt32(program, itemType); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// get_critter_stat +// 0x455C10 +void opGetCritterStat(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 { + scriptPredefinedError(program, "get_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, value); + programStackPushInt16(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. +// +// set_critter_stat +// 0x455CCC +void opSetCritterStat(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 == gDude) { + int currentValue = critterGetBaseStatWithTraitModifier(object, stat); + critterSetBaseStat(object, stat, currentValue + value); + } else { + scriptPredefinedError(program, "set_critter_stat", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Can't modify anyone except obj_dude!"); + result = -1; + } + } else { + scriptPredefinedError(program, "set_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); + result = -1; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// animate_stand_obj +// 0x455DC8 +void opAnimateStand(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to animate_stand_obj", program->name); + } + + Object* object = (Object*)data; + if (object == NULL) { + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + scriptPredefinedError(program, "animate_stand_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + return; + } + + object = scriptGetSelf(program); + } + + if (!isInCombat()) { + reg_anim_begin(1); + reg_anim_animate(object, ANIM_STAND, 0); + reg_anim_end(); + } +} + +// animate_stand_reverse_obj +// 0x455E7C +void opAnimateStandReverse(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + // FIXME: typo in message, should be animate_stand_reverse_obj. + programFatalError("script error: %s: invalid arg to animate_stand_obj", program->name); + } + + Object* object = (Object*)data; + if (object == NULL) { + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + scriptPredefinedError(program, "animate_stand_reverse_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + return; + } + + object = scriptGetSelf(program); + } + + if (!isInCombat()) { + reg_anim_begin(0x01); + reg_anim_animate_reverse(object, ANIM_STAND, 0); + reg_anim_end(); + } +} + +// animate_move_obj_to_tile +// 0x455F30 +void opAnimateMoveObjectToTile(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "animate_move_obj_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (tile <= -1) { + return; + } + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + scriptPredefinedError(program, "animate_move_obj_to_tile", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + return; + } + + if (!critterIsActive(object)) { + return; + } + + if (isInCombat()) { + return; + } + + if ((flags & 0x10) != 0) { + reg_anim_clear(object); + flags &= ~0x10; + } + + reg_anim_begin(1); + + if (flags == 0) { + reg_anim_obj_move_to_tile(object, tile, object->elevation, -1, 0); + } else { + reg_anim_obj_run_to_tile(object, tile, object->elevation, -1, 0); + } + + reg_anim_end(); +} + +// tile_in_tile_rect +// 0x45607C +void opTileInTileRect(Program* program) +{ + opcode_t opcode[5]; + int data[5]; + Point points[5]; + + for (int arg = 0; arg < 5; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// make_daytime +// 0x456170 +void opMakeDayTime(Program* program) +{ +} + +// tile_distance +// 0x456174 +void opTileDistanceBetween(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = tileDistanceBetween(tile1, tile2); + } else { + distance = 9999; + } + + programStackPushInt32(program, distance); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// tile_distance_objs +// 0x456228 +void opTileDistanceBetweenObjects(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 (data[1] >= HEX_GRID_SIZE && data[0] >= HEX_GRID_SIZE) { + if (object1->elevation == object2->elevation) { + if (object1->tile != -1 && object2->tile != -1) { + distance = tileDistanceBetween(object1->tile, object2->tile); + } + } + } else { + scriptPredefinedError(program, "tile_distance_objs", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Passed a tile # instead of an object!!!BADBADBAD!"); + } + } + + programStackPushInt32(program, distance); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// tile_num +// 0x456324 +void opGetObjectTile(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to tile_num", program->name); + } + + Object* obj = (Object*)data; + + int tile = -1; + if (obj != NULL) { + tile = obj->tile; + } else { + scriptPredefinedError(program, "tile_num", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, tile); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// tile_num_in_direction +// 0x4563B4 +void opGetTileInDirection(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 = tileGetTileInDirection(origin, rotation, distance); + if (tile < -1) { + debugPrint("\nError: %s: op_tile_num_in_direction got #: %d", program->name, tile); + tile = -1; + } + } + } else { + scriptPredefinedError(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); + debugPrint(" rotation out of Range!"); + } + } else { + scriptPredefinedError(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); + debugPrint(" tileNum is -1!"); + } + + programStackPushInt32(program, tile); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// pickup_obj +// 0x4564D4 +void opPickup(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to pickup_obj", program->name); + } + + Object* object = (Object*)data; + + if (object == NULL) { + return; + } + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == 1) { + scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + return; + } + + if (script->target == NULL) { + scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + actionPickUp(script->target, object); +} + +// drop_obj +// 0x456580 +void opDrop(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to drop_obj", program->name); + } + + Object* object = (Object*)data; + + if (object == NULL) { + return; + } + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. + scriptPredefinedError(program, "drop_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (script->target == NULL) { + // FIXME: Should be SCRIPT_ERROR_OBJECT_IS_NULL. + scriptPredefinedError(program, "drop_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + return; + } + + _obj_drop(script->target, object); +} + +// add_obj_to_inven +// 0x45662C +void opAddObjectToInventory(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (itemAdd(owner, item, 1) == 0) { + Rect rect; + _obj_disconnect(item, &rect); + tileWindowRefreshRect(&rect, item->elevation); + } + } else { + scriptPredefinedError(program, "add_obj_to_inven", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Item was already attached to something else!"); + } +} + +// rm_obj_from_inven +// 0x456708 +void opRemoveObjectFromInventory(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (itemRemove(owner, item, 1) == 0) { + Rect rect; + _obj_connect(item, 1, 0, &rect); + tileWindowRefreshRect(&rect, item->elevation); + + if (updateFlags) { + _correctFidForRemovedItem(owner, item, flags); + } + } +} + +// wield_obj_critter +// 0x45681C +void opWieldItem(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (item == NULL) { + scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Only works for critters! ERROR ERROR ERROR!"); + return; + } + + int hand = HAND_RIGHT; + + bool v1 = false; + Object* oldArmor = NULL; + Object* newArmor = NULL; + if (critter == gDude) { + if (interfaceGetCurrentHand() == HAND_LEFT) { + hand = HAND_LEFT; + } + + if (itemGetType(item) == ITEM_TYPE_ARMOR) { + oldArmor = critterGetArmor(gDude); + } + + v1 = true; + newArmor = item; + } + + if (_inven_wield(critter, item, hand) == -1) { + scriptPredefinedError(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); + debugPrint(" inven_wield failed! ERROR ERROR ERROR!"); + return; + } + + if (critter == gDude) { + if (v1) { + _adjust_ac(critter, oldArmor, newArmor); + } + + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } +} + +// use_obj +// 0x4569D0 +void opUseObject(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to use_obj", program->name); + } + + Object* object = (Object*)data; + + if (object == NULL) { + scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. + scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (script->target == NULL) { + scriptPredefinedError(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + Object* self = scriptGetSelf(program); + if ((self->pid >> 24) == OBJ_TYPE_CRITTER) { + _action_use_an_object(script->target, object); + } else { + _obj_use(self, object); + } +} + +// obj_can_see_obj +// 0x456AC4 +void opObjectCanSeeObject(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 == gDude) { + dudeHasState(0); + } + + critterGetStat(object1, STAT_PERCEPTION); + + if (objectCanHearObject(object1, object2)) { + Object* a5; + _make_straight_path(object1, object1->tile, object2->tile, NULL, &a5, 16); + if (a5 == object2) { + result = 1; + } + } + } + } else { + scriptPredefinedError(program, "obj_can_see_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// attack_complex +// 0x456C00 +void opAttackComplex(Program* program) +{ + opcode_t opcode[8]; + int data[8]; + + for (int arg = 0; arg < 8; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to attack", program->name, arg); + } + } + + Object* target = (Object*)data[7]; + if (target == NULL) { + scriptPredefinedError(program, "attack", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + program->flags |= PROGRAM_FLAG_0x20; + + Object* self = scriptGetSelf(program); + if (self == NULL) { + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if (!critterIsActive(self) || (self->flags & OBJECT_HIDDEN) != 0) { + debugPrint("\n But is already Inactive (Dead/Stunned/Invisible)"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if (!critterIsActive(target) || (target->flags & OBJECT_HIDDEN) != 0) { + debugPrint("\n But target is already dead or invisible"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if ((target->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { + debugPrint("\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; + } + + scriptsRequestCombat(&attack); + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// start_gdialog +// 0x456DF0 +void opStartGameDialog(Program* program) +{ + opcode_t opcode[5]; + int data[5]; + + for (int arg = 0; arg < 5; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "start_gdialog", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + gGameDialogHeadFid = -1; + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + Proto* proto; + if (protoGetProto(obj->pid, &proto) == -1) { + return; + } + } + + if (headId != -1) { + gGameDialogHeadFid = buildFid(OBJ_TYPE_HEAD, headId, 0, 0, 0); + } + + gameDialogSetBackground(backgroundId); + gGameDialogReactionOrFidget = reactionLevel; + + if (gGameDialogHeadFid != -1) { + int npcReactionValue = reactionGetValue(gGameDialogSpeaker); + int npcReactionType = reactionTranslateValue(npcReactionValue); + switch (npcReactionType) { + case NPC_REACTION_BAD: + gGameDialogReactionOrFidget = FIDGET_BAD; + break; + case NPC_REACTION_NEUTRAL: + gGameDialogReactionOrFidget = FIDGET_NEUTRAL; + break; + case NPC_REACTION_GOOD: + gGameDialogReactionOrFidget = FIDGET_GOOD; + break; + } + } + + gGameDialogSid = scriptGetSid(program); + gGameDialogSpeaker = scriptGetSelf(program); + _gdialogInitFromScript(gGameDialogHeadFid, gGameDialogReactionOrFidget); +} + +// end_dialogue +// 0x456F80 +void opEndGameDialog(Program* program) +{ + if (_gdialogExitFromScript() != -1) { + gGameDialogSpeaker = NULL; + gGameDialogSid = -1; + } +} + +// dialogue_reaction +// 0x456FA4 +void opGameDialogReaction(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int value = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, value); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to dialogue_reaction", program->name); + } + + gGameDialogReactionOrFidget = value; + _talk_to_critter_reacts(value); +} + +// metarule3 +// 0x457110 +void opMetarule3(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 = killsGetByType(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 = objectFindFirstAtLocation(elevation, tile); + while (object != NULL) { + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + if (critterFound) { + result = (int)object; + break; + } + } + + if (object == previousCritter) { + critterFound = true; + } + + object = objectFindNextAtLocation(); + } + } + break; + case METARULE3_ART_SET_BASE_FID_NUM: + if (1) { + Object* obj = (Object*)data[2]; + int frmId = data[1]; + + int fid = buildFid((obj->fid & 0xF000000) >> 24, + frmId, + (obj->fid & 0xFF0000) >> 16, + (obj->fid & 0xF000) >> 12, + (obj->fid & 0x70000000) >> 28); + + Rect updatedRect; + objectSetFid(obj, fid, &updatedRect); + tileWindowRefreshRect(&updatedRect, gElevation); + } + break; + case METARULE3_TILE_SET_CENTER: + result = tileSetCenter(data[2], TILE_SET_CENTER_FLAG_0x01); + break; + case METARULE3_109: + result = aiGetChemUse((Object*)data[2]); + break; + case METARULE3_110: + result = carIsEmpty() ? 1 : 0; + break; + case METARULE3_111: + result = _map_target_load_area(); + break; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// set_map_music +// 0x45734C +void opSetMapMusic(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + // FIXME: argument is wrong, should be 1. + programFatalError("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 = programGetString(program, opcode[0], data[0]); + } else { + // FIXME: argument is wrong, should be 0. + programFatalError("script error: %s: invalid arg %d to set_map_music", program->name, 2); + } + + debugPrint("\nset_map_music: %d, %s", mapIndex, string); + worldmapSetMapMusic(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. +// +// +// set_obj_visibility +// 0x45741C +void opSetObjectVisibility(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "set_obj_visibility", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (_isLoadingGame()) { + debugPrint("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()) { + objectDisableOutline(obj, NULL); + objectClearOutline(obj, NULL); + } + + Rect rect; + if (objectHide(obj, &rect) != -1) { + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + obj->flags |= OBJECT_NO_BLOCK; + } + + tileWindowRefreshRect(&rect, obj->elevation); + } + } + } else { + if ((obj->flags & OBJECT_HIDDEN) != 0) { + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + obj->flags &= ~OBJECT_NO_BLOCK; + } + + Rect rect; + if (objectShow(obj, &rect) != -1) { + tileWindowRefreshRect(&rect, obj->elevation); + } + } + } +} + +// load_map +// 0x45755C +void opLoadMap(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + opcode[0] = programStackPopInt16(program); + data[0] = programStackPopInt32(program); + + if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[0], data[0]); + } + + if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg 0 to load_map", program->name); + } + + opcode[1] = programStackPopInt16(program); + data[1] = programStackPopInt32(program); + + if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = programGetString(program, opcode[1], mapIndexOrName); + } else { + programFatalError("script error: %s: invalid arg 1 to load_map", program->name); + } + } + + int mapIndex = -1; + + if (mapName != NULL) { + gGameGlobalVars[GVAR_LOAD_MAP_INDEX] = param; + mapIndex = mapGetIndexByFileName(mapName); + } else { + if (mapIndexOrName >= 0) { + gGameGlobalVars[GVAR_LOAD_MAP_INDEX] = param; + mapIndex = mapIndexOrName; + } + } + + if (mapIndex != -1) { + MapTransition transition; + transition.map = mapIndex; + transition.elevation = -1; + transition.tile = -1; + transition.rotation = -1; + mapSetTransition(&transition); + } +} + +// wm_area_set_pos +// 0x457680 +void opWorldmapCitySetPos(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 (worldmapCitySetPos(city, x, y) == -1) { + scriptPredefinedError(program, "wm_area_set_pos", SCRIPT_ERROR_FOLLOWS); + debugPrint("Invalid Parameter!"); + } +} + +// set_exit_grids +// 0x457730 +void opSetExitGrids(Program* program) +{ + opcode_t opcode[5]; + int data[5]; + + for (int arg = 0; arg < 5; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = objectFindFirstAtElevation(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 = objectFindNextAtElevation(); + } +} + +// anim_busy +// 0x4577EC +void opAnimBusy(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to anim_busy", program->name); + } + + Object* object = (Object*)data; + + int rc = 0; + if (object != NULL) { + rc = animationIsBusy(object); + } else { + scriptPredefinedError(program, "anim_busy", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, rc); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_heal +// 0x457880 +void opCritterHeal(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to critter_heal", program->name, arg); + } + } + + Object* critter = (Object*)data[1]; + int amount = data[0]; + + int rc = critterAdjustHitPoints(critter, amount); + + if (critter == gDude) { + interfaceRenderHitPoints(true); + } + + programStackPushInt32(program, rc); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// set_light_level +// 0x457934 +void opSetLightLevel(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to set_light_level", program->name); + } + + int lightLevel = data; + + if (data == 50) { + lightSetLightLevel(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; + } + + lightSetLightLevel(lightIntensity, true); +} + +// game_time +// 0x4579F4 +void opGetGameTime(Program* program) +{ + int time = gameTimeGetTime(); + programStackPushInt32(program, time); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// game_time_in_seconds +// 0x457A18 +void opGetGameTimeInSeconds(Program* program) +{ + int time = gameTimeGetTime(); + programStackPushInt32(program, time / 10); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// elevation +// 0x457A44 +void opGetObjectElevation(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to elevation", program->name); + } + + Object* object = (Object*)data; + + int elevation = 0; + if (object != NULL) { + elevation = object->elevation; + } else { + scriptPredefinedError(program, "elevation", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, elevation); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// kill_critter +// 0x457AD4 +void opKillCritter(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to kill_critter", program->name, arg); + } + } + + Object* object = (Object*)data[1]; + int deathFrame = data[0]; + + if (object == NULL) { + scriptPredefinedError(program, "kill_critter", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (_isLoadingGame()) { + debugPrint("\nError: attempt to destroy critter in load/save-game: %s!", program->name); + } + + program->flags |= PROGRAM_FLAG_0x20; + + Object* self = scriptGetSelf(program); + bool isSelf = self == object; + + reg_anim_clear(object); + _combat_delete_critter(object); + critterKill(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; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); + + bool useStandardDeath = false; + if (violenceLevel < VIOLENCE_LEVEL_MAXIMUM_BLOOD) { + useStandardDeath = true; + } else { + int fid = buildFid(1, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + if (!artExists(fid)) { + useStandardDeath = true; + } + } + + if (useStandardDeath) { + if (forceBack) { + anim = ANIM_FALL_BACK; + } else { + int fid = buildFid(1, critter->fid & 0xFFF, ANIM_FALL_FRONT, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + if (artExists(fid)) { + anim = ANIM_FALL_FRONT; + } else { + anim = ANIM_FALL_BACK; + } + } + } + } + + return anim; +} + +// kill_critter_type +// 0x457CB4 +void opKillCritterType(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to kill_critter", program->name, arg); + } + } + + int pid = data[1]; + int deathFrame = data[0]; + + if (_isLoadingGame()) { + debugPrint("\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 = objectFindFirst(); + while (obj != NULL) { + if (((obj->fid & 0xFF0000) >> 16) >= ANIM_FALL_BACK_SF) { + obj = objectFindNext(); + continue; + } + + if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->pid == pid && !critterIsDead(obj)) { + if (obj == previousObj || count > 200) { + scriptPredefinedError(program, "kill_critter_type", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Infinite loop destroying critters!"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + reg_anim_clear(obj); + + if (deathFrame != 0) { + _combat_delete_critter(obj); + if (deathFrame == 1) { + int anim = _correctDeath(obj, _ftList[v3], 1); + critterKill(obj, anim, 1); + v3 += 1; + if (v3 >= 11) { + v3 = 0; + } + } else { + critterKill(obj, ANIM_FALL_BACK_SF, 1); + } + } else { + reg_anim_clear(obj); + + Rect rect; + objectDestroy(obj, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + + previousObj = obj; + count += 1; + + objectFindFirst(); + + gMapHeader.field_38 = gameTimeGetTime(); + } + + obj = objectFindNext(); + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// critter_dmg +// 0x457EB4 +void opCritterDamage(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "critter_damage", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + scriptPredefinedError(program, "critter_damage", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Can't call on non-critters!"); + return; + } + + Object* self = scriptGetSelf(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; + } +} + +// add_timer_event +// 0x457FF0 +void opAddTimerEvent(Program* s) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(s); + data[arg] = programStackPopInt32(s); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(s, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to add_timer_event", s->name, arg); + } + } + + Object* object = (Object*)data[2]; + int delay = data[1]; + int param = data[0]; + + if (object == NULL) { + scriptError("\nScript Error: %s: op_add_timer_event: pobj is NULL!", s->name); + return; + } + + scriptAddTimerEvent(object->sid, delay, param); +} + +// rm_timer_event +// 0x458094 +void opRemoveTimerEvent(Program* program) +{ + int elevation; + + elevation = 0; + + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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. + scriptError("\nScript Error: %s: op_add_timer_event: pobj is NULL!"); + return; + } + + queueRemoveEvents(object); +} + +// Converts seconds into game ticks. +// +// game_ticks +// 0x458108 +void opGameTicks(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to game_ticks", program->name); + } + + int ticks = data; + + if (ticks < 0) { + ticks = 0; + } + + programStackPushInt32(program, ticks * 10); + programStackPushInt16(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 +void opHasTrait(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 = perkGetRank(object, param); + } else { + scriptError("\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 ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + result = object->data.critter.combat.aiPacket; + } + break; + case CRITTER_TRAIT_OBJECT_TEAM: + if ((object->pid >> 24) == 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 = objectGetInventoryWeight(object); + break; + } + break; + case CRITTER_TRAIT_TRAIT: + if (param < TRAIT_COUNT) { + result = traitIsSelected(param); + } else { + scriptError("\nScript Error: %s: op_has_trait: Trait out of range", program->name); + } + break; + default: + scriptError("\nScript Error: %s: op_has_trait: Trait out of range", program->name); + break; + } + } else { + scriptPredefinedError(program, "has_trait", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_can_hear_obj +// 0x45835C +void opObjectCanHearObject(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (objectCanHearObject(object2, object1)) { + canHear = true; + } + } + } + } + + programStackPushInt32(program, canHear); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// game_time_hour +// 0x458438 +void opGameTimeHour(Program* program) +{ + int value = gameTimeGetHour(); + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// fixed_param +// 0x45845C +void opGetFixedParam(Program* program) +{ + int fixedParam = 0; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + fixedParam = script->fixedParam; + } else { + scriptPredefinedError(program, "fixed_param", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, fixedParam); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// tile_is_visible +// 0x4584B0 +void opTileIsVisible(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to tile_is_visible", program->name); + } + + int isVisible = 0; + if (tileIsVisible(data)) { + isVisible = 1; + } + + programStackPushInt32(program, isVisible); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// dialogue_system_enter +// 0x458534 +void opGameDialogSystemEnter(Program* program) +{ + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return; + } + + Object* self = scriptGetSelf(program); + if ((self->pid >> 24) == OBJ_TYPE_CRITTER) { + if (!critterIsActive(self)) { + return; + } + } + + if (isInCombat()) { + return; + } + + if (_game_state_request(4) == -1) { + return; + } + + gGameDialogSpeaker = scriptGetSelf(program); +} + +// action_being_used +// 0x458594 +void opGetActionBeingUsed(Program* program) +{ + int action = -1; + + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + action = script->actionBeingUsed; + } else { + scriptPredefinedError(program, "action_being_used", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); + } + + programStackPushInt32(program, action); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_state +// 0x4585E8 +void opGetCritterState(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to critter_state", program->name); + } + + Object* critter = (Object*)data; + + int state = CRITTER_STATE_DEAD; + if (critter != NULL && (critter->pid >> 24) == OBJ_TYPE_CRITTER) { + if (critterIsActive(critter)) { + state = CRITTER_STATE_NORMAL; + + int anim = (critter->fid & 0xFF0000) >> 16; + 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 (!critterIsDead(critter)) { + state = CRITTER_STATE_PRONE; + } + } + } else { + scriptPredefinedError(program, "critter_state", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, state); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// game_time_advance +// 0x4586C8 +void opGameTimeAdvance(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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++) { + gameTimeAddTicks(GAME_TIME_TICKS_PER_DAY); + queueProcessEvents(); + } + + gameTimeAddTicks(remainder); + queueProcessEvents(); +} + +// radiation_inc +// 0x458760 +void opRadiationIncrease(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to radiation_inc", program->name, arg); + } + } + + Object* object = (Object*)data[1]; + int amount = data[0]; + + if (object == NULL) { + scriptPredefinedError(program, "radiation_inc", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + critterAdjustRadiation(object, amount); +} + +// radiation_dec +// 0x458800 +void opRadiationDecrease(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to radiation_dec", program->name, arg); + } + } + + Object* object = (Object*)data[1]; + int amount = data[0]; + + if (object == NULL) { + scriptPredefinedError(program, "radiation_dec", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + int radiation = critterGetRadiation(object); + int adjustment = radiation >= 0 ? -amount : 0; + + critterAdjustRadiation(object, adjustment); +} + +// critter_attempt_placement +// 0x4588B4 +void opCritterAttemptPlacement(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "critter_attempt_placement", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (elevation != critter->elevation && critter->pid >> 24 == OBJ_TYPE_CRITTER) { + _combat_delete_critter(critter); + } + + objectSetLocation(critter, 0, elevation, NULL); + + int rc = _obj_attempt_placement(critter, tile, elevation, 1); + programStackPushInt32(program, rc); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_pid +// 0x4589A0 +void opGetObjectPid(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_pid", program->name); + } + + Object* obj = (Object*)data; + + int pid = -1; + if (obj) { + pid = obj->pid; + } else { + scriptPredefinedError(program, "obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, pid); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// cur_map_index +// 0x458A30 +void opGetCurrentMap(Program* program) +{ + int mapIndex = mapGetCurrentMap(); + programStackPushInt32(program, mapIndex); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_add_trait +// 0x458A54 +void opCritterAddTrait(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + switch (kind) { + case CRITTER_TRAIT_PERK: + if (1) { + char* critterName = critterGetName(object); + char* perkName = perkGetName(param); + debugPrint("\nintextra::critter_add_trait: Adding Perk %s to %s", perkName, critterName); + + if (value > 0) { + if (perkAddForce(object, param) != 0) { + scriptError("\nScript Error: %s: op_critter_add_trait: perk_add_force failed", program->name); + debugPrint("Perk: %d", param); + } + } else { + if (perkRemove(object, param) != 0) { + // FIXME: typo in debug message, should be perk_sub + scriptError("\nScript Error: %s: op_critter_add_trait: per_sub failed", program->name); + debugPrint("Perk: %d", param); + } + } + + if (object == gDude) { + interfaceRenderHitPoints(true); + } + } + break; + case CRITTER_TRAIT_OBJECT: + switch (param) { + case CRITTER_TRAIT_OBJECT_AI_PACKET: + critterSetAiPacket(object, value); + break; + case CRITTER_TRAIT_OBJECT_TEAM: + if (objectIsPartyMember(object)) { + break; + } + + if (object->data.critter.combat.team == value) { + break; + } + + if (_isLoadingGame()) { + break; + } + + critterSetTeam(object, value); + break; + } + break; + default: + scriptError("\nScript Error: %s: op_critter_add_trait: Trait out of range", program->name); + break; + } + } + } else { + scriptPredefinedError(program, "critter_add_trait", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, -1); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_rm_trait +// 0x458C2C +void opCritterRemoveTrait(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "critter_rm_trait", SCRIPT_ERROR_OBJECT_IS_NULL); + // FIXME: Ruins stack. + return; + } + + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + switch (kind) { + case CRITTER_TRAIT_PERK: + while (perkGetRank(object, param) > 0) { + if (perkRemove(object, param) != 0) { + scriptError("\nScript Error: op_critter_rm_trait: perk_sub failed"); + } + } + break; + default: + scriptError("\nScript Error: %s: op_critter_rm_trait: Trait out of range", program->name); + break; + } + } + + programStackPushInt32(program, -1); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// proto_data +// 0x458D38 +void opGetProtoData(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to proto_data", program->name, arg); + } + } + + int pid = data[1]; + int member = data[0]; + + int value = 0; + int valueType = _proto_data_member(pid, member, &value); + switch (valueType) { + case PROTO_DATA_MEMBER_TYPE_INT: + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); + break; + case PROTO_DATA_MEMBER_TYPE_STRING: + programStackPushInt32(program, programPushString(program, (char*)value)); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); + break; + default: + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); + break; + } +} + +// message_str +// 0x458E10 +void opGetMessageString(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + debugPrint("\nError: No message file EXISTS!: index %d, line %d", messageListIndex, messageIndex); + string = _errStr; + } + } else { + string = _errStr; + } + + programStackPushInt32(program, programPushString(program, string)); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// critter_inven_obj +// 0x458F00 +void opCritterGetInventoryObject(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 ((critter->pid >> 24) == OBJ_TYPE_CRITTER) { + switch (type) { + case INVEN_TYPE_WORN: + result = (int)critterGetArmor(critter); + break; + case INVEN_TYPE_RIGHT_HAND: + if (critter == gDude) { + if (interfaceGetCurrentHand() != HAND_LEFT) { + result = (int)critterGetItem2(critter); + } + } else { + result = (int)critterGetItem2(critter); + } + break; + case INVEN_TYPE_LEFT_HAND: + if (critter == gDude) { + if (interfaceGetCurrentHand() == HAND_LEFT) { + result = (int)critterGetItem1(critter); + } + } else { + result = (int)critterGetItem1(critter); + } + break; + case INVEN_TYPE_INV_COUNT: + result = critter->data.inventory.length; + break; + default: + scriptError("script error: %s: Error in critter_inven_obj -- wrong type!", program->name); + break; + } + } else { + scriptPredefinedError(program, "critter_inven_obj", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Not a critter!"); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_set_light_level +// 0x459088 +void opSetObjectLightLevel(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "obj_set_light_level", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + Rect rect; + if (lightIntensity != 0) { + if (objectSetLight(object, lightDistance, (lightIntensity * 65636) / 100, &rect) == -1) { + return; + } + } else { + if (objectSetLight(object, lightDistance, 0, &rect) == -1) { + return; + } + } + tileWindowRefreshRect(&rect, object->elevation); +} + +// 0x459170 +void opWorldmap(Program* program) +{ + scriptsRequestWorldMap(); +} + +// inven_cmds +// 0x459178 +void _op_inven_cmds(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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. + scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, (int)item); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// float_msg +// 0x459280 +void opFloatMessage(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + char* string = NULL; + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if (arg == 1) { + if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { + string = programGetString(program, opcode[arg], data[arg]); + } + } else { + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "float_msg", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (string == NULL || *string == '\0') { + textObjectsRemoveByOwner(obj); + tileWindowRefresh(); + return; + } + + if (obj->elevation != gElevation) { + 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; + tileSetCenter(gDude->tile, TILE_SET_CENTER_FLAG_0x01); + 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 (textObjectAdd(obj, string, font, color, a5, &rect) != -1) { + tileWindowRefreshRect(&rect, obj->elevation); + } +} + +// metarule +// 0x4594A0 +void opMetarule(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = (gMapHeader.flags & MAP_SAVED) == 0; + break; + case METARULE_ELEVATOR: + scriptsRequestElevator(scriptGetSelf(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 = queueHasEvent((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 = carGetCity(); + break; + case METARULE_GIVE_CAR_TO_PARTY: + result = _wmCarGiveToParty(); + break; + case METARULE_GIVE_CAR_GAS: + result = carAddFuel(param); + break; + case METARULE_SKILL_CHECK_TAG: + result = skillIsTagged(param); + break; + case METARULE_DROP_ALL_INVEN: + if (1) { + Object* object = (Object*)param; + result = _item_drop_all(object, object->tile); + if (gDude == object) { + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + interfaceRenderArmorClass(false); + } + } + break; + case METARULE_INVEN_UNWIELD_WHO: + if (1) { + Object* object = (Object*)param; + + int hand = HAND_RIGHT; + if (object == gDude) { + if (interfaceGetCurrentHand() == HAND_LEFT) { + hand = HAND_LEFT; + } + } + + result = _invenUnwieldFunc(object, hand, 0); + + if (object == gDude) { + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } else { + Object* item = critterGetItem1(object); + if (itemGetType(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) { + debugPrint("\nIntextra: Error: metarule: current_town"); + } + break; + case METARULE_LANGUAGE_FILTER: + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &result); + break; + case METARULE_VIOLENCE_FILTER: + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &result); + break; + case METARULE_WEAPON_DAMAGE_TYPE: + if (1) { + Object* object = (Object*)param; + if ((object->pid >> 24) == OBJ_TYPE_ITEM) { + if (itemGetType(object) == ITEM_TYPE_WEAPON) { + result = weaponGetDamageType(NULL, object); + break; + } + } else { + if (buildFid(5, 10, 0, 0, 0) == object->fid) { + result = DAMAGE_TYPE_EXPLOSION; + break; + } + } + + scriptPredefinedError(program, "metarule:w_damage_type", SCRIPT_ERROR_FOLLOWS); + debugPrint("Not a weapon!"); + } + break; + case METARULE_CRITTER_BARTERS: + if (1) { + Object* object = (Object*)param; + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + Proto* proto; + protoGetProto(object->pid, &proto); + if ((proto->critter.data.flags & 0x02) != 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 (protoGetProto(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 (protoGetProto(PROTO_ID_CAR_TRUNK, &proto) != -1) { + result = proto->item.data.container.maxSize; + } + } + break; + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// anim +// 0x4598BC +void opAnim(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (anim < ANIM_COUNT) { + CritterCombatData* combatData = NULL; + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + combatData = &(obj->data.critter.combat); + } + + anim = _correctDeath(obj, anim, true); + + reg_anim_begin(1); + + // TODO: Not sure about the purpose, why it handles knock down flag? + if (frame == 0) { + reg_anim_animate(obj, anim, 0); + if (anim >= ANIM_FALL_BACK && anim <= ANIM_FALL_FRONT_BLOOD) { + int fid = buildFid(1, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28); + reg_anim_17(obj, fid, -1); + } + + if (combatData != NULL) { + combatData->results &= DAM_KNOCKED_DOWN; + } + } else { + int fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); + reg_anim_animate_reverse(obj, anim, 0); + + if (anim == ANIM_PRONE_TO_STANDING) { + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_FALL_FRONT_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); + } else if (anim == ANIM_BACK_TO_STANDING) { + fid = buildFid((obj->fid & 0xF000000) >> 24, obj->fid & 0xFFF, ANIM_FALL_BACK_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); + } + + if (combatData != NULL) { + combatData->results |= DAM_KNOCKED_DOWN; + } + + reg_anim_17(obj, fid, -1); + } + + reg_anim_end(); + } else if (anim == 1000) { + if (frame < ROTATION_COUNT) { + Rect rect; + objectSetRotation(obj, frame, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + } else if (anim == 1010) { + Rect rect; + objectSetFrame(obj, frame, &rect); + tileWindowRefreshRect(&rect, gElevation); + } else { + scriptError("\nScript Error: %s: op_anim: anim out of range", program->name); + } +} + +// obj_carrying_pid_obj +// 0x459B5C +void opObjectCarryingObjectByPid(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = objectGetCarriedObjectByPid(object, pid); + } else { + scriptPredefinedError(program, "obj_carrying_pid_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, (int)result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// reg_anim_func +// 0x459C20 +void opRegAnimFunc(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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: + reg_anim_begin(param); + break; + case OP_REG_ANIM_FUNC_CLEAR: + reg_anim_clear((Object*)param); + break; + case OP_REG_ANIM_FUNC_END: + reg_anim_end(); + break; + } + } +} + +// reg_anim_animate +// 0x459CD4 +void opRegAnimAnimate(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 || (configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel) && violenceLevel >= 2)) { + if (object != NULL) { + reg_anim_animate(object, anim, delay); + } else { + scriptPredefinedError(program, "reg_anim_animate", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } + } +} + +// reg_anim_animate_reverse +// 0x459DC4 +void opRegAnimAnimateReverse(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_animate_reverse(object, anim, delay); + } else { + scriptPredefinedError(program, "reg_anim_animate_reverse", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// reg_anim_obj_move_to_obj +// 0x459E74 +void opRegAnimObjectMoveToObject(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_obj_move_to_obj(object, dest, -1, delay); + } else { + scriptPredefinedError(program, "reg_anim_obj_move_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// reg_anim_obj_run_to_obj +// 0x459F28 +void opRegAnimObjectRunToObject(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_obj_run_to_obj(object, dest, -1, delay); + } else { + scriptPredefinedError(program, "reg_anim_obj_run_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// reg_anim_obj_move_to_tile +// 0x459FDC +void opRegAnimObjectMoveToTile(Program* prg) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(prg); + data[arg] = programStackPopInt32(prg); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(prg, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_obj_move_to_tile(object, tile, object->elevation, -1, delay); + } else { + scriptPredefinedError(prg, "reg_anim_obj_move_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// reg_anim_obj_run_to_tile +// 0x45A094 +void opRegAnimObjectRunToTile(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_obj_run_to_tile(object, tile, object->elevation, -1, delay); + } else { + scriptPredefinedError(program, "reg_anim_obj_run_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// play_gmovie +// 0x45A14C +void opPlayGameMovie(Program* program) +{ + unsigned short flags[MOVIE_COUNT]; + memcpy(flags, word_453F9C, sizeof(word_453F9C)); + + program->flags |= PROGRAM_FLAG_0x20; + + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to play_gmovie", program->name); + } + + gameDialogDisable(); + + if (gameMoviePlay(data, word_453F9C[data]) == -1) { + debugPrint("\nError playing movie %d!", data); + } + + gameDialogEnable(); + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// add_mult_objs_to_inven +// 0x45A200 +void opAddMultipleObjectsToInventory(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 (itemAdd(object, item, quantity) == 0) { + Rect rect; + _obj_disconnect(item, &rect); + tileWindowRefreshRect(&rect, item->elevation); + } +} + +// rm_mult_objs_from_inven +// 0x45A2D4 +void opRemoveMultipleObjectsFromInventory(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (itemRemove(owner, item, quantity) == 0) { + Rect updatedRect; + _obj_connect(item, 1, 0, &updatedRect); + if (itemWasEquipped) { + if (owner == gDude) { + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } + } + } + } + + programStackPushInt32(program, quantity); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// get_month +// 0x45A40C +void opGetMonth(Program* program) +{ + int month; + gameTimeGetDate(&month, NULL, NULL); + + programStackPushInt32(program, month); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// get_day +// 0x45A43C +void opGetDay(Program* program) +{ + int day; + gameTimeGetDate(NULL, &day, NULL); + + programStackPushInt32(program, day); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// explosion +// 0x45A46C +void opExplosion(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + debugPrint("\nError: explosion: bad tile_num!"); + return; + } + + int minDamage = 1; + if (maxDamage == 0) { + minDamage = 0; + } + + scriptsRequestExplosion(tile, elevation, minDamage, maxDamage); +} + +// days_since_visited +// 0x45A528 +void opGetDaysSinceLastVisit(Program* program) +{ + int days; + + if (gMapHeader.field_38 != 0) { + days = (gameTimeGetTime() - gMapHeader.field_38) / GAME_TIME_TICKS_PER_DAY; + } else { + days = -1; + } + + programStackPushInt32(program, days); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// gsay_start +// 0x45A56C +void _op_gsay_start(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + + if (_gdialogStart() != 0) { + program->flags &= ~PROGRAM_FLAG_0x20; + programFatalError("Error starting dialog."); + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// gsay_end +// 0x45A5B0 +void _op_gsay_end(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + _gdialogGo(); + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// gsay_reply +// 0x45A5D4 +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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = programGetString(program, opcode[arg], data[arg]); + } else { + programFatalError("script error: %s: invalid arg %d to gsay_reply", program->name, arg); + } + } else { + programFatalError("script error: %s: invalid arg %d to gsay_reply", program->name, arg); + } + } + } + + int messageListId = data[1]; + int messageId = data[0]; + + if (string != NULL) { + gameDialogSetTextReply(program, messageListId, string); + } else { + gameDialogSetMessageReply(program, messageListId, messageId); + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// gsay_option +// 0x45A6C4 +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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = programGetString(program, opcode[arg], data[arg]); + } else { + programFatalError("script error: %s: invalid arg %d to gsay_option", program->name, arg); + } + } else { + programFatalError("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 = programGetString(program, opcode[1], data[1]); + if (string != NULL) { + gameDialogAddTextOptionWithProcIdentifier(data[3], string, procName, reaction); + } else { + gameDialogAddMessageOptionWithProcIdentifier(data[3], data[2], procName, reaction); + } + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 3 to sayOption"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if (string != NULL) { + gameDialogAddTextOptionWithProc(data[3], string, proc, reaction); + program->flags &= ~PROGRAM_FLAG_0x20; + } else { + gameDialogAddMessageOptionWithProc(data[3], data[2], proc, reaction); + program->flags &= ~PROGRAM_FLAG_0x20; + } +} + +// gsay_message +// 0x45A8AC +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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = programGetString(program, opcode[arg], data[arg]); + } else { + programFatalError("script error: %s: invalid arg %d to gsay_message", program->name, arg); + } + } else { + programFatalError("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) { + gameDialogSetTextReply(program, messageListId, string); + } else { + gameDialogSetMessageReply(program, messageListId, messageId); + } + + gameDialogAddMessageOptionWithProcIdentifier(-2, -2, NULL, 50); + _gdialogSayMessage(); + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// giq_option +// 0x45A9B4 +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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(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 = programGetString(program, opcode[arg], data[arg]); + } else { + programFatalError("script error: %s: invalid arg %d to giq_option", program->name, arg); + } + } else { + programFatalError("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(gDude, STAT_INTELLIGENCE); + intelligence += perkGetRank(gDude, 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 = programGetString(program, opcode[1], data[1]); + if (string != NULL) { + gameDialogAddTextOptionWithProcIdentifier(messageListId, string, procName, reaction); + } else { + gameDialogAddMessageOptionWithProcIdentifier(messageListId, messageId, procName, reaction); + } + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 4 to sayOption"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if (string != NULL) { + gameDialogAddTextOptionWithProc(messageListId, string, proc, reaction); + program->flags &= ~PROGRAM_FLAG_0x20; + } else { + gameDialogAddMessageOptionWithProc(messageListId, messageId, proc, reaction); + program->flags &= ~PROGRAM_FLAG_0x20; + } +} + +// poison +// 0x45AB90 +void opPoison(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to poison", program->name, arg); + } + } + + Object* obj = (Object*)data[1]; + int amount = data[0]; + + if (obj == NULL) { + scriptPredefinedError(program, "poison", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (critterAdjustPoison(obj, amount) != 0) { + debugPrint("\nScript Error: poison: adjust failed!"); + } +} + +// get_poison +// 0x45AC44 +void opGetPoison(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to get_poison", program->name); + } + + Object* obj = (Object*)data; + + int poison = 0; + if (obj != NULL) { + if (obj->pid >> 24 == 1) { + poison = critterGetPoison(obj); + } else { + debugPrint("\nScript Error: get_poison: who is not a critter!"); + } + } else { + scriptPredefinedError(program, "get_poison", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, poison); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// party_add +// 0x45ACF4 +void opPartyAdd(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to party_add", program->name); + } + + Object* object = (Object*)data; + if (object == NULL) { + scriptPredefinedError(program, "party_add", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + partyMemberAdd(object); +} + +// party_remove +// 0x45AD68 +void opPartyRemove(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to party_remove", program->name); + } + + Object* object = (Object*)data; + if (object == NULL) { + scriptPredefinedError(program, "party_remove", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + partyMemberRemove(object); +} + +// reg_anim_animate_forever +// 0x45ADDC +void opRegAnimAnimateForever(Program* prg) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(prg); + data[arg] = programStackPopInt32(prg); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(prg, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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) { + reg_anim_animate_forever(obj, anim, -1); + } else { + scriptPredefinedError(prg, "reg_anim_animate_forever", SCRIPT_ERROR_OBJECT_IS_NULL); + } + } +} + +// critter_injure +// 0x45AE8C +void opCritterInjure(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to critter_injure", program->name, arg); + } + } + + Object* critter = (Object*)data[1]; + int flags = data[0]; + + if (critter == NULL) { + scriptPredefinedError(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 == gDude) { + if ((flags & (DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT)) != 0) { + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + interfaceUpdateItems(true, leftItemAction, rightItemAction); + } + } +} + +// combat_is_initialized +// 0x45AF7C +void opCombatIsInitialized(Program* program) +{ + programStackPushInt32(program, isInCombat()); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// gdialog_barter +// 0x45AFA0 +void _op_gdialog_barter(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to gdialog_barter", program->name); + } + + if (gameDialogBarter(data) == -1) { + debugPrint("\nScript Error: gdialog_barter: failed"); + } +} + +// difficulty_level +// 0x45B010 +void opGetGameDifficulty(Program* program) +{ + int gameDifficulty; + if (!configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) { + gameDifficulty = GAME_DIFFICULTY_NORMAL; + } + + programStackPushInt32(program, gameDifficulty); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// running_burning_guy +// 0x45B05C +void opGetRunningBurningGuy(Program* program) +{ + int runningBurningGuy; + if (!configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_BURNING_GUY_KEY, &runningBurningGuy)) { + runningBurningGuy = 1; + } + + programStackPushInt32(program, runningBurningGuy); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// inven_unwield +void _op_inven_unwield(Program* program) +{ + Object* obj; + int v1; + + obj = scriptGetSelf(program); + v1 = 1; + + if (obj == gDude && !interfaceGetCurrentHand()) { + v1 = 0; + } + + _inven_unwield(obj, v1); +} + +// obj_is_locked +// 0x45B0D8 +void opObjectIsLocked(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_is_locked", program->name); + } + + Object* object = (Object*)data; + + bool locked = false; + if (object != NULL) { + locked = objectIsLocked(object); + } else { + scriptPredefinedError(program, "obj_is_locked", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, locked); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_lock +// 0x45B16C +void opObjectLock(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_lock", program->name); + } + + Object* object = (Object*)data; + + if (object != NULL) { + objectLock(object); + } else { + scriptPredefinedError(program, "obj_lock", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// obj_unlock +// 0x45B1E0 +void opObjectUnlock(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_unlock", program->name); + } + + Object* object = (Object*)data; + + if (object != NULL) { + objectUnlock(object); + } else { + scriptPredefinedError(program, "obj_unlock", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// obj_is_open +// 0x45B254 +void opObjectIsOpen(Program* s) +{ + opcode_t opcode = programStackPopInt16(s); + int data = programStackPopInt32(s); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(s, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_is_open", s->name); + } + + Object* object = (Object*)data; + + bool isOpen = false; + if (object != NULL) { + isOpen = objectIsOpen(object); + } else { + scriptPredefinedError(s, "obj_is_open", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(s, isOpen); + programStackPushInt16(s, VALUE_TYPE_INT); +} + +// obj_open +// 0x45B2E8 +void opObjectOpen(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_open", program->name); + } + + Object* object = (Object*)data; + + if (object != NULL) { + objectOpen(object); + } else { + scriptPredefinedError(program, "obj_open", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// obj_close +// 0x45B35C +void opObjectClose(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_close", program->name); + } + + Object* object = (Object*)data; + + if (object != NULL) { + objectClose(object); + } else { + scriptPredefinedError(program, "obj_close", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// game_ui_disable +// 0x45B3D0 +void opGameUiDisable(Program* program) +{ + gameUiDisable(0); +} + +// game_ui_enable +// 0x45B3D8 +void opGameUiEnable(Program* program) +{ + gameUiEnable(); +} + +// game_ui_is_disabled +// 0x45B3E0 +void opGameUiIsDisabled(Program* program) +{ + programStackPushInt32(program, gameUiIsDisabled()); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// gfade_out +// 0x45B404 +void opFadeOut(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to gfade_out", program->name); + } + + if (data != 0) { + paletteFadeTo(gPaletteBlack); + } else { + scriptPredefinedError(program, "gfade_out", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// gfade_in +// 0x45B47C +void opFadeIn(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to gfade_in", program->name); + } + + if (data != 0) { + paletteFadeTo(_cmap); + } else { + scriptPredefinedError(program, "gfade_in", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// item_caps_total +// 0x45B4F4 +void opItemCapsTotal(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to item_caps_total", program->name); + } + + Object* object = (Object*)data; + + int amount = 0; + if (object != NULL) { + amount = itemGetTotalCaps(object); + } else { + scriptPredefinedError(program, "item_caps_total", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, amount); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// item_caps_adjust +// 0x45B588 +void opItemCapsAdjust(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = itemCapsAdjust(object, amount); + } else { + scriptPredefinedError(program, "item_caps_adjust", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, rc); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// anim_action_frame +void _op_anim_action_frame(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = buildFid((object->fid & 0xF000000) >> 24, object->fid & 0xFFF, anim, 0, object->rotation); + CacheEntry* frmHandle; + Art* frm = artLock(fid, &frmHandle); + if (frm != NULL) { + actionFrame = artGetActionFrame(frm); + artUnlock(frmHandle); + } + } else { + scriptPredefinedError(program, "anim_action_frame", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, actionFrame); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// reg_anim_play_sfx +// 0x45B740 +void opRegAnimPlaySfx(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if (arg == 1) { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("script error: %s: invalid arg %d to reg_anim_play_sfx", program->name, arg); + } + } else { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 = programGetString(program, opcode[1], name); + if (soundEffectName == NULL) { + scriptPredefinedError(program, "reg_anim_play_sfx", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Can't match string!"); + } + + if (obj != NULL) { + reg_anim_play_sfx(obj, soundEffectName, delay); + } else { + scriptPredefinedError(program, "reg_anim_play_sfx", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// critter_mod_skill +// 0x45B840 +void opCritterModifySkill(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (critter->pid >> 24 == OBJ_TYPE_CRITTER) { + if (critter == gDude) { + int normalizedPoints = abs(points); + if (skillIsTagged(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++) { + skillAddForce(gDude, skill); + } + } else { + // Decrement skill points one by one. + for (int it = 0; it < normalizedPoints; it++) { + skillSubForce(gDude, skill); + } + } + + // TODO: Checking for critter is dude twice probably means this + // is inlined function. + if (critter == gDude) { + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + interfaceUpdateItems(false, leftItemAction, rightItemAction); + } + } else { + scriptPredefinedError(program, "critter_mod_skill", SCRIPT_ERROR_FOLLOWS); + debugPrint(" Can't modify anyone except obj_dude!"); + } + } + } else { + scriptPredefinedError(program, "critter_mod_skill", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, 0); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// sfx_build_char_name +// 0x45B9C4 +void opSfxBuildCharName(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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, sfxBuildCharName(obj, anim, extra)); + stringOffset = programPushString(program, soundEffectName); + } else { + scriptPredefinedError(program, "sfx_build_char_name", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_ambient_name +// 0x45BAA8 +void opSfxBuildAmbientName(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to sfx_build_ambient_name", program->name); + } + + char* baseName = programGetString(program, opcode, data); + + char soundEffectName[16]; + strcpy(soundEffectName, gameSoundBuildAmbientSoundEffectName(baseName)); + + int stringOffset = programPushString(program, soundEffectName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_interface_name +// 0x45BB54 +void opSfxBuildInterfaceName(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to sfx_build_interface_name", program->name); + } + + char* baseName = programGetString(program, opcode, data); + + char soundEffectName[16]; + strcpy(soundEffectName, gameSoundBuildInterfaceName(baseName)); + + int stringOffset = programPushString(program, soundEffectName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_item_name +// 0x45BC00 +void opSfxBuildItemName(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to sfx_build_item_name", program->name); + } + + const char* baseName = programGetString(program, opcode, data); + + char soundEffectName[16]; + strcpy(soundEffectName, gameSoundBuildInterfaceName(baseName)); + + int stringOffset = programPushString(program, soundEffectName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_weapon_name +// 0x45BCAC +void opSfxBuildWeaponName(Program* program) +{ + opcode_t opcode[4]; + int data[4]; + + for (int arg = 0; arg < 4; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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, sfxBuildWeaponName(weaponSfxType, weapon, hitMode, target)); + + int stringOffset = programPushString(program, soundEffectName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_scenery_name +// 0x45BD7C +void opSfxBuildSceneryName(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to sfx_build_scenery_name", program->name, arg); + } + } + + int action = data[1]; + int actionType = data[0]; + + char* baseName = programGetString(program, opcode[2], data[2]); + + char soundEffectName[16]; + strcpy(soundEffectName, sfxBuildSceneryName(actionType, action, baseName)); + + int stringOffset = programPushString(program, soundEffectName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// sfx_build_open_name +// 0x45BE58 +void opSfxBuildOpenName(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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, sfxBuildOpenName(object, action)); + + stringOffset = programPushString(program, soundEffectName); + } else { + scriptPredefinedError(program, "sfx_build_open_name", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// attack_setup +// 0x45BF38 +void opAttackSetup(Program* program) +{ + opcode_t opcodes[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcodes[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcodes[arg], data[arg]); + } + + if ((opcodes[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 (!critterIsActive(attacker) || (attacker->flags & OBJECT_HIDDEN) != 0) { + debugPrint("\n But is already dead or invisible"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if (!critterIsActive(defender) || (defender->flags & OBJECT_HIDDEN) != 0) { + debugPrint("\n But target is already dead or invisible"); + program->flags &= ~PROGRAM_FLAG_0x20; + return; + } + + if ((defender->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { + debugPrint("\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; + } + + scriptsRequestCombat(&attack); + } + } + + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// destroy_mult_objs +// 0x45C0E8 +void opDestroyMultipleObjects(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to destroy_mult_objs", program->name, arg); + } + } + + Object* object = (Object*)data[1]; + int quantity = data[0]; + + Object* self = scriptGetSelf(program); + bool isSelf = self == object; + + int result = 0; + + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + _combat_delete_critter(object); + } + + Object* owner = objectGetOwner(object); + if (owner != NULL) { + int quantityToDestroy = _item_count(owner, object); + if (quantityToDestroy > quantity) { + quantityToDestroy = quantity; + } + + itemRemove(owner, object, quantityToDestroy); + + if (owner == gDude) { + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(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 { + reg_anim_clear(object); + objectDestroy(object, NULL); + } + + result = quantityToDestroy; + } else { + reg_anim_clear(object); + + Rect rect; + objectDestroy(object, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); + + program->flags &= ~PROGRAM_FLAG_0x20; + + if (isSelf) { + program->flags |= PROGRAM_FLAG_0x0100; + } +} + +// use_obj_on_obj +// 0x45C290 +void opUseObjectOnObject(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (target == NULL) { + scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + Script* script; + int sid = scriptGetSid(program); + if (scriptGetScript(sid, &script) == -1) { + // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. + scriptPredefinedError(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + Object* self = scriptGetSelf(program); + if ((self->pid >> 24) == OBJ_TYPE_CRITTER) { + _action_use_an_item_on_object(self, target, item); + } else { + _obj_use_item_on(self, target, item); + } +} + +// endgame_slideshow +// 0x45C3B0 +void opEndgameSlideshow(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + scriptsRequestEndgame(); + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// move_obj_inven_to_obj +// 0x45C3D0 +void opMoveObjectInventoryToObject(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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) { + scriptPredefinedError(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + if (object2 == NULL) { + scriptPredefinedError(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); + return; + } + + Object* oldArmor = NULL; + Object* item2 = NULL; + if (object1 == gDude) { + oldArmor = critterGetArmor(object1); + } else { + item2 = critterGetItem2(object1); + } + + if (object1 != gDude && 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 == gDude) { + if (oldArmor != NULL) { + _adjust_ac(gDude, oldArmor, NULL); + } + + _proto_dude_update_gender(); + + bool animated = !gameUiIsDisabled(); + interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } +} + +// endgame_movie +// 0x45C54C +void opEndgameMovie(Program* program) +{ + program->flags |= PROGRAM_FLAG_0x20; + endgamePlayMovie(); + program->flags &= ~PROGRAM_FLAG_0x20; +} + +// obj_art_fid +// 0x45C56C +void opGetObjectFid(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 { + scriptPredefinedError(program, "obj_art_fid", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, fid); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// art_anim +// 0x45C5F8 +void opGetFidAnim(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to art_anim", program->name); + } + + programStackPushInt32(program, (data & 0xFF0000) >> 16); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// party_member_obj +// 0x45C66C +void opGetPartyMember(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to party_member_obj", program->name); + } + + Object* object = partyMemberFindByPid(data); + programStackPushInt32(program, (int)object); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// rotation_to_tile +// 0x45C6DC +void opGetRotationToTile(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg %d to rotation_to_tile", program->name, arg); + } + } + + int tile1 = data[1]; + int tile2 = data[0]; + + int rotation = tileGetRotationTo(tile1, tile2); + programStackPushInt32(program, rotation); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// jam_lock +// 0x45C778 +void opJamLock(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to jam_lock", program->name); + } + + Object* object = (Object*)data; + + objectJamLock(object); +} + +// gdialog_set_barter_mod +// 0x45C7D4 +void opGameDialogSetBarterMod(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to gdialog_set_barter_mod", program->name); + } + + gameDialogSetBarterModifier(data); +} + +// combat_difficulty +// 0x45C830 +void opGetCombatDifficulty(Program* program) +{ + int combatDifficulty; + if (!configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficulty)) { + combatDifficulty = 0; + } + + programStackPushInt32(program, combatDifficulty); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_on_screen +// 0x45C878 +void opObjectOnScreen(Program* program) +{ + Rect rect; + rectCopy(&rect, &stru_453FC0); + + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_on_screen", program->name); + } + + Object* object = (Object*)data; + + int result = 0; + + if (object != NULL) { + if (gElevation == object->elevation) { + Rect objectRect; + objectGetRect(object, &objectRect); + + if (rectIntersection(&objectRect, &rect, &objectRect) == 0) { + result = 1; + } + } + } else { + scriptPredefinedError(program, "obj_on_screen", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + //debugPrint("ObjOnScreen: %d\n", result); + programStackPushInt32(program, result); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_is_fleeing +// 0x45C93C +void opCritterIsFleeing(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 { + scriptPredefinedError(program, "critter_is_fleeing", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + programStackPushInt32(program, fleeing); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// critter_set_flee_state +// 0x45C9DC +void opCritterSetFleeState(Program* program) +{ + opcode_t opcode[2]; + int data[2]; + + for (int arg = 0; arg < 2; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 { + scriptPredefinedError(program, "critter_set_flee_state", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// terminate_combat +// 0x45CA84 +void opTerminateCombat(Program* program) +{ + if (isInCombat()) { + _game_user_wants_to_quit = 1; + Object* self = scriptGetSelf(program); + if (self != NULL) { + if ((self->pid >> 24) == 1) { + self->data.critter.combat.maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; + self->data.critter.combat.whoHitMe = NULL; + _combatAIInfoSetLastTarget(self, NULL); + } + } + } +} + +// debug_msg +// 0x45CAC8 +void opDebugMessage(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("script error: %s: invalid arg to debug_msg", program->name); + } + + char* string = programGetString(program, opcode, data); + + if (string != NULL) { + bool showScriptMessages = false; + configGetBool(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); + if (showScriptMessages) { + debugPrint("\n"); + debugPrint(string); + } + } +} + +// critter_stop_attacking +// 0x45CB70 +void opCritterStopAttacking(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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 { + scriptPredefinedError(program, "critter_stop_attacking", SCRIPT_ERROR_OBJECT_IS_NULL); + } +} + +// tile_contains_pid_obj +// 0x45CBF8 +void opTileGetObjectWithPid(Program* program) +{ + opcode_t opcode[3]; + int data[3]; + + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + + if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { + programFatalError("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 = objectFindFirstAtLocation(elevation, tile); + while (object != NULL) { + if (object->pid == pid) { + found = object; + break; + } + object = objectFindNextAtLocation(); + } + } + + programStackPushInt32(program, (int)found); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// obj_name +// 0x45CCC8 +void opGetObjectName(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to obj_name", program->name); + } + + Object* obj = (Object*)data; + if (obj != NULL) { + _strName = objectGetName(obj); + } else { + scriptPredefinedError(program, "obj_name", SCRIPT_ERROR_OBJECT_IS_NULL); + } + + int stringOffset = programPushString(program, _strName); + + programStackPushInt32(program, stringOffset); + programStackPushInt16(program, VALUE_TYPE_DYNAMIC_STRING); +} + +// get_pc_stat +// 0x45CD64 +void opGetPcStat(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("script error: %s: invalid arg to get_pc_stat", program->name); + } + + int value = pcGetStat(data); + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// 0x45CDD4 +void _intExtraClose_() +{ +} + +// 0x45CDD8 +void _initIntExtra() +{ + interpreterRegisterOpcode(0x80A1, opGiveExpPoints); // op_give_exp_points + interpreterRegisterOpcode(0x80A2, opScrReturn); // op_scr_return + interpreterRegisterOpcode(0x80A3, opPlaySfx); // op_play_sfx + interpreterRegisterOpcode(0x80A4, opGetObjectName); // op_obj_name + interpreterRegisterOpcode(0x80A5, opSfxBuildOpenName); // op_sfx_build_open_name + interpreterRegisterOpcode(0x80A6, opGetPcStat); // op_get_pc_stat + interpreterRegisterOpcode(0x80A7, opTileGetObjectWithPid); // op_tile_contains_pid_obj + interpreterRegisterOpcode(0x80A8, opSetMapStart); // op_set_map_start + interpreterRegisterOpcode(0x80A9, opOverrideMapStart); // op_override_map_start + interpreterRegisterOpcode(0x80AA, opHasSkill); // op_has_skill + interpreterRegisterOpcode(0x80AB, opUsingSkill); // op_using_skill + interpreterRegisterOpcode(0x80AC, opRollVsSkill); // op_roll_vs_skill + interpreterRegisterOpcode(0x80AD, opSkillContest); // op_skill_contest + interpreterRegisterOpcode(0x80AE, opDoCheck); // op_do_check + interpreterRegisterOpcode(0x80AF, opSuccess); // op_success + interpreterRegisterOpcode(0x80B0, opCritical); // op_critical + interpreterRegisterOpcode(0x80B1, opHowMuch); // op_how_much + interpreterRegisterOpcode(0x80B2, opMarkAreaKnown); // op_mark_area_known + interpreterRegisterOpcode(0x80B3, opReactionInfluence); // op_reaction_influence + interpreterRegisterOpcode(0x80B4, opRandom); // op_random + interpreterRegisterOpcode(0x80B5, opRollDice); // op_roll_dice + interpreterRegisterOpcode(0x80B6, opMoveTo); // op_move_to + interpreterRegisterOpcode(0x80B7, opCreateObject); // op_create_object + interpreterRegisterOpcode(0x80B8, opDisplayMsg); // op_display_msg + interpreterRegisterOpcode(0x80B9, opScriptOverrides); // op_script_overrides + interpreterRegisterOpcode(0x80BA, opObjectIsCarryingObjectWithPid); // op_obj_is_carrying_obj + interpreterRegisterOpcode(0x80BB, opTileContainsObjectWithPid); // op_tile_contains_obj_pid + interpreterRegisterOpcode(0x80BC, opGetSelf); // op_self_obj + interpreterRegisterOpcode(0x80BD, opGetSource); // op_source_obj + interpreterRegisterOpcode(0x80BE, opGetTarget); // op_target_obj + interpreterRegisterOpcode(0x80BF, opGetDude); + interpreterRegisterOpcode(0x80C0, opGetObjectBeingUsed); // op_obj_being_used_with + interpreterRegisterOpcode(0x80C1, opGetLocalVar); // op_get_local_var + interpreterRegisterOpcode(0x80C2, opSetLocalVar); // op_set_local_var + interpreterRegisterOpcode(0x80C3, opGetMapVar); // op_get_map_var + interpreterRegisterOpcode(0x80C4, opSetMapVar); // op_set_map_var + interpreterRegisterOpcode(0x80C5, opGetGlobalVar); // op_get_global_var + interpreterRegisterOpcode(0x80C6, opSetGlobalVar); // op_set_global_var + interpreterRegisterOpcode(0x80C7, opGetScriptAction); // op_script_action + interpreterRegisterOpcode(0x80C8, opGetObjectType); // op_obj_type + interpreterRegisterOpcode(0x80C9, opGetItemType); // op_item_subtype + interpreterRegisterOpcode(0x80CA, opGetCritterStat); // op_get_critter_stat + interpreterRegisterOpcode(0x80CB, opSetCritterStat); // op_set_critter_stat + interpreterRegisterOpcode(0x80CC, opAnimateStand); // op_animate_stand_obj + interpreterRegisterOpcode(0x80CD, opAnimateStandReverse); // animate_stand_reverse_obj + interpreterRegisterOpcode(0x80CE, opAnimateMoveObjectToTile); // animate_move_obj_to_tile + interpreterRegisterOpcode(0x80CF, opTileInTileRect); // tile_in_tile_rect + interpreterRegisterOpcode(0x80D0, opAttackComplex); // op_attack + interpreterRegisterOpcode(0x80D1, opMakeDayTime); // op_make_daytime + interpreterRegisterOpcode(0x80D2, opTileDistanceBetween); // op_tile_distance + interpreterRegisterOpcode(0x80D3, opTileDistanceBetweenObjects); // op_tile_distance_objs + interpreterRegisterOpcode(0x80D4, opGetObjectTile); // op_tile_num + interpreterRegisterOpcode(0x80D5, opGetTileInDirection); // op_tile_num_in_direction + interpreterRegisterOpcode(0x80D6, opPickup); // op_pickup_obj + interpreterRegisterOpcode(0x80D7, opDrop); // op_drop_obj + interpreterRegisterOpcode(0x80D8, opAddObjectToInventory); // op_add_obj_to_inven + interpreterRegisterOpcode(0x80D9, opRemoveObjectFromInventory); // op_rm_obj_from_inven + interpreterRegisterOpcode(0x80DA, opWieldItem); // op_wield_obj_critter + interpreterRegisterOpcode(0x80DB, opUseObject); // op_use_obj + interpreterRegisterOpcode(0x80DC, opObjectCanSeeObject); // op_obj_can_see_obj + interpreterRegisterOpcode(0x80DD, opAttackComplex); // op_attack + interpreterRegisterOpcode(0x80DE, opStartGameDialog); // op_start_gdialog + interpreterRegisterOpcode(0x80DF, opEndGameDialog); // op_end_gdialog + interpreterRegisterOpcode(0x80E0, opGameDialogReaction); // op_dialogue_reaction + interpreterRegisterOpcode(0x80E1, opMetarule3); // op_metarule3 + interpreterRegisterOpcode(0x80E2, opSetMapMusic); // op_set_map_music + interpreterRegisterOpcode(0x80E3, opSetObjectVisibility); // op_set_obj_visibility + interpreterRegisterOpcode(0x80E4, opLoadMap); // op_load_map + interpreterRegisterOpcode(0x80E5, opWorldmapCitySetPos); // op_wm_area_set_pos + interpreterRegisterOpcode(0x80E6, opSetExitGrids); // op_set_exit_grids + interpreterRegisterOpcode(0x80E7, opAnimBusy); // op_anim_busy + interpreterRegisterOpcode(0x80E8, opCritterHeal); // op_critter_heal + interpreterRegisterOpcode(0x80E9, opSetLightLevel); // op_set_light_level + interpreterRegisterOpcode(0x80EA, opGetGameTime); // op_game_time + interpreterRegisterOpcode(0x80EB, opGetGameTimeInSeconds); // op_game_time / 10 + interpreterRegisterOpcode(0x80EC, opGetObjectElevation); // op_elevation + interpreterRegisterOpcode(0x80ED, opKillCritter); // op_kill_critter + interpreterRegisterOpcode(0x80EE, opKillCritterType); // op_kill_critter_type + interpreterRegisterOpcode(0x80EF, opCritterDamage); // op_critter_damage + interpreterRegisterOpcode(0x80F0, opAddTimerEvent); // op_add_timer_event + interpreterRegisterOpcode(0x80F1, opRemoveTimerEvent); // op_rm_timer_event + interpreterRegisterOpcode(0x80F2, opGameTicks); // op_game_ticks + interpreterRegisterOpcode(0x80F3, opHasTrait); // op_has_trait + interpreterRegisterOpcode(0x80F4, opDestroyObject); // op_destroy_object + interpreterRegisterOpcode(0x80F5, opObjectCanHearObject); // op_obj_can_hear_obj + interpreterRegisterOpcode(0x80F6, opGameTimeHour); // op_game_time_hour + interpreterRegisterOpcode(0x80F7, opGetFixedParam); // op_fixed_param + interpreterRegisterOpcode(0x80F8, opTileIsVisible); // op_tile_is_visible + interpreterRegisterOpcode(0x80F9, opGameDialogSystemEnter); // op_dialogue_system_enter + interpreterRegisterOpcode(0x80FA, opGetActionBeingUsed); // op_action_being_used + interpreterRegisterOpcode(0x80FB, opGetCritterState); // critter_state + interpreterRegisterOpcode(0x80FC, opGameTimeAdvance); // op_game_time_advance + interpreterRegisterOpcode(0x80FD, opRadiationIncrease); // op_radiation_inc + interpreterRegisterOpcode(0x80FE, opRadiationDecrease); // op_radiation_dec + interpreterRegisterOpcode(0x80FF, opCritterAttemptPlacement); // critter_attempt_placement + interpreterRegisterOpcode(0x8100, opGetObjectPid); // op_obj_pid + interpreterRegisterOpcode(0x8101, opGetCurrentMap); // op_cur_map_index + interpreterRegisterOpcode(0x8102, opCritterAddTrait); // op_critter_add_trait + interpreterRegisterOpcode(0x8103, opCritterRemoveTrait); // critter_rm_trait + interpreterRegisterOpcode(0x8104, opGetProtoData); // op_proto_data + interpreterRegisterOpcode(0x8105, opGetMessageString); // op_message_str + interpreterRegisterOpcode(0x8106, opCritterGetInventoryObject); // op_critter_inven_obj + interpreterRegisterOpcode(0x8107, opSetObjectLightLevel); // op_obj_set_light_level + interpreterRegisterOpcode(0x8108, opWorldmap); // op_scripts_request_world_map + interpreterRegisterOpcode(0x8109, _op_inven_cmds); // op_inven_cmds + interpreterRegisterOpcode(0x810A, opFloatMessage); // op_float_msg + interpreterRegisterOpcode(0x810B, opMetarule); // op_metarule + interpreterRegisterOpcode(0x810C, opAnim); // op_anim + interpreterRegisterOpcode(0x810D, opObjectCarryingObjectByPid); // op_obj_carrying_pid_obj + interpreterRegisterOpcode(0x810E, opRegAnimFunc); // op_reg_anim_func + interpreterRegisterOpcode(0x810F, opRegAnimAnimate); // op_reg_anim_animate + interpreterRegisterOpcode(0x8110, opRegAnimAnimateReverse); // op_reg_anim_animate_reverse + interpreterRegisterOpcode(0x8111, opRegAnimObjectMoveToObject); // op_reg_anim_obj_move_to_obj + interpreterRegisterOpcode(0x8112, opRegAnimObjectRunToObject); // op_reg_anim_obj_run_to_obj + interpreterRegisterOpcode(0x8113, opRegAnimObjectMoveToTile); // op_reg_anim_obj_move_to_tile + interpreterRegisterOpcode(0x8114, opRegAnimObjectRunToTile); // op_reg_anim_obj_run_to_tile + interpreterRegisterOpcode(0x8115, opPlayGameMovie); // op_play_gmovie + interpreterRegisterOpcode(0x8116, opAddMultipleObjectsToInventory); // op_add_mult_objs_to_inven + interpreterRegisterOpcode(0x8117, opRemoveMultipleObjectsFromInventory); // rm_mult_objs_from_inven + interpreterRegisterOpcode(0x8118, opGetMonth); // op_month + interpreterRegisterOpcode(0x8119, opGetDay); // op_day + interpreterRegisterOpcode(0x811A, opExplosion); // op_explosion + interpreterRegisterOpcode(0x811B, opGetDaysSinceLastVisit); // op_days_since_visited + interpreterRegisterOpcode(0x811C, _op_gsay_start); + interpreterRegisterOpcode(0x811D, _op_gsay_end); + interpreterRegisterOpcode(0x811E, _op_gsay_reply); // op_gsay_reply + interpreterRegisterOpcode(0x811F, _op_gsay_option); // op_gsay_option + interpreterRegisterOpcode(0x8120, _op_gsay_message); // op_gsay_message + interpreterRegisterOpcode(0x8121, _op_giq_option); // op_giq_option + interpreterRegisterOpcode(0x8122, opPoison); // op_poison + interpreterRegisterOpcode(0x8123, opGetPoison); // op_get_poison + interpreterRegisterOpcode(0x8124, opPartyAdd); // op_party_add + interpreterRegisterOpcode(0x8125, opPartyRemove); // op_party_remove + interpreterRegisterOpcode(0x8126, opRegAnimAnimateForever); // op_reg_anim_animate_forever + interpreterRegisterOpcode(0x8127, opCritterInjure); // op_critter_injure + interpreterRegisterOpcode(0x8128, opCombatIsInitialized); // op_is_in_combat + interpreterRegisterOpcode(0x8129, _op_gdialog_barter); // op_gdialog_barter + interpreterRegisterOpcode(0x812A, opGetGameDifficulty); // op_game_difficulty + interpreterRegisterOpcode(0x812B, opGetRunningBurningGuy); // op_running_burning_guy + interpreterRegisterOpcode(0x812C, _op_inven_unwield); // op_inven_unwield + interpreterRegisterOpcode(0x812D, opObjectIsLocked); // op_obj_is_locked + interpreterRegisterOpcode(0x812E, opObjectLock); // op_obj_lock + interpreterRegisterOpcode(0x812F, opObjectUnlock); // op_obj_unlock + interpreterRegisterOpcode(0x8131, opObjectOpen); // op_obj_open + interpreterRegisterOpcode(0x8130, opObjectIsOpen); // op_obj_is_open + interpreterRegisterOpcode(0x8132, opObjectClose); // op_obj_close + interpreterRegisterOpcode(0x8133, opGameUiDisable); // op_game_ui_disable + interpreterRegisterOpcode(0x8134, opGameUiEnable); // op_game_ui_enable + interpreterRegisterOpcode(0x8135, opGameUiIsDisabled); // op_game_ui_is_disabled + interpreterRegisterOpcode(0x8136, opFadeOut); // op_gfade_out + interpreterRegisterOpcode(0x8137, opFadeIn); // op_gfade_in + interpreterRegisterOpcode(0x8138, opItemCapsTotal); // op_item_caps_total + interpreterRegisterOpcode(0x8139, opItemCapsAdjust); // op_item_caps_adjust + interpreterRegisterOpcode(0x813A, _op_anim_action_frame); // op_anim_action_frame + interpreterRegisterOpcode(0x813B, opRegAnimPlaySfx); // op_reg_anim_play_sfx + interpreterRegisterOpcode(0x813C, opCritterModifySkill); // op_critter_mod_skill + interpreterRegisterOpcode(0x813D, opSfxBuildCharName); // op_sfx_build_char_name + interpreterRegisterOpcode(0x813E, opSfxBuildAmbientName); // op_sfx_build_ambient_name + interpreterRegisterOpcode(0x813F, opSfxBuildInterfaceName); // op_sfx_build_interface_name + interpreterRegisterOpcode(0x8140, opSfxBuildItemName); // op_sfx_build_item_name + interpreterRegisterOpcode(0x8141, opSfxBuildWeaponName); // op_sfx_build_weapon_name + interpreterRegisterOpcode(0x8142, opSfxBuildSceneryName); // op_sfx_build_scenery_name + interpreterRegisterOpcode(0x8143, opAttackSetup); // op_attack_setup + interpreterRegisterOpcode(0x8144, opDestroyMultipleObjects); // op_destroy_mult_objs + interpreterRegisterOpcode(0x8145, opUseObjectOnObject); // op_use_obj_on_obj + interpreterRegisterOpcode(0x8146, opEndgameSlideshow); // op_endgame_slideshow + interpreterRegisterOpcode(0x8147, opMoveObjectInventoryToObject); // op_move_obj_inven_to_obj + interpreterRegisterOpcode(0x8148, opEndgameMovie); // op_endgame_movie + interpreterRegisterOpcode(0x8149, opGetObjectFid); // op_obj_art_fid + interpreterRegisterOpcode(0x814A, opGetFidAnim); // op_art_anim + interpreterRegisterOpcode(0x814B, opGetPartyMember); // op_party_member_obj + interpreterRegisterOpcode(0x814C, opGetRotationToTile); // op_rotation_to_tile + interpreterRegisterOpcode(0x814D, opJamLock); // op_jam_lock + interpreterRegisterOpcode(0x814E, opGameDialogSetBarterMod); // op_gdialog_set_barter_mod + interpreterRegisterOpcode(0x814F, opGetCombatDifficulty); // op_combat_difficulty + interpreterRegisterOpcode(0x8150, opObjectOnScreen); // op_obj_on_screen + interpreterRegisterOpcode(0x8151, opCritterIsFleeing); // op_critter_is_fleeing + interpreterRegisterOpcode(0x8152, opCritterSetFleeState); // op_critter_set_flee_state + interpreterRegisterOpcode(0x8153, opTerminateCombat); // op_terminate_combat + interpreterRegisterOpcode(0x8154, opDebugMessage); // op_debug_msg + interpreterRegisterOpcode(0x8155, opCritterStopAttacking); // op_critter_stop_attacking +} + +// 0x45D878 +void _intExtraRemoveProgramReferences_() +{ +} diff --git a/src/interpreter_extra.h b/src/interpreter_extra.h new file mode 100644 index 0000000..e1362ce --- /dev/null +++ b/src/interpreter_extra.h @@ -0,0 +1,321 @@ +#ifndef INTERPRETER_EXTRA_H +#define INTERPRETER_EXTRA_H + +#include "game_movie.h" +#include "geometry.h" +#include "interpreter.h" +#include "obj_types.h" + +#include + +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; + +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 [opGetCritterState]. +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; + +extern char _Error_0[]; +extern char _aCritter[]; + +extern const int dword_453F90[3]; +extern const unsigned short word_453F9C[MOVIE_COUNT]; +extern Rect stru_453FC0; + +extern const char* _dbg_error_strs[SCRIPT_ERROR_COUNT]; +extern const int _ftList[11]; +extern char* _errStr; +extern int _last_color; +extern char* _strName; + +extern int gGameDialogReactionOrFidget; + +void scriptPredefinedError(Program* program, const char* name, int error); +void scriptError(const char* format, ...); +int tileIsVisible(int tile); +int _correctFidForRemovedItem(Object* a1, Object* a2, int a3); +void opGiveExpPoints(Program* program); +void opScrReturn(Program* program); +void opPlaySfx(Program* program); +void opSetMapStart(Program* program); +void opOverrideMapStart(Program* program); +void opHasSkill(Program* program); +void opUsingSkill(Program* program); +void opRollVsSkill(Program* program); +void opSkillContest(Program* program); +void opDoCheck(Program* program); +void opSuccess(Program* program); +void opCritical(Program* program); +void opHowMuch(Program* program); +void opMarkAreaKnown(Program* program); +void opReactionInfluence(Program* program); +void opRandom(Program* program); +void opRollDice(Program* program); +void opMoveTo(Program* program); +void opCreateObject(Program* program); +void opDestroyObject(Program* program); +void opDisplayMsg(Program* program); +void opScriptOverrides(Program* program); +void opObjectIsCarryingObjectWithPid(Program* program); +void opTileContainsObjectWithPid(Program* program); +void opGetSelf(Program* program); +void opGetSource(Program* program); +void opGetTarget(Program* program); +void opGetDude(Program* program); +void opGetObjectBeingUsed(Program* program); +void opGetLocalVar(Program* program); +void opSetLocalVar(Program* program); +void opGetMapVar(Program* program); +void opSetMapVar(Program* program); +void opGetGlobalVar(Program* program); +void opSetGlobalVar(Program* program); +void opGetScriptAction(Program* program); +void opGetObjectType(Program* program); +void opGetItemType(Program* program); +void opGetCritterStat(Program* program); +void opSetCritterStat(Program* program); +void opAnimateStand(Program* program); +void opAnimateStandReverse(Program* program); +void opAnimateMoveObjectToTile(Program* program); +void opTileInTileRect(Program* program); +void opMakeDayTime(Program* program); +void opTileDistanceBetween(Program* program); +void opTileDistanceBetweenObjects(Program* program); +void opGetObjectTile(Program* program); +void opGetTileInDirection(Program* program); +void opPickup(Program* program); +void opDrop(Program* program); +void opAddObjectToInventory(Program* program); +void opRemoveObjectFromInventory(Program* program); +void opWieldItem(Program* program); +void opUseObject(Program* program); +void opObjectCanSeeObject(Program* program); +void opAttackComplex(Program* program); +void opStartGameDialog(Program* program); +void opEndGameDialog(Program* program); +void opGameDialogReaction(Program* program); +void opMetarule3(Program* program); +void opSetMapMusic(Program* program); +void opSetObjectVisibility(Program* program); +void opLoadMap(Program* program); +void opWorldmapCitySetPos(Program* program); +void opSetExitGrids(Program* program); +void opAnimBusy(Program* program); +void opCritterHeal(Program* program); +void opSetLightLevel(Program* program); +void opGetGameTime(Program* program); +void opGetGameTimeInSeconds(Program* program); +void opGetObjectElevation(Program* program); +void opKillCritter(Program* program); +int _correctDeath(Object* critter, int anim, bool a3); +void opKillCritterType(Program* program); +void opCritterDamage(Program* program); +void opAddTimerEvent(Program* program); +void opRemoveTimerEvent(Program* program); +void opGameTicks(Program* program); +void opHasTrait(Program* program); +void opObjectCanHearObject(Program* program); +void opGameTimeHour(Program* program); +void opGetFixedParam(Program* program); +void opTileIsVisible(Program* program); +void opGameDialogSystemEnter(Program* program); +void opGetActionBeingUsed(Program* program); +void opGetCritterState(Program* program); +void opGameTimeAdvance(Program* program); +void opRadiationIncrease(Program* program); +void opRadiationDecrease(Program* program); +void opCritterAttemptPlacement(Program* program); +void opGetObjectPid(Program* program); +void opGetCurrentMap(Program* program); +void opCritterAddTrait(Program* program); +void opCritterRemoveTrait(Program* program); +void opGetProtoData(Program* program); +void opGetMessageString(Program* program); +void opCritterGetInventoryObject(Program* program); +void opSetObjectLightLevel(Program* program); +void opWorldmap(Program* program); +void _op_inven_cmds(Program* program); +void opFloatMessage(Program* program); +void opMetarule(Program* program); +void opAnim(Program* program); +void opObjectCarryingObjectByPid(Program* program); +void opRegAnimFunc(Program* program); +void opRegAnimAnimate(Program* program); +void opRegAnimAnimateReverse(Program* program); +void opRegAnimObjectMoveToObject(Program* program); +void opRegAnimObjectRunToObject(Program* program); +void opRegAnimObjectMoveToTile(Program* program); +void opRegAnimObjectRunToTile(Program* program); +void opPlayGameMovie(Program* program); +void opAddMultipleObjectsToInventory(Program* program); +void opRemoveMultipleObjectsFromInventory(Program* program); +void opGetMonth(Program* program); +void opGetDay(Program* program); +void opExplosion(Program* program); +void opGetDaysSinceLastVisit(Program* program); +void _op_gsay_start(Program* program); +void _op_gsay_end(Program* program); +void _op_gsay_reply(Program* program); +void _op_gsay_option(Program* program); +void _op_gsay_message(Program* program); +void _op_giq_option(Program* program); +void opPoison(Program* program); +void opGetPoison(Program* program); +void opPartyAdd(Program* program); +void opPartyRemove(Program* program); +void opRegAnimAnimateForever(Program* program); +void opCritterInjure(Program* program); +void opCombatIsInitialized(Program* program); +void _op_gdialog_barter(Program* program); +void opGetGameDifficulty(Program* program); +void opGetRunningBurningGuy(Program* program); +void _op_inven_unwield(Program* program); +void opObjectIsLocked(Program* program); +void opObjectLock(Program* program); +void opObjectUnlock(Program* program); +void opObjectIsOpen(Program* program); +void opObjectOpen(Program* program); +void opObjectClose(Program* program); +void opGameUiDisable(Program* program); +void opGameUiEnable(Program* program); +void opGameUiIsDisabled(Program* program); +void opFadeOut(Program* program); +void opFadeIn(Program* program); +void opItemCapsTotal(Program* program); +void opItemCapsAdjust(Program* program); +void _op_anim_action_frame(Program* program); +void opRegAnimPlaySfx(Program* program); +void opCritterModifySkill(Program* program); +void opSfxBuildCharName(Program* program); +void opSfxBuildAmbientName(Program* program); +void opSfxBuildInterfaceName(Program* program); +void opSfxBuildItemName(Program* program); +void opSfxBuildWeaponName(Program* program); +void opSfxBuildSceneryName(Program* program); +void opSfxBuildOpenName(Program* program); +void opAttackSetup(Program* program); +void opDestroyMultipleObjects(Program* program); +void opUseObjectOnObject(Program* program); +void opEndgameSlideshow(Program* program); +void opMoveObjectInventoryToObject(Program* program); +void opEndgameMovie(Program* program); +void opGetObjectFid(Program* program); +void opGetFidAnim(Program* program); +void opGetPartyMember(Program* program); +void opGetRotationToTile(Program* program); +void opJamLock(Program* program); +void opGameDialogSetBarterMod(Program* program); +void opGetCombatDifficulty(Program* program); +void opObjectOnScreen(Program* program); +void opCritterIsFleeing(Program* program); +void opCritterSetFleeState(Program* program); +void opTerminateCombat(Program* program); +void opDebugMessage(Program* program); +void opCritterStopAttacking(Program* program); +void opTileGetObjectWithPid(Program* program); +void opGetObjectName(Program* program); +void opGetPcStat(Program* program); +void _intExtraClose_(); +void _initIntExtra(); +void _intExtraRemoveProgramReferences_(); + +#endif /* INTERPRETER_EXTRA_H */ diff --git a/src/interpreter_lib.c b/src/interpreter_lib.c new file mode 100644 index 0000000..9210eeb --- /dev/null +++ b/src/interpreter_lib.c @@ -0,0 +1,1553 @@ +#include "interpreter_lib.h" + +#include "color.h" +#include "core.h" +#include "debug.h" +#include "dialog.h" +#include "interpreter_extra.h" +#include "memory_manager.h" +#include "nevs.h" +#include "widget.h" + +// 0x59D5D0 +Sound* gInterpreterSounds[INTERPRETER_SOUNDS_LENGTH]; + +// 0x59D950 +InterpreterKeyHandlerEntry gInterpreterKeyHandlerEntries[INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH]; + +// 0x59E154 +int gIntepreterAnyKeyHandlerProc; + +// Number of entries in _callbacks. +// +// 0x59E158 +int _numCallbacks; + +// 0x59E15C +Program* gInterpreterAnyKeyHandlerProgram; + +// 0x59E160 +OFF_59E160* _callbacks; + +// 0x59E164 +int _sayStartingPosition; + +// format +// 0x461850 +void opFormat(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 6 given to format\n"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 5 given to format\n"); + } + + if ((opcode[2] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 4 given to format\n"); + } + + if ((opcode[3] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 3 given to format\n"); + } + + if ((opcode[4] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 2 given to format\n"); + } + + if ((opcode[5] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid arg 1 given to format\n"); + } + + char* string = programGetString(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)) { + programFatalError("Error formatting message\n"); + } +} + +// print +// 0x461A5C +void opPrint(Program* program) +{ + _selectWindowID(program->field_84); + + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + switch (opcode & 0xF7FF) { + case VALUE_TYPE_STRING: + _interpretOutput("%s", programGetString(program, opcode, data)); + break; + case VALUE_TYPE_FLOAT: + _interpretOutput("%.5f", *((float*)&data)); + break; + case VALUE_TYPE_INT: + _interpretOutput("%d", data); + break; + } +} + +// printrect +// 0x461F1C +void opPrintRect(Program* program) +{ + _selectWindowID(program->field_84); + + opcode_t opcode[3]; + int data[3]; + + // NOTE: Original code does not use loop. + for (int arg = 0; arg < 3; arg++) { + opcode[arg] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT || data[0] > 2) { + programFatalError("Invalid arg 3 given to printrect, expecting int"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 2 given to printrect, expecting int"); + } + + char string[80]; + switch (opcode[2] & 0xF7FF) { + case VALUE_TYPE_STRING: + sprintf(string, "%s", programGetString(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])) { + programFatalError("Error in printrect"); + } +} + +// movieflags +// 0x462584 +void opSetMovieFlags(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if (!_windowSetMovieFlags(data)) { + programFatalError("Error setting movie flags\n"); + } +} + +// stopmovie +// 0x46287C +void opStopMovie(Program* program) +{ + _windowStopMovie(); + program->flags |= PROGRAM_FLAG_0x40; +} + +// deleteregion +// 0x462890 +void opDeleteRegion(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + if ((opcode & 0xF7FF) == VALUE_TYPE_INT && data != -1) { + programFatalError("Invalid type given to deleteregion"); + } + } + + _selectWindowID(program->field_84); + + const char* regionName = data != -1 ? programGetString(program, opcode, data) : NULL; + _windowDeleteRegion(regionName); +} + +// checkregion +// 0x4629A0 +void opCheckRegion(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid arg 1 given to checkregion();\n"); + } + + const char* regionName = programGetString(program, opcode, data); + + bool regionExists = _windowCheckRegionExists(regionName); + programStackPushInt32(program, regionExists); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// addregionproc +// 0x462C10 +void opAddRegionProc(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 4 name given to addregionproc"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 3 name given to addregionproc"); + } + + if ((opcode[2] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 2 name given to addregionproc"); + } + + if ((opcode[3] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 1 name given to addregionproc"); + } + + if ((opcode[4] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid name given to addregionproc"); + } + + const char* regionName = programGetString(program, opcode[4], data[4]); + _selectWindowID(program->field_84); + + if (!_windowAddRegionProc(regionName, program, data[3], data[2], data[1], data[0])) { + programFatalError("Error setting procedures to region %s\n", regionName); + } +} + +// addregionrightproc +// 0x462DDC +void opAddRegionRightProc(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 2 name given to addregionrightproc"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 1 name given to addregionrightproc"); + } + + if ((opcode[2] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid name given to addregionrightproc"); + } + + const char* regionName = programGetString(program, opcode[2], data[2]); + _selectWindowID(program->field_84); + + if (!_windowAddRegionRightProc(regionName, program, data[1], data[0])) { + programFatalError("ErrorError setting right button procedures to region %s\n", regionName); + } +} + +// saystart +// 0x4633E4 +void opSayStart(Program* program) +{ + _sayStartingPosition = 0; + + program->flags |= PROGRAM_FLAG_0x20; + int rc = _dialogStart(program); + program->flags &= ~PROGRAM_FLAG_0x20; + + if (rc != 0) { + programFatalError("Error starting dialog."); + } +} + +// saystartpos +// 0x463430 +void opSayStartPos(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + _sayStartingPosition = data; + + program->flags |= PROGRAM_FLAG_0x20; + int rc = _dialogStart(program); + program->flags &= ~PROGRAM_FLAG_0x20; + + if (rc != 0) { + programFatalError("Error starting dialog."); + } +} + +// sayreplytitle +// 0x46349C +void opSayReplyTitle(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + char* string = NULL; + if ((opcode & 0xF7FF) == VALUE_TYPE_STRING) { + string = programGetString(program, opcode, data); + } + + if (dialogSetReplyTitle(string) != 0) { + programFatalError("Error setting title."); + } +} + +// saygotoreply +// 0x463510 +void opSayGoToReply(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + char* string = NULL; + if ((opcode & 0xF7FF) == VALUE_TYPE_STRING) { + string = programGetString(program, opcode, data); + } + + if (_dialogGotoReply(string) != 0) { + programFatalError("Error during goto, couldn't find reply target %s", string); + } +} + +// saygetlastpos +// 0x4637EC +void opSayGetLastPos(Program* program) +{ + int value = _dialogGetExitPoint(); + programStackPushInt32(program, value); + programStackPushInt16(program, VALUE_TYPE_INT); +} + +// sayquit +// 0x463810 +void opSayQuit(Program* program) +{ + if (_dialogQuit() != 0) { + programFatalError("Error quitting option."); + } +} + +// saymessagetimeout +// 0x463838 +void opSayMessageTimeout(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + // TODO: What the hell is this? + if ((opcode & 0xF7FF) == 0x4000) { + programFatalError("sayMsgTimeout: invalid var type passed."); + } + + _TimeOut = data; +} + +// addbuttonflag +// 0x463A38 +void opAddButtonFlag(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 2 given to addbuttonflag"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid arg 1 given to addbuttonflag"); + } + + const char* buttonName = programGetString(program, opcode[1], data[1]); + if (!_windowSetButtonFlag(buttonName, data[0])) { + // NOTE: Original code calls programGetString one more time with the + // same params. + programFatalError("Error setting flag on button %s", buttonName); + } +} + +// addregionflag +// 0x463B10 +void opAddRegionFlag(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 2 given to addregionflag"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid arg 1 given to addregionflag"); + } + + const char* regionName = programGetString(program, opcode[1], data[1]); + if (!_windowSetRegionFlag(regionName, data[0])) { + // NOTE: Original code calls programGetString one more time with the + // same params. + programFatalError("Error setting flag on region %s", regionName); + } +} + +// addbuttonproc +// 0x4640DC +void opAddButtonProc(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 4 name given to addbuttonproc"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 3 name given to addbuttonproc"); + } + + if ((opcode[2] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 2 name given to addbuttonproc"); + } + + if ((opcode[3] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 1 name given to addbuttonproc"); + } + + if ((opcode[4] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid name given to addbuttonproc"); + } + + const char* buttonName = programGetString(program, opcode[4], data[4]); + _selectWindowID(program->field_84); + + if (!_windowAddButtonProc(buttonName, program, data[3], data[2], data[1], data[0])) { + programFatalError("Error setting procedures to button %s\n", buttonName); + } +} + +// addbuttonrightproc +// 0x4642A8 +void opAddButtonRightProc(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[0] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 2 name given to addbuttonrightproc"); + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid procedure 1 name given to addbuttonrightproc"); + } + + if ((opcode[2] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid name given to addbuttonrightproc"); + } + + const char* regionName = programGetString(program, opcode[2], data[2]); + _selectWindowID(program->field_84); + + if (!_windowAddRegionRightProc(regionName, program, data[1], data[0])) { + programFatalError("Error setting right button procedures to button %s\n", regionName); + } +} + +// showwin +// 0x4643D4 +void opShowWin(Program* program) +{ + _selectWindowID(program->field_84); + _windowDraw(); +} + +// deletebutton +// 0x4643E4 +void opDeleteButton(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + if ((opcode & 0xF7FF) == VALUE_TYPE_INT && data != -1) { + programFatalError("Invalid type given to delete button"); + } + } + + _selectWindowID(program->field_84); + + if ((opcode & 0xF7FF) == VALUE_TYPE_INT) { + if (_windowDeleteButton(NULL)) { + return; + } + } else { + const char* buttonName = programGetString(program, opcode, data); + if (_windowDeleteButton(buttonName)) { + return; + } + } + + programFatalError("Error deleting button"); +} + +// hidemouse +// 0x46489C +void opHideMouse(Program* program) +{ + mouseHideCursor(); +} + +// showmouse +// 0x4648A4 +void opShowMouse(Program* program) +{ + mouseShowCursor(); +} + +// setglobalmousefunc +// 0x4649C4 +void opSetGlobalMouseFunc(Program* Program) +{ + programFatalError("setglobalmousefunc not defined"); +} + +// loadpalettetable +// 0x464ADC +void opLoadPaletteTable(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + if ((opcode & 0xF7FF) == VALUE_TYPE_INT && data != -1) { + programFatalError("Invalid type given to loadpalettetable"); + } + } + + char* path = programGetString(program, opcode, data); + if (!colorPaletteLoad(path)) { + programFatalError(_colorError()); + } +} + +// addnamedevent +// 0x464B54 +void opAddNamedEvent(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid type given to addnamedevent"); + } + + const char* v1 = programGetString(program, opcode[1], data[1]); + _nevs_addevent(v1, program, data[0], 0); +} + +// addnamedhandler +// 0x464BE8 +void opAddNamedHandler(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + if ((opcode[1] & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid type given to addnamedhandler"); + } + + const char* v1 = programGetString(program, opcode[1], data[1]); + _nevs_addevent(v1, program, data[0], 1); +} + +// clearnamed +// 0x464C80 +void opClearNamed(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid type given to clearnamed"); + } + + char* string = programGetString(program, opcode, data); + _nevs_clearevent(string); +} + +// signalnamed +// 0x464CE4 +void opSignalNamed(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_STRING) { + programFatalError("Invalid type given to signalnamed"); + } + + char* str = programGetString(program, opcode, data); + _nevs_signal(str); +} + +// addkey +// 0x464D48 +void opAddKey(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 2; arg++) { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg %d given to addkey", arg + 1); + } + } + + int key = data[1]; + int proc = data[0]; + + if (key == -1) { + gIntepreterAnyKeyHandlerProc = proc; + gInterpreterAnyKeyHandlerProgram = program; + } else { + if (key > INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH - 1) { + programFatalError("Key out of range"); + } + + gInterpreterKeyHandlerEntries[key].program = program; + gInterpreterKeyHandlerEntries[key].proc = proc; + } +} + +// deletekey +// 0x464E24 +void opDeleteKey(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to deletekey"); + } + + int key = data; + + if (key == -1) { + gIntepreterAnyKeyHandlerProc = 0; + gInterpreterAnyKeyHandlerProgram = NULL; + } else { + if (key > INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH - 1) { + programFatalError("Key out of range"); + } + + gInterpreterKeyHandlerEntries[key].program = NULL; + gInterpreterKeyHandlerEntries[key].proc = 0; + } +} + +// setfont +// 0x464F18 +void opSetFont(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to setfont"); + } + + if (!widgetSetFont(data)) { + programFatalError("Error setting font"); + } +} + +// setflags +// 0x464F84 +void opSetTextFlags(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to setflags"); + } + + if (!widgetSetTextFlags(data)) { + programFatalError("Error setting text flags"); + } +} + +// settextcolor +// 0x464FF0 +void opSetTextColor(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 3; arg++) { + if (((opcode[arg] & 0xF7FF) != VALUE_TYPE_FLOAT && (opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) + || floats[arg] == 0.0) { + programFatalError("Invalid type given to settextcolor"); + } + } + + float r = floats[2]; + float g = floats[1]; + float b = floats[0]; + + if (!widgetSetTextColor(r, g, b)) { + programFatalError("Error setting text color"); + } +} + +// sayoptioncolor +// 0x465140 +void opSayOptionColor(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 3; arg++) { + if (((opcode[arg] & 0xF7FF) != VALUE_TYPE_FLOAT && (opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) + || floats[arg] == 0.0) { + programFatalError("Invalid type given to sayoptioncolor"); + } + } + + float r = floats[2]; + float g = floats[1]; + float b = floats[0]; + + if (dialogSetOptionColor(r, g, b)) { + programFatalError("Error setting option color"); + } +} + +// sayreplycolor +// 0x465290 +void opSayReplyColor(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + ; + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 3; arg++) { + if (((opcode[arg] & 0xF7FF) != VALUE_TYPE_FLOAT && (opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) + || floats[arg] == 0.0) { + programFatalError("Invalid type given to sayreplycolor"); + } + } + + float r = floats[2]; + float g = floats[1]; + float b = floats[0]; + + if (dialogSetReplyColor(r, g, b) != 0) { + programFatalError("Error setting reply color"); + } +} + +// sethighlightcolor +// 0x4653E0 +void opSetHighlightColor(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 3; arg++) { + if (((opcode[arg] & 0xF7FF) != VALUE_TYPE_FLOAT && (opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) + || floats[arg] == 0.0) { + programFatalError("Invalid type given to sethighlightcolor"); + } + } + + float r = floats[2]; + float g = floats[1]; + float b = floats[0]; + + if (!widgetSetHighlightColor(r, g, b)) { + programFatalError("Error setting text highlight color"); + } +} + +// sayreplyflags +// 0x465688 +void opSayReplyFlags(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to sayreplyflags"); + } + + if (!_dialogSetOptionFlags(data)) { + programFatalError("Error setting reply flags"); + } +} + +// sayoptionflags +// 0x4656F4 +void opSayOptionFlags(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to sayoptionflags"); + } + + if (!_dialogSetOptionFlags(data)) { + programFatalError("Error setting option flags"); + } +} + +// sayborder +// 0x4658B8 +void opSayBorder(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] = programStackPopInt16(program); + data[arg] = programStackPopInt32(program); + + if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode[arg], data[arg]); + } + } + + for (int arg = 0; arg < 2; arg++) { + if ((opcode[arg] & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg %d given to sayborder", arg + 1); + } + } + + if (dialogSetBorder(data[1], data[0]) != 0) { + programFatalError("Error setting dialog border"); + } +} + +// saysetspacing +// 0x465FE0 +void opSaySetSpacing(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to saysetspacing"); + } + + if (dialogSetOptionSpacing(data) != 0) { + programFatalError("Error setting option spacing"); + } +} + +// sayrestart +// 0x46604C +void opSayRestart(Program* program) +{ + if (_dialogRestart() != 0) { + programFatalError("Error restarting option"); + } +} + +// 0x466064 +void interpreterSoundCallback(void* userData, int a2) +{ + if (a2 == 1) { + Sound** sound = (Sound**)userData; + *sound = NULL; + } +} + +// 0x466070 +int interpreterSoundDelete(int value) +{ + if (value == -1) { + return 1; + } + + if ((value & 0xA0000000) == 0) { + return 0; + } + + int index = value & ~0xA0000000; + Sound* sound = gInterpreterSounds[index]; + if (sound == NULL) { + return 0; + } + + if (soundIsPlaying(sound)) { + soundStop(sound); + } + + soundDelete(sound); + + gInterpreterSounds[index] = NULL; + + return 1; +} + +// 0x466110 +int interpreterSoundPlay(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 < INTERPRETER_SOUNDS_LENGTH; index++) { + if (gInterpreterSounds[index] == NULL) { + break; + } + } + + if (index == INTERPRETER_SOUNDS_LENGTH) { + return -1; + } + + Sound* sound = gInterpreterSounds[index] = soundAllocate(v3, v5); + if (sound == NULL) { + return -1; + } + + soundSetCallback(sound, interpreterSoundCallback, &(gInterpreterSounds[index])); + + if (mode & 0x01) { + soundSetLooping(sound, 0xFFFF); + } + + if (mode & 0x1000) { + // mono + soundSetChannels(sound, 2); + } + + if (mode & 0x2000) { + // stereo + soundSetChannels(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: + debugPrint("soundPlay error: %s\n", "SOUND_NO_DEVICE"); + goto err; + case SOUND_NOT_INITIALIZED: + debugPrint("soundPlay error: %s\n", "SOUND_NOT_INITIALIZED"); + goto err; + case SOUND_NO_SOUND: + debugPrint("soundPlay error: %s\n", "SOUND_NO_SOUND"); + goto err; + case SOUND_FUNCTION_NOT_SUPPORTED: + debugPrint("soundPlay error: %s\n", "SOUND_FUNC_NOT_SUPPORTED"); + goto err; + case SOUND_NO_BUFFERS_AVAILABLE: + debugPrint("soundPlay error: %s\n", "SOUND_NO_BUFFERS_AVAILABLE"); + goto err; + case SOUND_FILE_NOT_FOUND: + debugPrint("soundPlay error: %s\n", "SOUND_FILE_NOT_FOUND"); + goto err; + case SOUND_ALREADY_PLAYING: + debugPrint("soundPlay error: %s\n", "SOUND_ALREADY_PLAYING"); + goto err; + case SOUND_NOT_PLAYING: + debugPrint("soundPlay error: %s\n", "SOUND_NOT_PLAYING"); + goto err; + case SOUND_ALREADY_PAUSED: + debugPrint("soundPlay error: %s\n", "SOUND_ALREADY_PAUSED"); + goto err; + case SOUND_NOT_PAUSED: + debugPrint("soundPlay error: %s\n", "SOUND_NOT_PAUSED"); + goto err; + case SOUND_INVALID_HANDLE: + debugPrint("soundPlay error: %s\n", "SOUND_INVALID_HANDLE"); + goto err; + case SOUND_NO_MEMORY_AVAILABLE: + debugPrint("soundPlay error: %s\n", "SOUND_NO_MEMORY"); + goto err; + case SOUND_UNKNOWN_ERROR: + debugPrint("soundPlay error: %s\n", "SOUND_ERROR"); + goto err; + } + + return index | 0xA0000000; + +err: + + soundDelete(sound); + gInterpreterSounds[index] = NULL; + return -1; +} + +// 0x46655C +int interpreterSoundPause(int value) +{ + if (value == -1) { + return 1; + } + + if ((value & 0xA0000000) == 0) { + return 0; + } + + int index = value & ~0xA0000000; + Sound* sound = gInterpreterSounds[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 +int interpreterSoundRewind(int value) +{ + if (value == -1) { + return 1; + } + + if ((value & 0xA0000000) == 0) { + return 0; + } + + int index = value & ~0xA0000000; + Sound* sound = gInterpreterSounds[index]; + if (sound == NULL) { + return 0; + } + + if (!soundIsPlaying(sound)) { + return 1; + } + + soundStop(sound); + + return soundPlay(sound) == SOUND_NO_ERROR; +} + +// 0x46662C +int interpreterSoundResume(int value) +{ + if (value == -1) { + return 1; + } + + if ((value & 0xA0000000) == 0) { + return 0; + } + + int index = value & ~0xA0000000; + Sound* sound = gInterpreterSounds[index]; + if (sound == NULL) { + return 0; + } + + int rc; + if (_soundType(sound, 0x01)) { + rc = soundPlay(sound); + } else { + rc = soundResume(sound); + } + return rc == SOUND_NO_ERROR; +} + +// soundplay +// 0x466698 +void opSoundPlay(Program* program) +{ + // TODO: Incomplete. +} + +// soundpause +// 0x466768 +void opSoundPause(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (data == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to soundpause"); + } + + interpreterSoundPause(data); +} + +// soundresume +// 0x4667C0 +void opSoundResume(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (data == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to soundresume"); + } + + interpreterSoundResume(data); +} + +// soundstop +// 0x466818 +void opSoundStop(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (data == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to soundstop"); + } + + interpreterSoundPause(data); +} + +// soundrewind +// 0x466870 +void opSoundRewind(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (data == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to soundrewind"); + } + + interpreterSoundRewind(data); +} + +// sounddelete +// 0x4668C8 +void opSoundDelete(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (data == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("Invalid arg 1 given to sounddelete"); + } + + interpreterSoundDelete(data); +} + +// SetOneOptPause +// 0x466920 +void opSetOneOptPause(Program* program) +{ + opcode_t opcode = programStackPopInt16(program); + int data = programStackPopInt32(program); + + if (opcode == VALUE_TYPE_DYNAMIC_STRING) { + programPopString(program, opcode, data); + } + + if ((opcode & 0xF7FF) != VALUE_TYPE_INT) { + programFatalError("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(); + _intExtraRemoveProgramReferences_(); +} + +// 0x4669A0 +void _intlibClose() +{ + _dialogClose(); + _intExtraClose_(); + + for (int index = 0; index < INTERPRETER_SOUNDS_LENGTH; index++) { + if (gInterpreterSounds[index] != NULL) { + interpreterSoundDelete(index | 0xA0000000); + } + } + + _nevs_close(); + + if (_callbacks != NULL) { + internal_free_safe(_callbacks, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1976 + _callbacks = NULL; + _numCallbacks = 0; + } +} + +// 0x466A04 +bool _intLibDoInput(int key) +{ + if (key < 0 || key >= INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH) { + return false; + } + + if (gInterpreterAnyKeyHandlerProgram != NULL) { + if (gIntepreterAnyKeyHandlerProc != 0) { + _executeProc(gInterpreterAnyKeyHandlerProgram, gIntepreterAnyKeyHandlerProc); + } + return true; + } + + InterpreterKeyHandlerEntry* entry = &(gInterpreterKeyHandlerEntries[key]); + if (entry->program == NULL) { + return false; + } + + if (entry->proc != 0) { + _executeProc(entry->program, entry->proc); + } + + return true; +} + +// 0x466A70 +void _initIntlib() +{ + // TODO: Incomplete. + _nevs_initonce(); + _initIntExtra(); +} + +// 0x466F6C +void _interpretRegisterProgramDeleteCallback(OFF_59E160 fn) +{ + int index; + for (index = 0; index < _numCallbacks; index++) { + if (_callbacks[index] == NULL) { + break; + } + } + + if (index == _numCallbacks) { + if (_callbacks != NULL) { + _callbacks = internal_realloc_safe(_callbacks, sizeof(*_callbacks) * (_numCallbacks + 1), __FILE__, __LINE__); // ..\\int\\INTLIB.C, 2110 + } else { + _callbacks = internal_malloc_safe(sizeof(*_callbacks), __FILE__, __LINE__); // ..\\int\\INTLIB.C, 2112 + } + _numCallbacks++; + } + + _callbacks[index] = fn; +} + +// 0x467040 +void _removeProgramReferences_(Program* program) +{ + for (int index = 0; index < INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH; index++) { + if (program == gInterpreterKeyHandlerEntries[index].program) { + gInterpreterKeyHandlerEntries[index].program = NULL; + } + } + + _intExtraRemoveProgramReferences_(); + + for (int index = 0; index < _numCallbacks; index++) { + OFF_59E160 fn = _callbacks[index]; + if (fn != NULL) { + fn(program); + } + } +} diff --git a/src/interpreter_lib.h b/src/interpreter_lib.h new file mode 100644 index 0000000..8282dd5 --- /dev/null +++ b/src/interpreter_lib.h @@ -0,0 +1,88 @@ +#ifndef INTERPRETER_LIB_H +#define INTERPRETER_LIB_H + +#include "interpreter.h" +#include "sound.h" + +#define INTERPRETER_SOUNDS_LENGTH (32) +#define INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH (256) + +typedef struct InterpreterKeyHandlerEntry { + Program* program; + int proc; +} InterpreterKeyHandlerEntry; + +typedef void (*OFF_59E160)(Program*); + +extern Sound* gInterpreterSounds[INTERPRETER_SOUNDS_LENGTH]; +extern InterpreterKeyHandlerEntry gInterpreterKeyHandlerEntries[INTERPRETER_KEY_HANDLER_ENTRIES_LENGTH]; +extern int gIntepreterAnyKeyHandlerProc; +extern int _numCallbacks; +extern Program* gInterpreterAnyKeyHandlerProgram; +extern OFF_59E160* _callbacks; +extern int _sayStartingPosition; + +void opFormat(Program* program); +void opPrint(Program* program); +void opPrintRect(Program* program); +void opSetMovieFlags(Program* program); +void opStopMovie(Program* program); +void opAddRegionProc(Program* program); +void opAddRegionRightProc(Program* program); +void opSayStart(Program* program); +void opDeleteRegion(Program* program); +void opCheckRegion(Program* program); +void opSayStartPos(Program* program); +void opSayReplyTitle(Program* program); +void opSayGoToReply(Program* program); +void opSayGetLastPos(Program* program); +void opSayQuit(Program* program); +void opSayMessageTimeout(Program* program); +void opAddButtonFlag(Program* program); +void opAddRegionFlag(Program* program); +void opAddButtonProc(Program* program); +void opAddButtonRightProc(Program* program); +void opShowWin(Program* program); +void opDeleteButton(Program* program); +void opHideMouse(Program* program); +void opShowMouse(Program* program); +void opSetGlobalMouseFunc(Program* Program); +void opLoadPaletteTable(Program* program); +void opAddNamedEvent(Program* program); +void opAddNamedHandler(Program* program); +void opClearNamed(Program* program); +void opSignalNamed(Program* program); +void opAddKey(Program* program); +void opDeleteKey(Program* program); +void opSetFont(Program* program); +void opSetTextFlags(Program* program); +void opSetTextColor(Program* program); +void opSayOptionColor(Program* program); +void opSayReplyColor(Program* program); +void opSetHighlightColor(Program* program); +void opSayReplyFlags(Program* program); +void opSayOptionFlags(Program* program); +void opSayBorder(Program* program); +void opSaySetSpacing(Program* program); +void opSayRestart(Program* program); +void interpreterSoundCallback(void* userData, int a2); +int interpreterSoundDelete(int a1); +int interpreterSoundPlay(char* fileName, int mode); +int interpreterSoundPause(int value); +int interpreterSoundRewind(int value); +int interpreterSoundResume(int value); +void opSoundPlay(Program* program); +void opSoundPause(Program* program); +void opSoundResume(Program* program); +void opSoundStop(Program* program); +void opSoundRewind(Program* program); +void opSoundDelete(Program* program); +void opSetOneOptPause(Program* program); +void _updateIntLib(); +void _intlibClose(); +bool _intLibDoInput(int key); +void _initIntlib(); +void _interpretRegisterProgramDeleteCallback(OFF_59E160 fn); +void _removeProgramReferences_(Program* program); + +#endif /* INTERPRETER_LIB_H */ diff --git a/src/inventory.c b/src/inventory.c new file mode 100644 index 0000000..c00cfba --- /dev/null +++ b/src/inventory.c @@ -0,0 +1,4900 @@ +#include "inventory.h" + +#include "actions.h" +#include "animation.h" +#include "art.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "dbox.h" +#include "debug.h" +#include "dialog.h" +#include "display_monitor.h" +#include "draw.h" +#include "game.h" +#include "game_dialog.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "light.h" +#include "map.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "random.h" +#include "reaction.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include + +// 0x46E6D0 +const int dword_46E6D0[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 +const int dword_46E6EC[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, +}; + +// 0x46E708 +const int gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_COUNT] = { + 122, // left arrow up + 123, // left arrow down + 124, // right arrow up + 125, // right arrow down +}; + +// The number of items to show in scroller. +// +// 0x519054 +int gInventorySlotsCount = 6; + +// 0x519058 +Object* _inven_dude = NULL; + +// Probably fid of armor to display in inventory dialog. +// +// 0x51905C +int _inven_pid = -1; + +// 0x519060 +bool _inven_is_initialized = false; + +// 0x519064 +int _inven_display_msg_line = 1; + +// 0x519068 +const InventoryWindowDescription gInventoryWindowDescriptions[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 +bool _dropped_explosive = false; + +// 0x5190E4 +int gInventoryScrollUpButton = -1; + +// 0x5190E8 +int gInventoryScrollDownButton = -1; + +// 0x5190EC +int gSecondaryInventoryScrollUpButton = -1; + +// 0x5190F0 +int gSecondaryInventoryScrollDownButton = -1; + +// 0x5190F4 +unsigned int gInventoryWindowDudeRotationTimestamp = 0; + +// 0x5190F8 +int gInventoryWindowDudeRotation = 0; + +// 0x5190FC +const int gInventoryWindowCursorFrmIds[INVENTORY_WINDOW_CURSOR_COUNT] = { + 286, // pointing hand + 250, // action arrow + 282, // action pick + 283, // action menu + 266, // blank +}; + +// 0x519110 +Object* _last_target = NULL; + +// 0x519114 +const 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 +const int _act_no_use[3] = { + GAME_MOUSE_ACTION_MENU_ITEM_LOOK, + GAME_MOUSE_ACTION_MENU_ITEM_DROP, + GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, +}; + +// 0x519130 +const int _act_just_use[3] = { + GAME_MOUSE_ACTION_MENU_ITEM_LOOK, + GAME_MOUSE_ACTION_MENU_ITEM_USE, + GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, +}; + +// 0x51913C +const int _act_nothing[2] = { + GAME_MOUSE_ACTION_MENU_ITEM_LOOK, + GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, +}; + +// 0x519144 +const 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 +const int _act_weap2[3] = { + GAME_MOUSE_ACTION_MENU_ITEM_LOOK, + GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD, + GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, +}; + +// 0x59E79C +CacheEntry* _mt_key[8]; + +// 0x59E7BC +CacheEntry* _ikey[OFF_59E7BC_COUNT]; + +// 0x59E7EC +int _target_stack_offset[10]; + +// inventory.msg +// +// 0x59E814 +MessageList gInventoryMessageList; + +// 0x59E81C +Object* _target_stack[10]; + +// 0x59E844 +int _stack_offset[10]; + +// 0x59E86C +Object* _stack[10]; + +// 0x59E894 +int _mt_wid; + +// 0x59E898 +int _barter_mod; + +// 0x59E89C +int _btable_offset; + +// 0x59E8A0 +int _ptable_offset; + +// 0x59E8A4 +Inventory* _ptable_pud; + +// 0x59E8A8 +InventoryCursorData gInventoryCursorData[INVENTORY_WINDOW_CURSOR_COUNT]; + +// 0x59E934 +Object* _ptable; + +// 0x59E938 +InventoryPrintItemDescriptionHandler* gInventoryPrintItemDescriptionHandler; + +// 0x59E93C +int _im_value; + +// 0x59E940 +int gInventoryCursor; + +// 0x59E944 +Object* _btable; + +// 0x59E948 +int _target_curr_stack; + +// 0x59E94C +Inventory* _btable_pud; + +// 0x59E950 +bool _inven_ui_was_disabled; + +// 0x59E954 +Object* gInventoryArmor; + +// 0x59E958 +Object* gInventoryLeftHandItem; + +// Rotating character's fid. +// +// 0x59E95C +int gInventoryWindowDudeFid; + +// 0x59E960 +Inventory* _pud; + +// 0x59E964 +int gInventoryWindow; + +// item2 +// 0x59E968 +Object* gInventoryRightHandItem; + +// 0x59E96C +int _curr_stack; + +// 0x59E970 +int gInventoryWindowMaxY; + +// 0x59E974 +int gInventoryWindowMaxX; + +// 0x59E978 +Inventory* _target_pud; + +// 0x59E97C +int _barter_back_win; + +// 0x46E724 +void _inven_reset_dude() +{ + _inven_dude = gDude; + _inven_pid = 0x1000000; +} + +// inventory_msg_init +// 0x46E73C +int inventoryMessageListInit() +{ + char path[MAX_PATH]; + + if (!messageListInit(&gInventoryMessageList)) + return -1; + + sprintf(path, "%s%s", asc_5186C8, "inventry.msg"); + if (!messageListLoad(&gInventoryMessageList, path)) + return -1; + + return 0; +} + +// inventory_msg_free +// 0x46E7A0 +int inventoryMessageListFree() +{ + messageListFree(&gInventoryMessageList); + return 0; +} + +// 0x46E7B0 +void inventoryOpen() +{ + if (isInCombat()) { + if (_combat_whose_turn() != _inven_dude) { + return; + } + } + + if (inventoryCommonInit() == -1) { + return; + } + + if (isInCombat()) { + if (_inven_dude == gDude) { + int actionPointsRequired = 4 - 2 * perkGetRank(_inven_dude, PERK_QUICK_POCKETS); + if (actionPointsRequired > 0 && actionPointsRequired > gDude->data.critter.combat.ap) { + // You don't have enough action points to use inventory + MessageListItem messageListItem; + messageListItem.num = 19; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + // NOTE: Uninline. + inventoryCommonFree(); + + return; + } + + if (actionPointsRequired > 0) { + if (actionPointsRequired > gDude->data.critter.combat.ap) { + gDude->data.critter.combat.ap = 0; + } else { + gDude->data.critter.combat.ap -= actionPointsRequired; + } + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + } + } + + Object* oldArmor = critterGetArmor(_inven_dude); + bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_NORMAL); + reg_anim_clear(_inven_dude); + inventoryRenderSummary(); + _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); + inventorySetCursor(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() == 5) { + break; + } + + if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X) { + showQuitConfirmationDialog(); + } 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] -= gInventorySlotsCount; + 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 - gInventorySlotsCount; + 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 (gInventorySlotsCount + _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 = gInventorySlotsCount + _stack_offset[_curr_stack]; + int v13 = v12 + gInventorySlotsCount; + _stack_offset[_curr_stack] = v12; + int v14 = _pud->length; + if (v13 >= _pud->length) { + int v15 = v14 - gInventorySlotsCount; + _stack_offset[_curr_stack] = v14 - gInventorySlotsCount; + 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 ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + } else if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + inventoryRenderSummary(); + windowRefresh(gInventoryWindow); + } + } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if (keyCode >= 1000 && keyCode <= 1008) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_NORMAL); + } else { + _inven_pickup(keyCode, _stack_offset[_curr_stack]); + } + } + } + } + } + + _inven_dude = _stack[0]; + _adjust_fid(); + + if (_inven_dude == gDude) { + Rect rect; + objectSetFid(_inven_dude, gInventoryWindowDudeFid, &rect); + tileWindowRefreshRect(&rect, _inven_dude->elevation); + } + + Object* newArmor = critterGetArmor(_inven_dude); + if (_inven_dude == gDude) { + if (oldArmor != newArmor) { + interfaceRenderArmorClass(true); + } + } + + _exit_inventory(isoWasEnabled); + + // NOTE: Uninline. + inventoryCommonFree(); + + if (_inven_dude == gDude) { + interfaceUpdateItems(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; + gInventorySlotsCount = 6; + _pud = &(_inven_dude->data.inventory); + _stack[0] = _inven_dude; + + if (inventoryWindowType <= INVENTORY_WINDOW_TYPE_LOOT) { + const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); + gInventoryWindow = windowCreate(80, 0, windowDescription->width, windowDescription->height, 257, 20); + gInventoryWindowMaxX = windowDescription->width + 80; + gInventoryWindowMaxY = windowDescription->height; + + unsigned char* dest = windowGetBuffer(gInventoryWindow); + int backgroundFid = buildFid(6, windowDescription->field_0, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + blitBufferToBuffer(backgroundFrmData, windowDescription->width, windowDescription->height, windowDescription->width, dest, windowDescription->width); + artUnlock(backgroundFrmHandle); + } + + gInventoryPrintItemDescriptionHandler = displayMonitorAddMessage; + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + if (_barter_back_win == -1) { + exit(1); + } + + gInventorySlotsCount = 3; + + gInventoryWindow = windowCreate(80, 290, 480, 180, 257, 0); + gInventoryWindowMaxX = 560; + gInventoryWindowMaxY = 470; + + unsigned char* dest = windowGetBuffer(gInventoryWindow); + unsigned char* src = windowGetBuffer(_barter_back_win); + blitBufferToBuffer(src + 80, 480, 180, _scr_size.right - _scr_size.left + 1, dest, 480); + + gInventoryPrintItemDescriptionHandler = gameDialogRenderSupplementaryMessage; + } + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + // Create invsibile buttons representing character's inventory item + // slots. + for (int index = 0; index < gInventorySlotsCount; index++) { + int btn = buttonCreate(gInventoryWindow, 176, 48 * (gInventorySlotsCount - index - 1) + 37, 64, 48, 999 + gInventorySlotsCount - index, -1, 999 + gInventorySlotsCount - index, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + } + + int eventCode = 2005; + int y = 277; + + // 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 = buttonCreate(gInventoryWindow, 297, y, 64, 48, eventCode, -1, eventCode, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + eventCode -= 1; + y -= 48; + } + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + int y1 = 35; + int y2 = 20; + + for (int index = 0; index < gInventorySlotsCount; index++) { + int btn; + + // Invsibile button representing left inventory slot. + btn = buttonCreate(gInventoryWindow, 29, y1, 64, 48, 1000 + index, -1, 1000 + index, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + // Invisible button representing right inventory slot. + btn = buttonCreate(gInventoryWindow, 395, y1, 64, 48, 2000 + index, -1, 2000 + index, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + // Invisible button representing left suggested slot. + btn = buttonCreate(gInventoryWindow, 165, y2, 64, 48, 2300 + index, -1, 2300 + index, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + // Invisible button representing right suggested slot. + btn = buttonCreate(gInventoryWindow, 250, y2, 64, 48, 2400 + index, -1, 2400 + index, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + y1 += 48; + y2 += 48; + } + } else { + // Create invisible buttons representing item slots. + for (int index = 0; index < gInventorySlotsCount; index++) { + int btn = buttonCreate(gInventoryWindow, + 44, + 48 * (gInventorySlotsCount - index - 1) + 35, + 64, + 48, + 999 + gInventorySlotsCount - index, + -1, + 999 + gInventorySlotsCount - index, + -1, + NULL, + NULL, + NULL, + 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + } + } + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { + int btn; + + // Item2 slot + btn = buttonCreate(gInventoryWindow, 245, 286, 90, 61, 1006, -1, 1006, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + // Item1 slot + btn = buttonCreate(gInventoryWindow, 154, 286, 90, 61, 1007, -1, 1007, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + + // Armor slot + btn = buttonCreate(gInventoryWindow, 154, 183, 90, 61, 1008, -1, 1008, -1, NULL, NULL, NULL, 0); + if (btn != -1) { + buttonSetMouseCallbacks(btn, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit, NULL, NULL); + } + } + + memset(_ikey, 0, sizeof(_ikey)); + + int fid; + int btn; + unsigned char* buttonUpData; + unsigned char* buttonDownData; + unsigned char* buttonDisabledData; + + fid = buildFid(6, 8, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[0])); + + fid = buildFid(6, 9, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[1])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + btn = -1; + switch (inventoryWindowType) { + case INVENTORY_WINDOW_TYPE_NORMAL: + // Done button + btn = buttonCreate(gInventoryWindow, 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 = buttonCreate(gInventoryWindow, 233, 328, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + break; + case INVENTORY_WINDOW_TYPE_LOOT: + // Done button + btn = buttonCreate(gInventoryWindow, 476, 331, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + break; + } + + if (btn != -1) { + buttonSetCallbacks(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 = buildFid(6, 100, 0, 0, 0); + fid = buildFid(6, fid, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[2])); + + // Large arrow up (pressed). + fid = buildFid(6, 101, 0, 0, 0); + fid = buildFid(6, fid, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[3])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Left inventory up button. + btn = buttonCreate(gInventoryWindow, 109, 56, 23, 24, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Right inventory up button. + btn = buttonCreate(gInventoryWindow, 342, 56, 23, 24, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + } else { + // Large up arrow (normal). + fid = buildFid(6, 49, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[2])); + + // Large up arrow (pressed). + fid = buildFid(6, 50, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[3])); + + // Large up arrow (disabled). + fid = buildFid(6, 53, 0, 0, 0); + buttonDisabledData = artLockFrameData(fid, 0, 0, &(_ikey[4])); + + if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) { + if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) { + // Left inventory up button. + gInventoryScrollUpButton = buttonCreate(gInventoryWindow, 128, 39, 22, 23, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (gInventoryScrollUpButton != -1) { + _win_register_button_disable(gInventoryScrollUpButton, buttonDisabledData, buttonDisabledData, buttonDisabledData); + buttonSetCallbacks(gInventoryScrollUpButton, _gsound_red_butt_press, _gsound_red_butt_release); + buttonDisable(gInventoryScrollUpButton); + } + } + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + // Right inventory up button. + gSecondaryInventoryScrollUpButton = buttonCreate(gInventoryWindow, 379, 39, 22, 23, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (gSecondaryInventoryScrollUpButton != -1) { + _win_register_button_disable(gSecondaryInventoryScrollUpButton, buttonDisabledData, buttonDisabledData, buttonDisabledData); + buttonSetCallbacks(gSecondaryInventoryScrollUpButton, _gsound_red_butt_press, _gsound_red_butt_release); + buttonDisable(gSecondaryInventoryScrollUpButton); + } + } + } + } + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + // Large dialog down button (normal) + fid = buildFid(6, 93, 0, 0, 0); + fid = buildFid(6, fid, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[5])); + + // Dialog down button (pressed) + fid = buildFid(6, 94, 0, 0, 0); + fid = buildFid(6, fid, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[6])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Left inventory down button. + btn = buttonCreate(gInventoryWindow, 109, 82, 24, 25, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Right inventory down button + btn = buttonCreate(gInventoryWindow, 342, 82, 24, 25, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Invisible button representing left character. + buttonCreate(_barter_back_win, 15, 25, 60, 100, -1, -1, 2500, -1, NULL, NULL, NULL, 0); + + // Invisible button representing right character. + buttonCreate(_barter_back_win, 560, 25, 60, 100, -1, -1, 2501, -1, NULL, NULL, NULL, 0); + } + } else { + // Large arrow down (normal). + fid = buildFid(6, 51, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[5])); + + // Large arrow down (pressed). + fid = buildFid(6, 52, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[6])); + + // Large arrow down (disabled). + fid = buildFid(6, 54, 0, 0, 0); + buttonDisabledData = artLockFrameData(fid, 0, 0, &(_ikey[7])); + + if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) { + // Left inventory down button. + gInventoryScrollDownButton = buttonCreate(gInventoryWindow, 128, 62, 22, 23, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + buttonSetCallbacks(gInventoryScrollDownButton, _gsound_red_butt_press, _gsound_red_butt_release); + _win_register_button_disable(gInventoryScrollDownButton, buttonDisabledData, buttonDisabledData, buttonDisabledData); + buttonDisable(gInventoryScrollDownButton); + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + // Invisible button representing left character. + buttonCreate(gInventoryWindow, 44, 35, 60, 100, -1, -1, 2500, -1, NULL, NULL, NULL, 0); + + // Right inventory down button. + gSecondaryInventoryScrollDownButton = buttonCreate(gInventoryWindow, 379, 62, 22, 23, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, 0, 0); + if (gSecondaryInventoryScrollDownButton != -1) { + buttonSetCallbacks(gSecondaryInventoryScrollDownButton, _gsound_red_butt_press, _gsound_red_butt_release); + _win_register_button_disable(gSecondaryInventoryScrollDownButton, buttonDisabledData, buttonDisabledData, buttonDisabledData); + buttonDisable(gSecondaryInventoryScrollDownButton); + } + + // Invisible button representing right character. + buttonCreate(gInventoryWindow, 422, 35, 60, 100, -1, -1, 2501, -1, NULL, NULL, NULL, 0); + } else { + // Invisible button representing character (in inventory and use on dialogs). + buttonCreate(gInventoryWindow, 176, 37, 60, 100, -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 = buildFid(6, 436, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[8])); + + // Take all button (pressed) + fid = buildFid(6, 437, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[9])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Take all button. + btn = buttonCreate(gInventoryWindow, 432, 204, 39, 41, -1, -1, KEY_UPPERCASE_A, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + } + } + } else { + // Inventory button up (normal) + fid = buildFid(6, 49, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[8])); + + // Inventory button up (pressed) + fid = buildFid(6, 50, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[9])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Left offered inventory up button. + btn = buttonCreate(gInventoryWindow, 128, 113, 22, 23, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Right offered inventory up button. + btn = buttonCreate(gInventoryWindow, 333, 113, 22, 23, -1, -1, KEY_CTRL_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + + // Inventory button down (normal) + fid = buildFid(6, 51, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_ikey[8])); + + // Inventory button down (pressed). + fid = buildFid(6, 52, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_ikey[9])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Left offered inventory down button. + btn = buttonCreate(gInventoryWindow, 128, 136, 22, 23, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Right offered inventory down button. + btn = buttonCreate(gInventoryWindow, 333, 136, 22, 23, -1, -1, KEY_CTRL_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + } + + gInventoryRightHandItem = NULL; + gInventoryArmor = NULL; + gInventoryLeftHandItem = 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) { + gInventoryRightHandItem = item; + } + gInventoryLeftHandItem = item; + } else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { + gInventoryRightHandItem = item; + } else if ((item->flags & OBJECT_WORN) != 0) { + gInventoryArmor = item; + } + } + + if (gInventoryLeftHandItem != NULL) { + itemRemove(_inven_dude, gInventoryLeftHandItem, 1); + } + + if (gInventoryRightHandItem != NULL && gInventoryRightHandItem != gInventoryLeftHandItem) { + itemRemove(_inven_dude, gInventoryRightHandItem, 1); + } + + if (gInventoryArmor != NULL) { + itemRemove(_inven_dude, gInventoryArmor, 1); + } + + _adjust_fid(); + + bool isoWasEnabled = isoDisable(); + + _gmouse_disable(0); + + return isoWasEnabled; +} + +// 0x46FBD8 +void _exit_inventory(bool shouldEnableIso) +{ + _inven_dude = _stack[0]; + + if (gInventoryLeftHandItem != NULL) { + gInventoryLeftHandItem->flags |= OBJECT_IN_LEFT_HAND; + if (gInventoryLeftHandItem == gInventoryRightHandItem) { + gInventoryLeftHandItem->flags |= OBJECT_IN_RIGHT_HAND; + } + + itemAdd(_inven_dude, gInventoryLeftHandItem, 1); + } + + if (gInventoryRightHandItem != NULL && gInventoryRightHandItem != gInventoryLeftHandItem) { + gInventoryRightHandItem->flags |= OBJECT_IN_RIGHT_HAND; + itemAdd(_inven_dude, gInventoryRightHandItem, 1); + } + + if (gInventoryArmor != NULL) { + gInventoryArmor->flags |= OBJECT_WORN; + itemAdd(_inven_dude, gInventoryArmor, 1); + } + + gInventoryRightHandItem = NULL; + gInventoryArmor = NULL; + gInventoryLeftHandItem = NULL; + + for (int index = 0; index < OFF_59E7BC_COUNT; index++) { + artUnlock(_ikey[index]); + } + + if (shouldEnableIso) { + isoEnable(); + } + + windowDestroy(gInventoryWindow); + + _gmouse_enable(); + + if (_dropped_explosive) { + Attack v1; + attackInit(&v1, gDude, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); + v1.attackerFlags = DAM_HIT; + v1.tile = gDude->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 != gDude + && critter->data.critter.combat.team != gDude->data.critter.combat.team + && statRoll(critter, STAT_PERCEPTION, 0, NULL) >= ROLL_SUCCESS) { + _critter_set_who_hit_me(critter, gDude); + + if (v2 == NULL) { + v2 = critter; + } + } + } + + if (v2 != NULL) { + if (!isInCombat()) { + STRUCT_664980 v3; + v3.attacker = v2; + v3.defender = gDude; + v3.actionPointsBonus = 0; + v3.accuracyBonus = 0; + v3.damageBonus = 0; + v3.minDamage = 0; + v3.maxDamage = INT_MAX; + v3.field_1C = 0; + scriptsRequestCombat(&v3); + } + } + + _dropped_explosive = false; + } +} + +// 0x46FDF4 +void _display_inventory(int a1, int a2, int inventoryWindowType) +{ + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + int pitch; + + int v49 = 0; + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { + pitch = 499; + + int backgroundFid = buildFid(6, 48, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + // Clear scroll view background. + blitBufferToBuffer(backgroundFrmData + pitch * 35 + 44, 64, gInventorySlotsCount * 48, pitch, windowBuffer + pitch * 35 + 44, pitch); + + // Clear armor button background. + blitBufferToBuffer(backgroundFrmData + pitch * 183 + 154, 90, 61, pitch, windowBuffer + pitch * 183 + 154, pitch); + + if (gInventoryLeftHandItem != NULL && gInventoryLeftHandItem == gInventoryRightHandItem) { + // Clear item1. + int itemBackgroundFid = buildFid(6, 32, 0, 0, 0); + + CacheEntry* itemBackgroundFrmHandle; + Art* itemBackgroundFrm = artLock(itemBackgroundFid, &itemBackgroundFrmHandle); + if (itemBackgroundFrm != NULL) { + unsigned char* data = artGetFrameData(itemBackgroundFrm, 0, 0); + int width = artGetWidth(itemBackgroundFrm, 0, 0); + int height = artGetHeight(itemBackgroundFrm, 0, 0); + blitBufferToBuffer(data, width, height, width, windowBuffer + pitch * 284 + 152, pitch); + artUnlock(itemBackgroundFrmHandle); + } + } else { + // Clear both items in one go. + blitBufferToBuffer(backgroundFrmData + pitch * 286 + 154, 180, 61, pitch, windowBuffer + pitch * 286 + 154, pitch); + } + + artUnlock(backgroundFrmHandle); + } + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) { + pitch = 292; + + int backgroundFid = buildFid(6, 113, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + // Clear scroll view background. + blitBufferToBuffer(backgroundFrmData + pitch * 35 + 44, 64, gInventorySlotsCount * 48, pitch, windowBuffer + pitch * 35 + 44, pitch); + artUnlock(backgroundFrmHandle); + } + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + pitch = 537; + + int backgroundFid = buildFid(6, 114, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + // Clear scroll view background. + blitBufferToBuffer(backgroundFrmData + pitch * 37 + 176, 64, gInventorySlotsCount * 48, pitch, windowBuffer + pitch * 37 + 176, pitch); + artUnlock(backgroundFrmHandle); + } + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + pitch = 480; + + windowBuffer = windowGetBuffer(gInventoryWindow); + + blitBufferToBuffer(windowGetBuffer(_barter_back_win) + 35 * (_scr_size.right - _scr_size.left + 1) + 100, 64, 48 * gInventorySlotsCount, _scr_size.right - _scr_size.left + 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 (gInventoryScrollUpButton != -1) { + if (a1 <= 0) { + buttonDisable(gInventoryScrollUpButton); + } else { + buttonEnable(gInventoryScrollUpButton); + } + } + + if (gInventoryScrollDownButton != -1) { + if (_pud->length - a1 <= gInventorySlotsCount) { + buttonDisable(gInventoryScrollDownButton); + } else { + buttonEnable(gInventoryScrollDownButton); + } + } + } + + int y = 0; + for (int v19 = 0; v19 + a1 < _pud->length && v19 < gInventorySlotsCount; 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 = itemGetInventoryFid(inventoryItem->item); + artRender(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 (gInventoryRightHandItem != NULL) { + int width = gInventoryRightHandItem == gInventoryLeftHandItem ? 180 : 90; + int inventoryFid = itemGetInventoryFid(gInventoryRightHandItem); + artRender(inventoryFid, windowBuffer + 499 * 286 + 245, width, 61, 499); + } + + if (gInventoryLeftHandItem != NULL && gInventoryLeftHandItem != gInventoryRightHandItem) { + int inventoryFid = itemGetInventoryFid(gInventoryLeftHandItem); + artRender(inventoryFid, windowBuffer + 499 * 286 + 154, 90, 61, 499); + } + + if (gInventoryArmor != NULL) { + int inventoryFid = itemGetInventoryFid(gInventoryArmor); + artRender(inventoryFid, windowBuffer + 499 * 183 + 154, 90, 61, 499); + } + } + + windowRefresh(gInventoryWindow); +} + +// 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 = windowGetBuffer(gInventoryWindow); + + int pitch; + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + pitch = 537; + + int fid = buildFid(6, 114, 0, 0, 0); + + CacheEntry* handle; + unsigned char* data = artLockFrameData(fid, 0, 0, &handle); + if (data != NULL) { + blitBufferToBuffer(data + 537 * 37 + 297, 64, 48 * gInventorySlotsCount, 537, windowBuffer + 537 * 37 + 297, 537); + artUnlock(handle); + } + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + pitch = 480; + + unsigned char* src = windowGetBuffer(_barter_back_win); + blitBufferToBuffer(src + (_scr_size.right - _scr_size.left + 1) * 35 + 475, 64, 48 * gInventorySlotsCount, _scr_size.right - _scr_size.left + 1, windowBuffer + 480 * 35 + 395, 480); + } else { + assert(false && "Should be unreachable"); + } + + int y = 0; + for (int index = 0; index < gInventorySlotsCount; 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 = itemGetInventoryFid(inventoryItem->item); + artRender(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 (gSecondaryInventoryScrollUpButton != -1) { + if (a1 <= 0) { + buttonDisable(gSecondaryInventoryScrollUpButton); + } else { + buttonEnable(gSecondaryInventoryScrollUpButton); + } + } + + if (gSecondaryInventoryScrollDownButton != -1) { + if (inventory->length - a1 <= gInventorySlotsCount) { + buttonDisable(gSecondaryInventoryScrollDownButton); + } else { + buttonEnable(gSecondaryInventoryScrollDownButton); + } + } + } +} + +// Renders inventory item quantity. +// +// 0x4705A0 +void _display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5) +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + char formattedText[12]; + + // NOTE: Original code is slightly different and probably used goto. + bool draw = false; + + if (itemGetType(item) == ITEM_TYPE_AMMO) { + int ammoQuantity = ammoGetCapacity(item) * (quantity - 1); + + if (!a5) { + ammoQuantity += ammoGetQuantity(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) { + fontDrawText(dest, formattedText, 80, pitch, _colorTable[32767]); + } + + fontSetCurrent(oldFont); +} + +// 0x470650 +void _display_body(int fid, int inventoryWindowType) +{ + if (getTicksSince(gInventoryWindowDudeRotationTimestamp) < INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY) { + return; + } + + gInventoryWindowDudeRotation += 1; + + if (gInventoryWindowDudeRotation == ROTATION_COUNT) { + gInventoryWindowDudeRotation = 0; + } + + int rotations[2]; + if (fid == -1) { + rotations[0] = gInventoryWindowDudeRotation; + rotations[1] = ROTATION_SE; + } else { + rotations[0] = ROTATION_SW; + rotations[1] = _target_stack[_target_curr_stack]->rotation; + } + + int fids[2] = { + gInventoryWindowDudeFid, + fid, + }; + + for (int index = 0; index < 2; index += 1) { + int fid = fids[index]; + if (fid == -1) { + continue; + } + + CacheEntry* handle; + Art* art = artLock(fid, &handle); + if (art == NULL) { + continue; + } + + int frame = 0; + if (index == 1) { + frame = artGetFrameCount(art) - 1; + } + + int rotation = rotations[index]; + + unsigned char* frameData = artGetFrameData(art, frame, rotation); + + int framePitch = artGetWidth(art, frame, rotation); + int frameWidth = min(framePitch, 60); + + int frameHeight = artGetHeight(art, frame, rotation); + if (frameHeight > 100) { + frameHeight = 100; + } + + int win; + Rect rect; + CacheEntry* backrgroundFrmHandle; + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + unsigned char* windowBuffer = windowGetBuffer(_barter_back_win); + int windowPitch = windowGetWidth(_barter_back_win); + + if (index == 1) { + rect.left = 560; + rect.top = 25; + } else { + rect.left = 15; + rect.top = 25; + } + + rect.right = rect.left + 60 - 1; + rect.bottom = rect.top + 100 - 1; + + int frmId = gGameDialogSpeakerIsPartyMember ? 420 : 111; + int backgroundFid = buildFid(6, frmId, 0, 0, 0); + + unsigned char* src = artLockFrameData(backgroundFid, 0, 0, &backrgroundFrmHandle); + if (src != NULL) { + blitBufferToBuffer(src + rect.top * (_scr_size.right - _scr_size.left + 1) + rect.left, + 60, + 100, + _scr_size.right - _scr_size.left + 1, + windowBuffer + windowPitch * rect.top + rect.left, + windowPitch); + } + + blitBufferToBufferTrans(frameData, frameWidth, frameHeight, framePitch, + windowBuffer + windowPitch * (rect.top + (100 - frameHeight) / 2) + (60 - frameWidth) / 2 + rect.left, + windowPitch); + + win = _barter_back_win; + } else { + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + int windowPitch = windowGetWidth(gInventoryWindow); + + if (index == 1) { + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + rect.left = 426; + rect.top = 39; + } else { + rect.left = 297; + rect.top = 37; + } + } else { + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + rect.left = 48; + rect.top = 39; + } else { + rect.left = 176; + rect.top = 37; + } + } + + rect.right = rect.left + 60 - 1; + rect.bottom = rect.top + 100 - 1; + + int backgroundFid = buildFid(6, 114, 0, 0, 0); + unsigned char* src = artLockFrameData(backgroundFid, 0, 0, &backrgroundFrmHandle); + if (src != NULL) { + blitBufferToBuffer(src + 537 * rect.top + rect.left, + 60, + 100, + 537, + windowBuffer + windowPitch * rect.top + rect.left, + windowPitch); + } + + blitBufferToBufferTrans(frameData, frameWidth, frameHeight, framePitch, + windowBuffer + windowPitch * (rect.top + (100 - frameHeight) / 2) + (60 - frameWidth) / 2 + rect.left, + windowPitch); + + win = gInventoryWindow; + } + windowRefreshRect(win, &rect); + + artUnlock(backrgroundFrmHandle); + artUnlock(handle); + } + + gInventoryWindowDudeRotationTimestamp = _get_time(); +} + +// 0x470A2C +int inventoryCommonInit() +{ + if (inventoryMessageListInit() == -1) { + return -1; + } + + _inven_ui_was_disabled = gameUiIsDisabled(); + + if (_inven_ui_was_disabled) { + gameUiEnable(); + } + + gameMouseObjectsHide(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + int index; + for (index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { + InventoryCursorData* cursorData = &(gInventoryCursorData[index]); + + int fid = buildFid(6, gInventoryWindowCursorFrmIds[index], 0, 0, 0); + Art* frm = artLock(fid, &(cursorData->frmHandle)); + if (frm == NULL) { + break; + } + + cursorData->frm = frm; + cursorData->frmData = artGetFrameData(frm, 0, 0); + cursorData->width = artGetWidth(frm, 0, 0); + cursorData->height = artGetHeight(frm, 0, 0); + artGetFrameOffsets(frm, 0, 0, &(cursorData->offsetX), &(cursorData->offsetY)); + } + + if (index != INVENTORY_WINDOW_CURSOR_COUNT) { + for (; index >= 0; index--) { + artUnlock(gInventoryCursorData[index].frmHandle); + } + + if (_inven_ui_was_disabled) { + gameUiDisable(0); + } + + messageListFree(&gInventoryMessageList); + + return -1; + } + + _inven_is_initialized = true; + _im_value = -1; + + return 0; +} + +// NOTE: Inlined. +// +// 0x470B8C +void inventoryCommonFree() +{ + for (int index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { + artUnlock(gInventoryCursorData[index].frmHandle); + } + + if (_inven_ui_was_disabled) { + gameUiDisable(0); + } + + // NOTE: Uninline. + inventoryMessageListFree(); + + _inven_is_initialized = 0; +} + +// 0x470BCC +void inventorySetCursor(int cursor) +{ + gInventoryCursor = cursor; + + if (cursor != INVENTORY_WINDOW_CURSOR_ARROW || _im_value == -1) { + InventoryCursorData* cursorData = &(gInventoryCursorData[cursor]); + mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); + } else { + inventoryItemSlotOnMouseEnter(-1, _im_value); + } +} + +// 0x470C2C +void inventoryItemSlotOnMouseEnter(int btn, int keyCode) +{ + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + int x; + int y; + mouseGetPosition(&x, &y); + + Object* a2a = NULL; + if (_inven_from_button(keyCode, &a2a, NULL, NULL) != 0) { + gameMouseRenderPrimaryAction(x, y, 3, gInventoryWindowMaxX, gInventoryWindowMaxY); + + int v5 = 0; + int v6 = 0; + _gmouse_3d_pick_frame_hot(&v5, &v6); + + InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_PICK]); + mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, v5, v6, 0); + + if (a2a != _last_target) { + _obj_look_at_func(_stack[0], a2a, gInventoryPrintItemDescriptionHandler); + } + } else { + InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_ARROW]); + mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); + } + + _last_target = a2a; + } + + _im_value = keyCode; +} + +// 0x470D1C +void inventoryItemSlotOnMouseExit(int btn, int keyCode) +{ + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_ARROW]); + mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); + } + + _im_value = -1; +} + +// 0x470D5C +void _inven_update_lighting(Object* a1) +{ + if (gDude == _inven_dude) { + int lightDistance; + if (a1 != NULL && a1->lightDistance > 4) { + lightDistance = a1->lightDistance; + } else { + lightDistance = 4; + } + + Rect rect; + objectSetLight(_inven_dude, lightDistance, 0x10000, &rect); + tileWindowRefreshRect(&rect, gElevation); + } +} + +// 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.left = 245; + rect.top = 286; + if (_inven_dude == gDude && interfaceGetCurrentHand() != HAND_LEFT) { + v39 = a1a; + } + break; + case 1007: + rect.left = 154; + rect.top = 286; + if (_inven_dude == gDude && interfaceGetCurrentHand() == HAND_LEFT) { + v39 = a1a; + } + break; + case 1008: + rect.left = 154; + rect.top = 183; + break; + default: + // NOTE: Original code a little bit different, this code path + // is only for key codes below 1006. + v3 = keyCode - 1000; + rect.left = 44; + rect.top = 48 * v3 + 35; + break; + } + + if (v3 == -1 || _pud->items[a2 + v3].quantity <= 1) { + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + if (gInventoryRightHandItem != gInventoryLeftHandItem || a1a != gInventoryLeftHandItem) { + int height; + int width; + if (v3 == -1) { + height = 61; + width = 90; + } else { + height = 48; + width = 64; + } + + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 48, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + blitBufferToBuffer(backgroundFrmData + 499 * rect.top + rect.left, width, height, 499, windowBuffer + 499 * rect.top + rect.left, 499); + artUnlock(backgroundFrmHandle); + } + + rect.right = rect.left + width - 1; + rect.bottom = rect.top + height - 1; + } else { + CacheEntry* backgroundFrmHandle; + int backgroundFid = buildFid(6, 48, 0, 0, 0); + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + blitBufferToBuffer(backgroundFrmData + 499 * 286 + 154, 180, 61, 499, windowBuffer + 499 * 286 + 154, 499); + artUnlock(backgroundFrmHandle); + } + + rect.left = 154; + rect.top = 286; + rect.right = rect.left + 180 - 1; + rect.bottom = rect.top + 61 - 1; + } + windowRefreshRect(gInventoryWindow, &rect); + } else { + _display_inventory(a2, v3, INVENTORY_WINDOW_TYPE_NORMAL); + } + + CacheEntry* itemInventoryFrmHandle; + int itemInventoryFid = itemGetInventoryFid(a1a); + Art* itemInventoryFrm = artLock(itemInventoryFid, &itemInventoryFrmHandle); + if (itemInventoryFrm != NULL) { + int width = artGetWidth(itemInventoryFrm, 0, 0); + int height = artGetHeight(itemInventoryFrm, 0, 0); + unsigned char* itemInventoryFrmData = artGetFrameData(itemInventoryFrm, 0, 0); + mouseSetFrame(itemInventoryFrmData, width, height, width, width / 2, height / 2, 0); + soundPlayFile("ipickup1"); + } + + if (v39 != NULL) { + _inven_update_lighting(NULL); + } + + do { + _get_input(); + _display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); + } while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); + + if (itemInventoryFrm != NULL) { + artUnlock(itemInventoryFrmHandle); + soundPlayFile("iputdown"); + } + + if (_mouse_click_in(124, 35, 188, 48 * gInventorySlotsCount + 35)) { + int x; + int y; + mouseGetPosition(&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 (itemGetType(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 (itemAdd(_inven_dude, a1a, 1)) { + *v29 = a1a; + } else if (v29 == &gInventoryArmor) { + _adjust_ac(_stack[0], a1a, NULL); + } else if (gInventoryRightHandItem == gInventoryLeftHandItem) { + gInventoryLeftHandItem = NULL; + gInventoryRightHandItem = NULL; + } + } + } else if (_mouse_click_in(234, 286, 324, 347)) { + if (gInventoryLeftHandItem != NULL && itemGetType(gInventoryLeftHandItem) == ITEM_TYPE_CONTAINER && gInventoryLeftHandItem != a1a) { + _drop_into_container(gInventoryLeftHandItem, a1a, v3, v29, count); + } else if (gInventoryLeftHandItem == NULL || _drop_ammo_into_weapon(gInventoryLeftHandItem, a1a, v29, count, keyCode)) { + _switch_hand(a1a, &gInventoryLeftHandItem, v29, keyCode); + } + } else if (_mouse_click_in(325, 286, 415, 347)) { + if (gInventoryRightHandItem != NULL && itemGetType(gInventoryRightHandItem) == ITEM_TYPE_CONTAINER && gInventoryRightHandItem != a1a) { + _drop_into_container(gInventoryRightHandItem, a1a, v3, v29, count); + } else if (gInventoryRightHandItem == NULL || _drop_ammo_into_weapon(gInventoryRightHandItem, a1a, v29, count, keyCode)) { + _switch_hand(a1a, &gInventoryRightHandItem, v29, v3); + } + } else if (_mouse_click_in(234, 183, 324, 244)) { + if (itemGetType(a1a) == ITEM_TYPE_ARMOR) { + Object* v21 = gInventoryArmor; + int v22 = 0; + if (v3 != -1) { + itemRemove(_inven_dude, a1a, 1); + } + + if (gInventoryArmor != NULL) { + if (v29 != NULL) { + *v29 = gInventoryArmor; + } else { + gInventoryArmor = NULL; + v22 = itemAdd(_inven_dude, v21, 1); + } + } else { + if (v29 != NULL) { + *v29 = gInventoryArmor; + } + } + + if (v22 != 0) { + gInventoryArmor = v21; + if (v3 != -1) { + itemAdd(_inven_dude, a1a, 1); + } + } else { + _adjust_ac(_stack[0], v21, a1a); + gInventoryArmor = a1a; + } + } + } else if (_mouse_click_in(256, 37, 316, 137)) { + 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(); + inventoryRenderSummary(); + _display_inventory(a2, -1, INVENTORY_WINDOW_TYPE_NORMAL); + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + if (_inven_dude == gDude) { + Object* item; + if (interfaceGetCurrentHand() == HAND_LEFT) { + item = critterGetItem1(_inven_dude); + } else { + item = critterGetItem2(_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 (itemGetType(*a2) == ITEM_TYPE_WEAPON && itemGetType(a1) == ITEM_TYPE_AMMO) { + return; + } + + if (a3 != NULL && (a3 != &gInventoryArmor || itemGetType(*a2) == ITEM_TYPE_ARMOR)) { + if (a3 == &gInventoryArmor) { + _adjust_ac(_stack[0], gInventoryArmor, *a2); + } + *a3 = *a2; + } else { + if (a4 != -1) { + itemRemove(_inven_dude, a1, 1); + } + + Object* itemToAdd = *a2; + *a2 = NULL; + if (itemAdd(_inven_dude, itemToAdd, 1) != 0) { + itemAdd(_inven_dude, a1, 1); + return; + } + + a4 = -1; + + if (a3 != NULL) { + if (a3 == &gInventoryArmor) { + _adjust_ac(_stack[0], gInventoryArmor, NULL); + } + *a3 = NULL; + } + } + } else { + if (a3 != NULL) { + if (a3 == &gInventoryArmor) { + _adjust_ac(_stack[0], gInventoryArmor, NULL); + } + *a3 = NULL; + } + } + + *a2 = a1; + + if (a4 != -1) { + itemRemove(_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 = critterGetBonusStat(critter, STAT_ARMOR_CLASS); + int oldArmorClass = armorGetArmorClass(oldArmor); + int newArmorClass = armorGetArmorClass(newArmor); + critterSetBonusStat(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 = critterGetBonusStat(critter, damageResistanceStat); + int oldArmorDamageResistance = armorGetDamageResistance(oldArmor, damageType); + int newArmorDamageResistance = armorGetDamageResistance(newArmor, damageType); + critterSetBonusStat(critter, damageResistanceStat, damageResistanceBonus - oldArmorDamageResistance + newArmorDamageResistance); + + int damageThresholdBonus = critterGetBonusStat(critter, damageThresholdStat); + int oldArmorDamageThreshold = armorGetDamageThreshold(oldArmor, damageType); + int newArmorDamageThreshold = armorGetDamageThreshold(newArmor, damageType); + critterSetBonusStat(critter, damageThresholdStat, damageThresholdBonus - oldArmorDamageThreshold + newArmorDamageThreshold); + + damageResistanceStat += 1; + damageThresholdStat += 1; + } + + if (objectIsPartyMember(critter)) { + if (oldArmor != NULL) { + int perk = armorGetPerk(oldArmor); + perkRemoveEffect(critter, perk); + } + + if (newArmor != NULL) { + int perk = armorGetPerk(newArmor); + perkAddEffect(critter, perk); + } + } +} + +// 0x4716E8 +void _adjust_fid() +{ + int fid; + if ((_inven_dude->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + Proto* proto; + + int v0 = _art_vault_guy_num; + + if (protoGetProto(_inven_pid, &proto) == -1) { + v0 = proto->fid & 0xFFF; + } + + if (gInventoryArmor != NULL) { + protoGetProto(gInventoryArmor->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 (interfaceGetCurrentHand()) { + if (gInventoryRightHandItem != NULL) { + protoGetProto(gInventoryRightHandItem->pid, &proto); + if (proto->item.type == ITEM_TYPE_WEAPON) { + animationCode = proto->item.data.weapon.animationCode; + } + } + } else { + if (gInventoryLeftHandItem != NULL) { + protoGetProto(gInventoryLeftHandItem->pid, &proto); + if (proto->item.type == ITEM_TYPE_WEAPON) { + animationCode = proto->item.data.weapon.animationCode; + } + } + } + + fid = buildFid(1, v0, 0, animationCode, 0); + } else { + fid = _inven_dude->fid; + } + + gInventoryWindowDudeFid = fid; +} + +// 0x4717E4 +void inventoryOpenUseItemOn(Object* a1) +{ + if (inventoryCommonInit() == -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); + inventorySetCursor(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] -= gInventorySlotsCount; + 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 - gInventorySlotsCount; + 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] + gInventorySlotsCount < _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] += gInventorySlotsCount; + if (_stack_offset[_curr_stack] + gInventorySlotsCount >= _pud->length) { + _stack_offset[_curr_stack] = _pud->length - gInventorySlotsCount; + 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 ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + } else { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + } + } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if (keyCode >= 1000 && keyCode < 1000 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(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 (gDude->data.critter.combat.ap >= 2) { + if (_action_use_an_item_on_object(gDude, a1, inventoryItem->item) != -1) { + int actionPoints = gDude->data.critter.combat.ap; + if (actionPoints < 2) { + gDude->data.critter.combat.ap = 0; + } else { + gDude->data.critter.combat.ap = actionPoints - 2; + } + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + } + } else { + _action_use_an_item_on_object(gDude, a1, inventoryItem->item); + } + keyCode = KEY_ESCAPE; + } else { + keyCode = -1; + } + } + } + } + } + + if (keyCode == KEY_ESCAPE) { + break; + } + } + + _exit_inventory(isoWasEnabled); + + // NOTE: Uninline. + inventoryCommonFree(); +} + +// 0x471B70 +Object* critterGetItem2(Object* critter) +{ + int i; + Inventory* inventory; + Object* item; + + if (gInventoryRightHandItem != NULL && critter == _inven_dude) { + return gInventoryRightHandItem; + } + + 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* critterGetItem1(Object* critter) +{ + int i; + Inventory* inventory; + Object* item; + + if (gInventoryLeftHandItem != NULL && critter == _inven_dude) { + return gInventoryLeftHandItem; + } + + 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* critterGetArmor(Object* critter) +{ + int i; + Inventory* inventory; + Object* item; + + if (gInventoryArmor != NULL && critter == _inven_dude) { + return gInventoryArmor; + } + + 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* objectGetCarriedObjectByPid(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 = objectGetCarriedObjectByPid(inventoryItem->item, pid); + if (found != NULL) { + return found; + } + } + + return NULL; +} + +// 0x471CDC +int objectGetCarriedQuantityByPid(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 += objectGetCarriedQuantityByPid(inventoryItem->item, pid); + } + + return quantity; +} + +// Renders character's summary of SPECIAL stats, equipped armor bonuses, +// and weapon's damage/range. +// +// 0x471D5C +void inventoryRenderSummary() +{ + int v56[7]; + static_assert(sizeof(v56) == sizeof(dword_46E6D0), "wrong size"); + memcpy(v56, dword_46E6D0, sizeof(v56)); + + int v57[7]; + static_assert(sizeof(v57) == sizeof(dword_46E6EC), "wrong size"); + memcpy(v57, dword_46E6EC, sizeof(v57)); + + char formattedText[80]; + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + + int fid = buildFid(6, 48, 0, 0, 0); + + CacheEntry* backgroundHandle; + unsigned char* backgroundData = artLockFrameData(fid, 0, 0, &backgroundHandle); + if (backgroundData != NULL) { + blitBufferToBuffer(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); + } + artUnlock(backgroundHandle); + + // Render character name. + const char* critterName = critterGetName(_stack[0]); + fontDrawText(windowBuffer + 499 * 44 + 297, critterName, 80, 499, _colorTable[992]); + + bufferDrawLine(windowBuffer, + 499, + 297, + 3 * fontGetLineHeight() / 2 + 44, + 440, + 3 * fontGetLineHeight() / 2 + 44, + _colorTable[992]); + + MessageListItem messageListItem; + + int offset = 499 * 2 * fontGetLineHeight() + 499 * 44 + 297; + for (int stat = 0; stat < 7; stat++) { + messageListItem.num = stat; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + fontDrawText(windowBuffer + offset, messageListItem.text, 80, 499, _colorTable[992]); + } + + int value = critterGetStat(_stack[0], stat); + sprintf(formattedText, "%d", value); + fontDrawText(windowBuffer + offset + 24, formattedText, 80, 499, _colorTable[992]); + + offset += 499 * fontGetLineHeight(); + } + + offset -= 499 * 7 * fontGetLineHeight(); + + for (int index = 0; index < 7; index += 1) { + messageListItem.num = 7 + index; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + fontDrawText(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); + } + + fontDrawText(windowBuffer + offset + 104, formattedText, 80, 499, _colorTable[992]); + + offset += 499 * fontGetLineHeight(); + } + + bufferDrawLine(windowBuffer, 499, 297, 18 * fontGetLineHeight() / 2 + 48, 440, 18 * fontGetLineHeight() / 2 + 48, _colorTable[992]); + bufferDrawLine(windowBuffer, 499, 297, 26 * fontGetLineHeight() / 2 + 48, 440, 26 * fontGetLineHeight() / 2 + 48, _colorTable[992]); + + Object* itemsInHands[2] = { + gInventoryLeftHandItem, + gInventoryRightHandItem, + }; + + const int hitModes[2] = { + HIT_MODE_LEFT_WEAPON_PRIMARY, + HIT_MODE_RIGHT_WEAPON_PRIMARY, + }; + + offset += 499 * fontGetLineHeight(); + + for (int index = 0; index < 2; index += 1) { + Object* item = itemsInHands[index]; + if (item == NULL) { + formattedText[0] = '\0'; + + // No item + messageListItem.num = 14; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + fontDrawText(windowBuffer + offset, messageListItem.text, 120, 499, _colorTable[992]); + } + + offset += 499 * fontGetLineHeight(); + + // Unarmed dmg: + messageListItem.num = 24; + if (messageListGetItem(&gInventoryMessageList, &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); + } + + fontDrawText(windowBuffer + offset, formattedText, 120, 499, _colorTable[992]); + + offset += 3 * 499 * fontGetLineHeight(); + continue; + } + + const char* itemName = itemGetName(item); + fontDrawText(windowBuffer + offset, itemName, 140, 499, _colorTable[992]); + + offset += 499 * fontGetLineHeight(); + + int itemType = itemGetType(item); + if (itemType != ITEM_TYPE_WEAPON) { + if (itemType == ITEM_TYPE_ARMOR) { + // (Not worn) + messageListItem.num = 18; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + fontDrawText(windowBuffer + offset, messageListItem.text, 120, 499, _colorTable[992]); + } + } + + offset += 3 * 499 * fontGetLineHeight(); + continue; + } + + int range = _item_w_range(_stack[0], hitModes[index]); + + int damageMin; + int damageMax; + weaponGetDamageMinMax(item, &damageMin, &damageMax); + + int attackType = weaponGetAttackTypeForHitMode(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + if (attackType != 4 && range <= 1) { + sprintf(formattedText, "%s %d-%d", messageListItem.text, damageMin, damageMax + meleeDamage); + } else { + MessageListItem rangeMessageListItem; + rangeMessageListItem.num = 16; // Rng: + if (messageListGetItem(&gInventoryMessageList, &rangeMessageListItem)) { + sprintf(formattedText, "%s %d-%d %s %d", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range); + } + } + + fontDrawText(windowBuffer + offset, formattedText, 140, 499, _colorTable[992]); + } + + offset += 499 * fontGetLineHeight(); + + if (ammoGetCapacity(item) > 0) { + int ammoTypePid = weaponGetAmmoTypePid(item); + + formattedText[0] = '\0'; + + messageListItem.num = 17; // Ammo: + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + if (ammoTypePid != -1) { + if (ammoGetQuantity(item) != 0) { + const char* ammoName = protoGetName(ammoTypePid); + int capacity = ammoGetCapacity(item); + int quantity = ammoGetQuantity(item); + sprintf(formattedText, "%s %d/%d %s", messageListItem.text, quantity, capacity, ammoName); + } else { + int capacity = ammoGetCapacity(item); + int quantity = ammoGetQuantity(item); + sprintf(formattedText, "%s %d/%d", messageListItem.text, quantity, capacity); + } + } + } else { + int capacity = ammoGetCapacity(item); + int quantity = ammoGetQuantity(item); + sprintf(formattedText, "%s %d/%d", messageListItem.text, quantity, capacity); + } + + fontDrawText(windowBuffer + offset, formattedText, 140, 499, _colorTable[992]); + } + + offset += 2 * 499 * fontGetLineHeight(); + } + + // Total wt: + messageListItem.num = 20; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + if (_stack[0]->pid >> 24 == OBJ_TYPE_CRITTER) { + int carryWeight = critterGetStat(_stack[0], STAT_CARRY_WEIGHT); + int inventoryWeight = objectGetInventoryWeight(_stack[0]); + sprintf(formattedText, "%s %d/%d", messageListItem.text, inventoryWeight, carryWeight); + + int color = _colorTable[992]; + if (critterIsEncumbered(_stack[0])) { + color = _colorTable[31744]; + } + + fontDrawText(windowBuffer + offset + 15, formattedText, 120, 499, color); + } else { + int inventoryWeight = objectGetInventoryWeight(_stack[0]); + sprintf(formattedText, "%s %d", messageListItem.text, inventoryWeight); + + fontDrawText(windowBuffer + offset + 30, formattedText, 80, 499, _colorTable[992]); + } + } + + fontSetCurrent(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 && itemGetType(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 (itemGetType(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 (!isoIsDisabled()) { + reg_anim_begin(2); + } + } + + int itemType = itemGetType(item); + if (itemType == ITEM_TYPE_ARMOR) { + Object* armor = critterGetArmor(critter); + if (armor != NULL) { + armor->flags = ~OBJECT_WORN; + } + + item->flags |= OBJECT_WORN; + + int baseFrmId; + if (critterGetStat(critter, STAT_GENDER) == GENDER_FEMALE) { + baseFrmId = armorGetFemaleFid(item); + } else { + baseFrmId = armorGetMaleFid(item); + } + + if (baseFrmId == -1) { + baseFrmId = 1; + } + + if (critter == gDude) { + if (!isoIsDisabled()) { + int fid = buildFid(1, baseFrmId, 0, (critter->fid & 0xF000) >> 12, critter->rotation + 1); + reg_anim_17(critter, fid, 0); + } + } else { + _adjust_ac(critter, armor, item); + } + } else { + int hand; + if (critter == gDude) { + hand = interfaceGetCurrentHand(); + } else { + hand = HAND_RIGHT; + } + + int weaponAnimationCode = weaponGetAnimationCode(item); + int hitModeAnimationCode = weaponGetAnimationForHitMode(item, HIT_MODE_RIGHT_WEAPON_PRIMARY); + int fid = buildFid(1, critter->fid & 0xFFF, hitModeAnimationCode, weaponAnimationCode, critter->rotation + 1); + if (!artExists(fid)) { + debugPrint("\ninven_wield failed! ERROR ERROR ERROR!"); + return -1; + } + + Object* v17; + if (a3) { + v17 = critterGetItem2(critter); + item->flags |= OBJECT_IN_RIGHT_HAND; + } else { + v17 = critterGetItem1(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 == gDude) { + lightIntensity = LIGHT_LEVEL_MAX; + lightDistance = 4; + } else { + Proto* proto; + if (protoGetProto(critter->pid, &proto) == -1) { + return -1; + } + + lightDistance = proto->lightDistance; + lightIntensity = proto->lightIntensity; + } + + objectSetLight(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; + } + + objectSetLight(critter, lightDistance, lightIntensity, &rect); + tileWindowRefreshRect(&rect, gElevation); + } + + if (itemGetType(item) == ITEM_TYPE_WEAPON) { + weaponAnimationCode = weaponGetAnimationCode(item); + } else { + weaponAnimationCode = 0; + } + + if (hand == a3) { + if ((critter->fid & 0xF000) >> 12 != 0) { + if (a4) { + if (!isoIsDisabled()) { + const char* soundEffectName = sfxBuildCharName(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(critter, soundEffectName, 0); + reg_anim_animate(critter, ANIM_PUT_AWAY, 0); + } + } + } + + if (a4 && !isoIsDisabled()) { + if (weaponAnimationCode != 0) { + reg_anim_18(critter, weaponAnimationCode, -1); + } else { + int fid = buildFid(1, critter->fid & 0xFFF, 0, 0, critter->rotation + 1); + reg_anim_17(critter, fid, -1); + } + } else { + int fid = buildFid(1, critter->fid & 0xFFF, 0, weaponAnimationCode, critter->rotation + 1); + _dude_stand(critter, critter->rotation, fid); + } + } + } + + if (a4) { + if (!isoIsDisabled()) { + return reg_anim_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 == gDude) { + v6 = interfaceGetCurrentHand(); + } else { + v6 = 1; + } + + if (a2) { + item_obj = critterGetItem2(obj); + } else { + item_obj = critterGetItem1(obj); + } + + if (item_obj) { + item_obj->flags &= ~OBJECT_IN_ANY_HAND; + } + + if (v6 == a2 && ((obj->fid & 0xF000) >> 12) != 0) { + if (a3 && !isoIsDisabled()) { + reg_anim_begin(2); + + const char* sfx = sfxBuildCharName(obj, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); + reg_anim_play_sfx(obj, sfx, 0); + + reg_anim_animate(obj, 39, 0); + + fid = buildFid(1, obj->fid & 0xFFF, 0, 0, obj->rotation + 1); + reg_anim_17(obj, fid, -1); + + return reg_anim_end(); + } + + fid = buildFid(1, 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 = &gInventoryRightHandItem; + v7 = _stack[0]; + v8 = gInventoryRightHandItem; + break; + case 1007: + v6 = &gInventoryLeftHandItem; + v7 = _stack[0]; + v8 = gInventoryLeftHandItem; + break; + case 1008: + v6 = &gInventoryArmor; + v7 = _stack[0]; + v8 = gInventoryArmor; + 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 inventoryRenderItemDescription(char* string) +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + windowBuffer += 499 * 44 + 297; + + char* c = string; + while (c != NULL && *c != '\0') { + _inven_display_msg_line += 1; + if (_inven_display_msg_line > 17) { + debugPrint("\nError: inven_display_msg: out of bounds!"); + return; + } + + char* space = NULL; + if (fontGetStringWidth(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. + fontDrawText(windowBuffer + 499 * _inven_display_msg_line * fontGetLineHeight(), 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 (fontGetStringWidth(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 (fontGetStringWidth(c) > 152) { + debugPrint("\nError: inven_display_msg: word too long!"); + return; + } + + fontDrawText(windowBuffer + 499 * _inven_display_msg_line * fontGetLineHeight(), c, 152, 499, _colorTable[992]); + + if (space != NULL) { + c = space + 1; + if (*space == '\0') { + *space = ' '; + } + } else { + c = NULL; + } + } + + fontSetCurrent(oldFont); +} + +// Examines inventory item. +// +// 0x472EB8 +void inventoryExamineItem(Object* critter, Object* item) +{ + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + + // Clear item description area. + int backgroundFid = buildFid(6, 48, 0, 0, 0); + + CacheEntry* handle; + unsigned char* backgroundData = artLockFrameData(backgroundFid, 0, 0, &handle); + if (backgroundData != NULL) { + blitBufferToBuffer(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); + } + artUnlock(handle); + + // Reset item description lines counter. + _inven_display_msg_line = 0; + + // Render item's name. + char* itemName = objectGetName(item); + inventoryRenderItemDescription(itemName); + + // Increment line counter to accomodate separator below. + _inven_display_msg_line += 1; + + int lineHeight = fontGetLineHeight(); + + // Draw separator. + bufferDrawLine(windowBuffer, + 499, + 297, + 3 * lineHeight / 2 + 49, + 440, + 3 * lineHeight / 2 + 49, + _colorTable[992]); + + // Examine item. + _obj_examine_func(critter, item, inventoryRenderItemDescription); + + // Add weight if neccessary. + int weight = itemGetWeight(item); + if (weight != 0) { + MessageListItem messageListItem; + messageListItem.num = 540; + + if (weight == 1) { + messageListItem.num = 541; + } + + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + debugPrint("\nError: Couldn't find message!"); + } + + char formattedText[40]; + sprintf(formattedText, messageListItem.text, weight); + inventoryRenderItemDescription(formattedText); + } + + fontSetCurrent(oldFont); +} + +// 0x47304C +void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType) +{ + Object* item; + Object** v43; + Object* v41; + + int v56 = _inven_from_button(keyCode, &item, &v43, &v41); + if (v56 == 0) { + return; + } + + int itemType = itemGetType(item); + + int mouseState; + do { + _get_input(); + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { + _display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); + } + + mouseState = mouseGetEvent(); + if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { + if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { + _obj_look_at_func(_stack[0], item, gInventoryPrintItemDescriptionHandler); + } else { + inventoryExamineItem(_stack[0], item); + } + windowRefresh(gInventoryWindow); + return; + } + } while ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT); + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_BLANK); + + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + + int x; + int y; + mouseGetPosition(&x, &y); + + int actionMenuItemsLength; + const int* actionMenuItems; + if (itemType == ITEM_TYPE_WEAPON && _item_w_can_unload(item)) { + if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL && objectGetOwner(item) != gDude) { + actionMenuItemsLength = 3; + actionMenuItems = _act_weap2; + } else { + actionMenuItemsLength = 4; + actionMenuItems = _act_weap; + } + } else { + if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { + if (objectGetOwner(item) != gDude) { + 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; + } + } + } + } + + const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); + gameMouseRenderActionMenuItems(x, y, actionMenuItems, actionMenuItemsLength, + windowDescription->width + windowDescription->x, + windowDescription->height + windowDescription->y); + + InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_MENU]); + + int offsetX; + int offsetY; + artGetRotationOffsets(cursorData->frm, 0, &offsetX, &offsetY); + + Rect rect; + rect.left = x - windowDescription->x - cursorData->width / 2 + offsetX; + rect.top = y - windowDescription->y - cursorData->height + 1 + offsetY; + rect.right = rect.left + cursorData->width - 1; + rect.bottom = rect.top + cursorData->height - 1; + + int menuButtonHeight = cursorData->height; + if (rect.top + menuButtonHeight > windowDescription->height) { + menuButtonHeight = windowDescription->height - rect.top; + } + + int btn = buttonCreate(gInventoryWindow, + rect.left, + rect.top, + cursorData->width, + menuButtonHeight, + -1, + -1, + -1, + -1, + cursorData->frmData, + cursorData->frmData, + 0, + BUTTON_FLAG_TRANSPARENT); + windowRefreshRect(gInventoryWindow, &rect); + + int menuItemIndex = 0; + int previousMouseY = y; + while ((mouseGetEvent() & 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; + mouseGetPosition(&x, &y); + if (y - previousMouseY > 10 || previousMouseY - y > 10) { + if (y >= previousMouseY || menuItemIndex <= 0) { + if (previousMouseY < y && menuItemIndex < actionMenuItemsLength - 1) { + menuItemIndex++; + } + } else { + menuItemIndex--; + } + gameMouseHighlightActionMenuItemAtIndex(menuItemIndex); + windowRefreshRect(gInventoryWindow, &rect); + previousMouseY = y; + } + } + + buttonDestroy(btn); + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + unsigned char* src = windowGetBuffer(_barter_back_win); + int pitch = _scr_size.right - _scr_size.left + 1; + blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, + cursorData->width, + menuButtonHeight, + pitch, + windowBuffer + windowDescription->width * rect.top + rect.left, + windowDescription->width); + } else { + int backgroundFid = buildFid(6, windowDescription->field_0, 0, 0, 0); + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + blitBufferToBuffer(backgroundFrmData + windowDescription->width * rect.top + rect.left, + cursorData->width, + menuButtonHeight, + windowDescription->width, + windowBuffer + windowDescription->width * rect.top + rect.left, + windowDescription->width); + artUnlock(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 == &gInventoryArmor) { + _adjust_ac(_stack[0], item, NULL); + } + itemAdd(v41, item, 1); + v56 = 1; + *v43 = NULL; + } + + if (item->pid == PROTO_ID_MONEY) { + if (v56 > 1) { + v56 = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, v56); + } else { + v56 = 1; + } + + if (v56 > 0) { + if (v56 == 1) { + itemSetMoney(item, 1); + _obj_drop(v41, item); + } else { + if (itemRemove(v41, item, v56 - 1) == 0) { + Object* a2; + if (_inven_from_button(keyCode, &a2, &v43, &v41) != 0) { + itemSetMoney(a2, v56); + _obj_drop(v41, a2); + } else { + itemAdd(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 = inventoryQuantitySelect(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, gInventoryPrintItemDescriptionHandler); + } else { + inventoryExamineItem(_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 { + itemRemove(v41, item, 1); + } + + _obj_connect(item, gDude->tile, gDude->elevation, NULL); + _obj_destroy(item); + } + interfaceRenderHitPoints(true); + break; + case ITEM_TYPE_WEAPON: + case ITEM_TYPE_MISC: + if (v43 == NULL) { + itemRemove(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, gDude->tile, gDude->elevation, NULL); + _obj_destroy(item); + } else { + if (v43 == NULL) { + itemAdd(v41, item, 1); + } + } + } + break; + case GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD: + if (v43 == NULL) { + itemRemove(v41, item, 1); + } + + for (;;) { + Object* ammo = _item_w_unload(item); + if (ammo == NULL) { + break; + } + + Rect rect; + _obj_disconnect(ammo, &rect); + itemAdd(v41, ammo, 1); + } + + if (v43 == NULL) { + itemAdd(v41, item, 1); + } + break; + default: + break; + } + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL && actionMenuItem != GAME_MOUSE_ACTION_MENU_ITEM_LOOK) { + inventoryRenderSummary(); + } + + 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) { + inventoryWindowRenderInnerInventories(_barter_back_win, _ptable, _btable, -1); + } + + _adjust_fid(); +} + +// 0x473904 +int inventoryOpenLooting(Object* a1, Object* a2) +{ + int arrowFrmIds[INVENTORY_ARROW_FRM_COUNT]; + CacheEntry* arrowFrmHandles[INVENTORY_ARROW_FRM_COUNT]; + MessageListItem messageListItem; + + static_assert(sizeof(arrowFrmIds) == sizeof(gInventoryArrowFrmIds), "wrong size"); + memcpy(arrowFrmIds, gInventoryArrowFrmIds, sizeof(gInventoryArrowFrmIds)); + + if (a1 != _inven_dude) { + return 0; + } + + if (((a2->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + if (_critter_flag_check(a2->pid, 0x20)) { + // You can't find anything to take from that. + messageListItem.num = 50; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return 0; + } + } + + if (((a2->fid & 0xF000000) >> 24) == OBJ_TYPE_ITEM) { + if (itemGetType(a2) == ITEM_TYPE_CONTAINER) { + if (a2->frame == 0) { + CacheEntry* handle; + Art* frm = artLock(a2->fid, &handle); + if (frm != NULL) { + int frameCount = artGetFrameCount(frm); + artUnlock(handle); + if (frameCount > 1) { + return 0; + } + } + } + } + } + + int sid = -1; + if (!_gIsSteal) { + if (_obj_sid(a2, &sid) != -1) { + scriptSetObjects(sid, a1, NULL); + scriptExecProc(sid, SCRIPT_PROC_PICKUP); + + Script* script; + if (scriptGetScript(sid, &script) != -1) { + if (script->scriptOverrides) { + return 0; + } + } + } + } + + if (inventoryCommonInit() == -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 (objectCreateWithFidPid(&a1a, 0, 467) == -1) { + return 0; + } + + _item_move_all_hidden(a2, a1a); + + Object* item1 = NULL; + Object* item2 = NULL; + Object* armor = NULL; + + if (_gIsSteal) { + item1 = critterGetItem1(a2); + if (item1 != NULL) { + itemRemove(a2, item1, 1); + } + + item2 = critterGetItem2(a2); + if (item2 != NULL) { + itemRemove(a2, item2, 1); + } + + armor = critterGetArmor(a2); + if (armor != NULL) { + itemRemove(a2, armor, 1); + } + } + + bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_LOOT); + + Object** critters = NULL; + int critterCount = 0; + int critterIndex = 0; + if (!_gIsSteal) { + if ((a2->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + critterCount = objectListCreate(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) { + objectListFree(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 = buildFid(6, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP], 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_UP])); + + fid = buildFid(6, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN], 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + btn = buttonCreate(gInventoryWindow, 436, 162, 20, 18, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + + // Setup right arrow button. + fid = buildFid(6, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP], 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP])); + + fid = buildFid(6, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN], 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + btn = buttonCreate(gInventoryWindow, 456, 162, 20, 18, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); + if (btn != -1) { + buttonSetCallbacks(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); + inventorySetCursor(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) { + showQuitConfirmationDialog(); + } + + if (_game_user_wants_to_quit != 0) { + break; + } + + if (keyCode == KEY_UPPERCASE_A) { + if (!_gIsSteal) { + int maxCarryWeight = critterGetStat(a1, STAT_CARRY_WEIGHT); + int currentWeight = objectGetInventoryWeight(a1); + int newInventoryWeight = objectGetInventoryWeight(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + showDialogBox(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] + gInventorySlotsCount < _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); + windowRefresh(gInventoryWindow); + } + } else if (keyCode == KEY_CTRL_ARROW_DOWN) { + if (_target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _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); + windowRefresh(gInventoryWindow); + } + } else if (keyCode >= 2500 && keyCode <= 2501) { + _container_exit(keyCode, INVENTORY_WINDOW_TYPE_LOOT); + } else { + if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + } else { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + } + } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if (keyCode >= 1000 && keyCode <= 1000 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_LOOT); + } else { + int v40 = keyCode - 1000; + if (v40 + _stack_offset[_curr_stack] < _pud->length) { + _gStealCount += 1; + _gStealSize += itemGetSize(_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 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_LOOT); + } else { + int v46 = keyCode - 2000; + if (v46 + _target_stack_offset[_target_curr_stack] < _target_pud->length) { + _gStealCount += 1; + _gStealSize += itemGetSize(_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) { + objectListFree(critters); + + for (int index = 0; index < INVENTORY_ARROW_FRM_COUNT; index++) { + artUnlock(arrowFrmHandles[index]); + } + } + + if (_gIsSteal) { + if (item1 != NULL) { + item1->flags |= OBJECT_IN_LEFT_HAND; + itemAdd(a2, item1, 1); + } + + if (item2 != NULL) { + item2->flags |= OBJECT_IN_RIGHT_HAND; + itemAdd(a2, item2, 1); + } + + if (armor != NULL) { + armor->flags |= OBJECT_WORN; + itemAdd(a2, armor, 1); + } + } + + _item_move_all(a1a, a2); + objectDestroy(a1a, NULL); + + if (_gIsSteal) { + if (!isCaughtStealing) { + if (stealingXp > 0) { + if (!objectIsPartyMember(a2)) { + stealingXp = min(300 - skillGetValue(a1, SKILL_STEAL), stealingXp); + debugPrint("\n[[[%d]]]", 300 - skillGetValue(a1, SKILL_STEAL)); + + // You gain %d experience points for successfully using your Steal skill. + messageListItem.num = 29; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + char formattedText[200]; + sprintf(formattedText, messageListItem.text, stealingXp); + displayMonitorAddMessage(formattedText); + } + + pcAddExperience(stealingXp); + } + } + } + } + + _exit_inventory(isoWasEnabled); + + // NOTE: Uninline. + inventoryCommonFree(); + + if (_gIsSteal) { + if (isCaughtStealing) { + if (_gStealCount > 0) { + if (_obj_sid(a2, &sid) != -1) { + scriptSetObjects(sid, a1, NULL); + scriptExecProc(sid, SCRIPT_PROC_PICKUP); + + // TODO: Looks like inlining, script is not used. + Script* script; + scriptGetScript(sid, &script); + } + } + } + } + + return 0; +} + +// 0x4746A0 +int inventoryOpenStealing(Object* a1, Object* a2) +{ + if (a1 == a2) { + return -1; + } + + _gIsSteal = (a1->pid >> 24) == OBJ_TYPE_CRITTER && critterIsActive(a2); + _gStealCount = 0; + _gStealSize = 0; + + int rc = inventoryOpenLooting(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.left = 176; + rect.top = 48 * a2 + 37; + + 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.left = 297; + rect.top = 48 * a2 + 37; + + 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); + windowRefresh(gInventoryWindow); + v38 = false; + } + } + + if (v38) { + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + + CacheEntry* handle; + int fid = buildFid(6, 114, 0, 0, 0); + unsigned char* data = artLockFrameData(fid, 0, 0, &handle); + if (data != NULL) { + blitBufferToBuffer(data + 537 * rect.top + rect.left, 64, 48, 537, windowBuffer + 537 * rect.top + rect.left, 537); + artUnlock(handle); + } + + rect.right = rect.left + 64 - 1; + rect.bottom = rect.top + 48 - 1; + windowRefreshRect(gInventoryWindow, &rect); + } + + CacheEntry* inventoryFrmHandle; + int inventoryFid = itemGetInventoryFid(a1); + Art* inventoryFrm = artLock(inventoryFid, &inventoryFrmHandle); + if (inventoryFrm != NULL) { + int width = artGetWidth(inventoryFrm, 0, 0); + int height = artGetHeight(inventoryFrm, 0, 0); + unsigned char* data = artGetFrameData(inventoryFrm, 0, 0); + mouseSetFrame(data, width, height, width, width / 2, height / 2, 0); + soundPlayFile("ipickup1"); + } + + do { + _get_input(); + } while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); + + if (inventoryFrm != NULL) { + artUnlock(inventoryFrmHandle); + soundPlayFile("iputdown"); + } + + int rc = 0; + MessageListItem messageListItem; + + if (a4) { + if (_mouse_click_in(377, 37, 441, 48 * gInventorySlotsCount + 37)) { + int quantityToMove; + if (quantity > 1) { + quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity); + } else { + quantityToMove = 1; + } + + if (quantityToMove != -1) { + if (_gIsSteal) { + if (skillsPerformStealing(_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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } + } else { + if (_mouse_click_in(256, 37, 320, 48 * gInventorySlotsCount + 37)) { + int quantityToMove; + if (quantity > 1) { + quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity); + } else { + quantityToMove = 1; + } + + if (quantityToMove != -1) { + if (_gIsSteal) { + if (skillsPerformStealing(_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 = buildFid((a3->fid & 0xF000000) >> 24, a3->fid & 0xFFF, (a3->fid & 0xFF0000) >> 16, 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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } + } + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + + return rc; +} + +// 0x474B2C +int _barter_compute_value(Object* a1, Object* a2) +{ + if (gGameDialogSpeakerIsPartyMember) { + return objectGetInventoryWeight(_btable); + } + + int cost = objectGetCost(_btable); + int caps = itemGetTotalCaps(_btable); + int v14 = cost - caps; + + double bonus = 0.0; + if (a1 == gDude) { + if (perkHasRank(gDude, PERK_MASTER_TRADER)) { + bonus = 25.0; + } + } + + int partyBarter = partyGetBestSkillValue(SKILL_BARTER); + int npcBarter = skillGetValue(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 +int _barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4) +{ + MessageListItem messageListItem; + + int v8 = critterGetStat(a1, STAT_CARRY_WEIGHT) - objectGetInventoryWeight(a1); + if (objectGetInventoryWeight(a4) > v8) { + // Sorry, you cannot carry that much. + messageListItem.num = 31; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + gameDialogRenderSupplementaryMessage(messageListItem.text); + } + return -1; + } + + if (gGameDialogSpeakerIsPartyMember) { + int v10 = critterGetStat(a3, STAT_CARRY_WEIGHT) - objectGetInventoryWeight(a3); + if (objectGetInventoryWeight(a2) > v10) { + // Sorry, that's too much to carry. + messageListItem.num = 32; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + gameDialogRenderSupplementaryMessage(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 || miscItemTurnOff(a2) == -1) { + v11 = true; + } + } + } + + if (!v11) { + int cost = objectGetCost(a2); + if (_barter_compute_value(a1, a3) > cost) { + v11 = true; + } + } + + if (v11) { + // No, your offer is not good enough. + messageListItem.num = 28; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + gameDialogRenderSupplementaryMessage(messageListItem.text); + } + return -1; + } + } + + _item_move_all(a4, a1); + _item_move_all(a2, a3); + return 0; +} + +// 0x474DAC +void _barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7) +{ + Rect rect; + if (a7) { + rect.left = 23; + rect.top = 48 * a3 + 34; + } else { + rect.left = 395; + rect.top = 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 = windowGetBuffer(gInventoryWindow); + unsigned char* src = windowGetBuffer(_barter_back_win); + + int pitch = _scr_size.right - _scr_size.left + 1; + blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, 64, 48, pitch, dest + 480 * rect.top + rect.left, 480); + + rect.right = rect.left + 64 - 1; + rect.bottom = rect.top + 48 - 1; + windowRefreshRect(gInventoryWindow, &rect); + } + + CacheEntry* inventoryFrmHandle; + int inventoryFid = itemGetInventoryFid(a1); + Art* inventoryFrm = artLock(inventoryFid, &inventoryFrmHandle); + if (inventoryFrm != NULL) { + int width = artGetWidth(inventoryFrm, 0, 0); + int height = artGetHeight(inventoryFrm, 0, 0); + unsigned char* data = artGetFrameData(inventoryFrm, 0, 0); + mouseSetFrame(data, width, height, width, width / 2, height / 2, 0); + soundPlayFile("ipickup1"); + } + + do { + _get_input(); + } while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); + + if (inventoryFrm != NULL) { + artUnlock(inventoryFrmHandle); + soundPlayFile("iputdown"); + } + + MessageListItem messageListItem; + + if (a7) { + if (_mouse_click_in(245, 310, 309, 48 * gInventorySlotsCount + 310)) { + int quantityToMove = quantity > 1 ? inventoryQuantitySelect(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } else { + if (_mouse_click_in(330, 310, 394, 48 * gInventorySlotsCount + 310)) { + int quantityToMove = quantity > 1 ? inventoryQuantitySelect(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); +} + +// 0x475070 +void _barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6) +{ + Rect rect; + if (a6) { + rect.left = 169; + rect.top = 48 * a3 + 24; + } else { + rect.left = 254; + rect.top = 48 * a3 + 24; + } + + if (quantity > 1) { + if (a6) { + inventoryWindowRenderInnerInventories(_barter_back_win, a5, NULL, a3); + } else { + inventoryWindowRenderInnerInventories(_barter_back_win, NULL, a5, a3); + } + } else { + unsigned char* dest = windowGetBuffer(gInventoryWindow); + unsigned char* src = windowGetBuffer(_barter_back_win); + + int pitch = _scr_size.right - _scr_size.left + 1; + blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, 64, 48, pitch, dest + 480 * rect.top + rect.left, 480); + + rect.right = rect.left + 64 - 1; + rect.bottom = rect.top + 48 - 1; + windowRefreshRect(gInventoryWindow, &rect); + } + + CacheEntry* inventoryFrmHandle; + int inventoryFid = itemGetInventoryFid(a1); + Art* inventoryFrm = artLock(inventoryFid, &inventoryFrmHandle); + if (inventoryFrm != NULL) { + int width = artGetWidth(inventoryFrm, 0, 0); + int height = artGetHeight(inventoryFrm, 0, 0); + unsigned char* data = artGetFrameData(inventoryFrm, 0, 0); + mouseSetFrame(data, width, height, width, width / 2, height / 2, 0); + soundPlayFile("ipickup1"); + } + + do { + _get_input(); + } while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); + + if (inventoryFrm != NULL) { + artUnlock(inventoryFrmHandle); + soundPlayFile("iputdown"); + } + + MessageListItem messageListItem; + + if (a6) { + if (_mouse_click_in(80, 310, 144, 48 * gInventorySlotsCount + 310)) { + int quantityToMove = quantity > 1 ? inventoryQuantitySelect(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } else { + if (_mouse_click_in(475, 310, 539, 48 * gInventorySlotsCount + 310)) { + int quantityToMove = quantity > 1 ? inventoryQuantitySelect(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + } + } + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); +} + +// 0x475334 +void inventoryWindowRenderInnerInventories(int win, Object* a2, Object* a3, int a4) +{ + unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + char formattedText[80]; + int v45 = fontGetLineHeight() + 48 * gInventorySlotsCount; + + if (a2 != NULL) { + unsigned char* src = windowGetBuffer(win); + blitBufferToBuffer(src + (_scr_size.right - _scr_size.left + 1) * 20 + 249, 64, v45 + 1, _scr_size.right - _scr_size.left + 1, windowBuffer + 480 * 20 + 169, 480); + + unsigned char* dest = windowBuffer + 480 * 24 + 169; + Inventory* inventory = &(a2->data.inventory); + for (int index = 0; index < gInventorySlotsCount && index + _ptable_offset < inventory->length; index++) { + InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + _ptable_offset + 1)]); + int inventoryFid = itemGetInventoryFid(inventoryItem->item); + artRender(inventoryFid, dest, 56, 40, 480); + _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); + + dest += 480 * 48; + } + + if (gGameDialogSpeakerIsPartyMember) { + MessageListItem messageListItem; + messageListItem.num = 30; + + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + int weight = objectGetInventoryWeight(a2); + sprintf(formattedText, "%s %d", messageListItem.text, weight); + } + } else { + int cost = objectGetCost(a2); + sprintf(formattedText, "$%d", cost); + } + + fontDrawText(windowBuffer + 480 * (48 * gInventorySlotsCount + 24) + 169, formattedText, 80, 480, _colorTable[32767]); + + Rect rect; + rect.left = 169; + rect.top = 24; + rect.right = 223; + rect.bottom = rect.top + v45; + windowRefreshRect(gInventoryWindow, &rect); + } + + if (a3 != NULL) { + unsigned char* src = windowGetBuffer(win); + blitBufferToBuffer(src + (_scr_size.right - _scr_size.left + 1) * 20 + 334, 64, v45 + 1, _scr_size.right - _scr_size.left + 1, windowBuffer + 480 * 20 + 254, 480); + + unsigned char* dest = windowBuffer + 480 * 24 + 254; + Inventory* inventory = &(a3->data.inventory); + for (int index = 0; index < gInventorySlotsCount && index + _btable_offset < inventory->length; index++) { + InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + _btable_offset + 1)]); + int inventoryFid = itemGetInventoryFid(inventoryItem->item); + artRender(inventoryFid, dest, 56, 40, 480); + _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); + + dest += 480 * 48; + } + + if (gGameDialogSpeakerIsPartyMember) { + MessageListItem messageListItem; + messageListItem.num = 30; + + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + int weight = _barter_compute_value(gDude, _target_stack[0]); + sprintf(formattedText, "%s %d", messageListItem.text, weight); + } + } else { + int cost = _barter_compute_value(gDude, _target_stack[0]); + sprintf(formattedText, "$%d", cost); + } + + fontDrawText(windowBuffer + 480 * (48 * gInventorySlotsCount + 24) + 254, formattedText, 80, 480, _colorTable[32767]); + + Rect rect; + rect.left = 254; + rect.top = 24; + rect.right = 318; + rect.bottom = rect.top + v45; + windowRefreshRect(gInventoryWindow, &rect); + } + + fontSetCurrent(oldFont); +} + +// 0x4757F0 +void inventoryOpenTrade(int win, Object* a2, Object* a3, Object* a4, int a5) +{ + _barter_mod = a5; + + if (inventoryCommonInit() == -1) { + return; + } + + Object* armor = critterGetArmor(a2); + if (armor != NULL) { + itemRemove(a2, armor, 1); + } + + Object* item1 = NULL; + Object* item2 = critterGetItem2(a2); + if (item2 != NULL) { + itemRemove(a2, item2, 1); + } else { + if (!gGameDialogSpeakerIsPartyMember) { + item1 = _inven_find_type(a2, ITEM_TYPE_WEAPON, NULL); + if (item1 != NULL) { + itemRemove(a2, item1, 1); + } + } + } + + Object* a1a = NULL; + if (objectCreateWithFidPid(&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); + windowRefresh(_barter_back_win); + inventoryWindowRenderInnerInventories(win, a3, a4, -1); + + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + + int modifier; + int npcReactionValue = reactionGetValue(a2); + int npcReactionType = reactionTranslateValue(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) { + showQuitConfirmationDialog(); + } + + 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, gDude); + _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); + inventoryWindowRenderInnerInventories(win, a3, a4, -1); + + // Ok, that's a good trade. + MessageListItem messageListItem; + messageListItem.num = 27; + if (!gGameDialogSpeakerIsPartyMember) { + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + gameDialogRenderSupplementaryMessage(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; + inventoryWindowRenderInnerInventories(win, a3, a4, -1); + } + } else if (keyCode == KEY_ARROW_DOWN) { + if (_stack_offset[_curr_stack] + gInventorySlotsCount < _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 + gInventorySlotsCount < _ptable_pud->length) { + _ptable_offset += 1; + inventoryWindowRenderInnerInventories(win, a3, a4, -1); + } + } else if (keyCode == KEY_CTRL_PAGE_DOWN) { + if (_btable_offset + gInventorySlotsCount < _btable_pud->length) { + _btable_offset++; + inventoryWindowRenderInnerInventories(win, a3, a4, -1); + } + } else if (keyCode == KEY_CTRL_PAGE_UP) { + if (_btable_offset > 0) { + _btable_offset -= 1; + inventoryWindowRenderInnerInventories(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); + windowRefresh(gInventoryWindow); + } + } else if (keyCode == KEY_CTRL_ARROW_DOWN) { + if (_target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _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); + windowRefresh(gInventoryWindow); + } + } else if (keyCode >= 2500 && keyCode <= 2501) { + _container_exit(keyCode, INVENTORY_WINDOW_TYPE_TRADE); + } else { + if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + } else { + inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); + } + } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { + if (keyCode >= 1000 && keyCode <= 1000 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); + inventoryWindowRenderInnerInventories(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); + inventoryWindowRenderInnerInventories(win, a3, NULL, -1); + } + } + + keyCode = -1; + } else if (keyCode >= 2000 && keyCode <= 2000 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); + inventoryWindowRenderInnerInventories(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); + inventoryWindowRenderInnerInventories(win, NULL, a4, -1); + } + } + + keyCode = -1; + } else if (keyCode >= 2300 && keyCode <= 2300 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); + inventoryWindowRenderInnerInventories(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); + inventoryWindowRenderInnerInventories(win, a3, NULL, -1); + } + } + + keyCode = -1; + } else if (keyCode >= 2400 && keyCode <= 2400 + gInventorySlotsCount) { + if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { + inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); + inventoryWindowRenderInnerInventories(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); + inventoryWindowRenderInnerInventories(win, NULL, a4, -1); + } + } + + keyCode = -1; + } + } + } + } + + _item_move_all(a1a, a2); + objectDestroy(a1a, NULL); + + if (armor != NULL) { + armor->flags |= OBJECT_WORN; + itemAdd(a2, armor, 1); + } + + if (item2 != NULL) { + item2->flags |= OBJECT_IN_RIGHT_HAND; + itemAdd(a2, item2, 1); + } + + if (item1 != NULL) { + itemAdd(a2, item1, 1); + } + + _exit_inventory(isoWasEnabled); + + // NOTE: Uninline. + inventoryCommonFree(); +} + +// 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 (itemGetType(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); + windowRefresh(gInventoryWindow); + } + } + } 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 (itemGetType(item) == ITEM_TYPE_CONTAINER) { + _curr_stack += 1; + + _stack[_curr_stack] = item; + _stack_offset[_curr_stack] = 0; + + _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); + windowRefresh(gInventoryWindow); + } + } +} + +// 0x476464 +int _drop_into_container(Object* a1, Object* a2, int a3, Object** a4, int quantity) +{ + int quantityToMove; + if (quantity > 1) { + quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a2, quantity); + } else { + quantityToMove = 1; + } + + if (quantityToMove == -1) { + return -1; + } + + if (a3 != -1) { + if (itemRemove(_inven_dude, a2, quantityToMove) == -1) { + return -1; + } + } + + int rc = itemAttemptAdd(a1, a2, quantityToMove); + if (rc != 0) { + if (a3 != -1) { + itemAttemptAdd(_inven_dude, a2, quantityToMove); + } + } else { + if (a4 != NULL) { + if (a4 == &gInventoryArmor) { + _adjust_ac(_stack[0], gInventoryArmor, NULL); + } + *a4 = NULL; + } + } + + return rc; +} + +// 0x47650C +int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** a3, int quantity, int keyCode) +{ + if (itemGetType(weapon) != ITEM_TYPE_WEAPON) { + return -1; + } + + if (itemGetType(ammo) != ITEM_TYPE_AMMO) { + return -1; + } + + if (!weaponCanBeReloadedWith(weapon, ammo)) { + return -1; + } + + int quantityToMove; + if (quantity > 1) { + quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, ammo, quantity); + } else { + quantityToMove = 1; + } + + if (quantityToMove == -1) { + return -1; + } + + Object* v14 = ammo; + bool v17 = false; + int rc = itemRemove(_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) { + itemAdd(_inven_dude, weapon, 1); + } + + if (!v17) { + return -1; + } + + const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL); + soundPlayFile(sfx); + + return 0; +} + +// 0x47664C +void _draw_amount(int value, int inventoryWindowType) +{ + // BIGNUM.frm + CacheEntry* handle; + int fid = buildFid(6, 170, 0, 0, 0); + unsigned char* data = artLockFrameData(fid, 0, 0, &handle); + if (data == NULL) { + return; + } + + Rect rect; + + int windowWidth = windowGetWidth(_mt_wid); + unsigned char* windowBuffer = windowGetBuffer(_mt_wid); + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { + rect.left = 125; + rect.top = 45; + rect.right = 195; + rect.bottom = 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.top * windowWidth + rect.left; + + for (int index = 0; index < 5; index++) { + unsigned char* src = data + 14 * ranks[index]; + blitBufferToBuffer(src, 14, 24, 336, windowBuffer, windowWidth); + windowBuffer += 14; + } + } else { + rect.left = 133; + rect.top = 64; + rect.right = 189; + rect.bottom = 88; + + windowBuffer += windowWidth * rect.top + rect.left; + blitBufferToBuffer(data + 14 * (value / 60), 14, 24, 336, windowBuffer, windowWidth); + blitBufferToBuffer(data + 14 * (value % 60 / 10), 14, 24, 336, windowBuffer + 14 * 2, windowWidth); + blitBufferToBuffer(data + 14 * (value % 10), 14, 24, 336, windowBuffer + 14 * 3, windowWidth); + } + + artUnlock(handle); + windowRefreshRect(_mt_wid, &rect); +} + +// 0x47688C +int inventoryQuantitySelect(int inventoryWindowType, Object* item, int max) +{ + inventoryQuantityWindowInit(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) { + inventoryQuantityWindowFree(inventoryWindowType); + return -1; + } + + if (keyCode == KEY_RETURN) { + if (value >= min && value <= max) { + if (inventoryWindowType != INVENTORY_WINDOW_TYPE_SET_TIMER || value % 10 == 0) { + soundPlayFile("ib1p1xx1"); + break; + } + } + + soundPlayFile("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 ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { + _get_time(); + + unsigned int delay = 100; + while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { + if (value < max) { + value++; + } + + _draw_amount(value, inventoryWindowType); + _get_input(); + + if (delay > 1) { + delay--; + coreDelayProcessingEvents(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 ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { + _get_time(); + + unsigned int delay = 100; + while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { + if (value > min) { + value--; + } + + _draw_amount(value, inventoryWindowType); + _get_input(); + + if (delay > 1) { + delay--; + coreDelayProcessingEvents(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; + } + } + } + + inventoryQuantityWindowFree(inventoryWindowType); + + return value; +} + +// Creates move items/set timer interface. +// +// 0x476AB8 +int inventoryQuantityWindowInit(int inventoryWindowType, Object* item) +{ + const int oldFont = fontGetCurrent(); + fontSetCurrent(103); + + for (int index = 0; index < 8; index++) { + _mt_key[index] = NULL; + } + + const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); + + _mt_wid = windowCreate(windowDescription->x, windowDescription->y, windowDescription->width, windowDescription->height, 257, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + unsigned char* windowBuffer = windowGetBuffer(_mt_wid); + + CacheEntry* backgroundHandle; + int backgroundFid = buildFid(6, windowDescription->field_0, 0, 0, 0); + unsigned char* backgroundData = artLockFrameData(backgroundFid, 0, 0, &backgroundHandle); + if (backgroundData != NULL) { + blitBufferToBuffer(backgroundData, windowDescription->width, windowDescription->height, windowDescription->width, windowBuffer, windowDescription->width); + artUnlock(backgroundHandle); + } + + MessageListItem messageListItem; + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { + // MOVE ITEMS + messageListItem.num = 21; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + int length = fontGetStringWidth(messageListItem.text); + fontDrawText(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 (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + int length = fontGetStringWidth(messageListItem.text); + fontDrawText(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, _colorTable[21091]); + } + + // Timer overlay + CacheEntry* overlayFrmHandle; + int overlayFid = buildFid(6, 306, 0, 0, 0); + unsigned char* overlayFrmData = artLockFrameData(overlayFid, 0, 0, &overlayFrmHandle); + if (overlayFrmData != NULL) { + blitBufferToBuffer(overlayFrmData, 105, 81, 105, windowBuffer + 34 * windowDescription->width + 113, windowDescription->width); + artUnlock(overlayFrmHandle); + } + } + + int inventoryFid = itemGetInventoryFid(item); + artRender(inventoryFid, windowBuffer + windowDescription->width * 46 + 16, 90, 61, 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 = buildFid(6, 193, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_mt_key[0])); + + fid = buildFid(6, 194, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_mt_key[1])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + btn = buttonCreate(_mt_wid, x, y, 16, 12, -1, -1, 6000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + + // Minus button + fid = buildFid(6, 191, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_mt_key[2])); + + fid = buildFid(6, 192, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_mt_key[3])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + btn = buttonCreate(_mt_wid, x, y + 12, 17, 12, -1, -1, 7000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + + fid = buildFid(6, 8, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_mt_key[4])); + + fid = buildFid(6, 9, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_mt_key[5])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // Done + btn = buttonCreate(_mt_wid, 98, 128, 15, 16, -1, -1, -1, KEY_RETURN, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // Cancel + btn = buttonCreate(_mt_wid, 148, 128, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { + fid = buildFid(6, 307, 0, 0, 0); + buttonUpData = artLockFrameData(fid, 0, 0, &(_mt_key[6])); + + fid = buildFid(6, 308, 0, 0, 0); + buttonDownData = artLockFrameData(fid, 0, 0, &(_mt_key[7])); + + if (buttonUpData != NULL && buttonDownData != NULL) { + // ALL + messageListItem.num = 22; + if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { + int length = fontGetStringWidth(messageListItem.text); + + // TODO: Where is y? Is it hardcoded in to 376? + fontDrawText(buttonUpData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, _colorTable[21091]); + fontDrawText(buttonDownData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, _colorTable[18977]); + + btn = buttonCreate(_mt_wid, 120, 80, 94, 33, -1, -1, -1, 5000, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + } + } + } + + windowRefresh(_mt_wid); + inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); + fontSetCurrent(oldFont); + + return 0; +} + +// 0x477030 +int inventoryQuantityWindowFree(int inventoryWindowType) +{ + int count = inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS ? 8 : 6; + + for (int index = 0; index < count; index++) { + artUnlock(_mt_key[index]); + } + + windowDestroy(_mt_wid); + + return 0; +} + +// 0x477074 +int _inven_set_timer(Object* a1) +{ + bool v1 = _inven_is_initialized; + + if (!v1) { + if (inventoryCommonInit() == -1) { + return -1; + } + } + + int seconds = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_SET_TIMER, a1, 180); + + if (!v1) { + // NOTE: Uninline. + inventoryCommonFree(); + } + + return seconds; +} diff --git a/src/inventory.h b/src/inventory.h new file mode 100644 index 0000000..0cf7395 --- /dev/null +++ b/src/inventory.h @@ -0,0 +1,193 @@ +#ifndef INVENTORY_H +#define INVENTORY_H + +#include "art.h" +#include "cache.h" +#include "message.h" +#include "obj_types.h" + +#include + +#define INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY (1000U / ROTATION_COUNT) +#define OFF_59E7BC_COUNT 12 + +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 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; + +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; + +typedef void InventoryPrintItemDescriptionHandler(char* string); + +extern const int dword_46E6D0[7]; +extern const int dword_46E6EC[7]; +extern const int gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_COUNT]; + +extern int gInventorySlotsCount; +extern Object* _inven_dude; +extern int _inven_pid; +extern bool _inven_is_initialized; +extern int _inven_display_msg_line; +extern const InventoryWindowDescription gInventoryWindowDescriptions[INVENTORY_WINDOW_TYPE_COUNT]; +extern bool _dropped_explosive; +extern int gInventoryScrollUpButton; +extern int gInventoryScrollDownButton; +extern int gSecondaryInventoryScrollUpButton; +extern int gSecondaryInventoryScrollDownButton; +extern unsigned int gInventoryWindowDudeRotationTimestamp; +extern int gInventoryWindowDudeRotation; +extern const int gInventoryWindowCursorFrmIds[INVENTORY_WINDOW_CURSOR_COUNT]; +extern Object* _last_target; +extern const int _act_use[4]; +extern const int _act_no_use[3]; +extern const int _act_just_use[3]; +extern const int _act_nothing[2]; +extern const int _act_weap[4]; +extern const int _act_weap2[3]; + +extern CacheEntry* _mt_key[8]; +extern CacheEntry* _ikey[OFF_59E7BC_COUNT]; +extern int _target_stack_offset[10]; +extern MessageList gInventoryMessageList; +extern Object* _target_stack[10]; +extern int _stack_offset[10]; +extern Object* _stack[10]; +extern int _mt_wid; +extern int _barter_mod; +extern int _btable_offset; +extern int _ptable_offset; +extern Inventory* _ptable_pud; +extern InventoryCursorData gInventoryCursorData[INVENTORY_WINDOW_CURSOR_COUNT]; +extern Object* _ptable; +extern InventoryPrintItemDescriptionHandler* gInventoryPrintItemDescriptionHandler; +extern int _im_value; +extern int gInventoryCursor; +extern Object* _btable; +extern int _target_curr_stack; +extern Inventory* _btable_pud; +extern bool _inven_ui_was_disabled; +extern Object* gInventoryArmor; +extern Object* gInventoryLeftHandItem; +extern int gInventoryWindowDudeFid; +extern Inventory* _pud; +extern int gInventoryWindow; +extern Object* gInventoryRightHandItem; +extern int _curr_stack; +extern int gInventoryWindowMaxY; +extern int gInventoryWindowMaxX; +extern Inventory* _target_pud; +extern int _barter_back_win; + +void _inven_reset_dude(); +int inventoryMessageListInit(); +int inventoryMessageListFree(); +void inventoryOpen(); +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_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5); +void _display_body(int fid, int inventoryWindowType); +int inventoryCommonInit(); +void inventoryCommonFree(); +void inventorySetCursor(int cursor); +void inventoryItemSlotOnMouseEnter(int btn, int keyCode); +void inventoryItemSlotOnMouseExit(int btn, int keyCode); +void _inven_update_lighting(Object* a1); +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 inventoryOpenUseItemOn(Object* a1); +Object* critterGetItem2(Object* obj); +Object* critterGetItem1(Object* obj); +Object* critterGetArmor(Object* obj); +Object* objectGetCarriedObjectByPid(Object* obj, int pid); +int objectGetCarriedQuantityByPid(Object* obj, int pid); +void inventoryRenderSummary(); +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 inventoryRenderItemDescription(char* string); +void inventoryExamineItem(Object* critter, Object* item); +void inventoryWindowOpenContextMenu(int eventCode, int inventoryWindowType); +int inventoryOpenLooting(Object* a1, Object* a2); +int inventoryOpenStealing(Object* a1, Object* a2); +int _move_inventory(Object* a1, int a2, Object* a3, bool a4); +int _barter_compute_value(Object* a1, Object* a2); +int _barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4); +void _barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7); +void _barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6); +void inventoryWindowRenderInnerInventories(int win, Object* a2, Object* a3, int a4); +void inventoryOpenTrade(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 inventoryQuantitySelect(int inventoryWindowType, Object* item, int a3); +int inventoryQuantityWindowInit(int inventoryWindowType, Object* item); +int inventoryQuantityWindowFree(int inventoryWindowType); +int _inven_set_timer(Object* a1); + +#endif /* INVENTORY_H */ diff --git a/src/item.c b/src/item.c new file mode 100644 index 0000000..cf156fa --- /dev/null +++ b/src/item.c @@ -0,0 +1,3197 @@ +#include "item.h" + +#include "animation.h" +#include "automap.h" +#include "combat.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "interface.h" +#include "inventory.h" +#include "light.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "queue.h" +#include "random.h" +#include "skill.h" +#include "stat.h" +#include "tile.h" +#include "trait.h" + +#include + +// 0x509FFC +char _aItem_1[] = ""; + +// Maps weapon extended flags to skill. +// +// 0x519160 +const 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 +const 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 +const 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 gDrugDescriptions[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 }, +}; + +// 0x519238 +char* _name_item = _aItem_1; + +// item.msg +// +// 0x59E980 +MessageList gItemsMessageList; + +// 0x59E988 +int _wd_onset; + +// 0x59E98C +Object* _wd_obj; + +// 0x59E990 +int _wd_gvar; + +// 0x4770E0 +int itemsInit() +{ + if (!messageListInit(&gItemsMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "item.msg"); + + if (!messageListLoad(&gItemsMessageList, path)) { + return -1; + } + + return 0; +} + +// 0x477144 +void itemsReset() +{ + return; +} + +// 0x477148 +void itemsExit() +{ + messageListFree(&gItemsMessageList); +} + +// NOTE: Collapsed. +// +// 0x477154 +int _item_load_(File* stream) +{ + return 0; +} + +// NOTE: Uncollapsed 0x477154. +int itemsLoad(File* stream) +{ + return _item_load_(stream); +} + +// NOTE: Uncollapsed 0x477154. +int itemsSave(File* stream) +{ + return _item_load_(stream); +} + +// 0x477158 +int itemAttemptAdd(Object* owner, Object* itemToAdd, int quantity) +{ + if (quantity < 1) { + return -1; + } + + int parentType = (owner->fid & 0xF000000) >> 24; + if (parentType == OBJ_TYPE_ITEM) { + int itemType = itemGetType(owner); + if (itemType == ITEM_TYPE_CONTAINER) { + // NOTE: Uninline. + int sizeToAdd = itemGetSize(itemToAdd); + sizeToAdd *= quantity; + + int currentSize = containerGetTotalSize(owner); + int maxSize = containerGetMaxSize(owner); + if (currentSize + sizeToAdd >= maxSize) { + return -6; + } + + Object* containerOwner = objectGetOwner(owner); + if (containerOwner != NULL) { + if (((containerOwner->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + int weightToAdd = itemGetWeight(itemToAdd); + weightToAdd *= quantity; + + int currentWeight = objectGetInventoryWeight(containerOwner); + int maxWeight = critterGetStat(containerOwner, STAT_CARRY_WEIGHT); + if (currentWeight + weightToAdd > maxWeight) { + return -6; + } + } + } + } else if (itemType == ITEM_TYPE_MISC) { + // NOTE: Uninline. + int powerTypePid = miscItemGetPowerTypePid(owner); + if (powerTypePid != itemToAdd->pid) { + return -1; + } + } else { + return -1; + } + } else if (parentType == OBJ_TYPE_CRITTER) { + if (critterGetBodyType(owner) != BODY_TYPE_BIPED) { + return -5; + } + + int weightToAdd = itemGetWeight(itemToAdd); + weightToAdd *= quantity; + + int currentWeight = objectGetInventoryWeight(owner); + int maxWeight = critterGetStat(owner, STAT_CARRY_WEIGHT); + if (currentWeight + weightToAdd > maxWeight) { + return -6; + } + } + + return itemAdd(owner, itemToAdd, quantity); +} + +// item_add +// 0x4772B8 +int itemAdd(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 = internal_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. + stealthBoyTurnOn(owner); + } + } + + inventory->length++; + itemToAdd->owner = owner; + + return 0; + } + + if (itemToAdd == inventory->items[index].item) { + debugPrint("Warning! Attempt to add same item twice in item_add()\n"); + return 0; + } + + if (itemGetType(itemToAdd) == ITEM_TYPE_AMMO) { + // NOTE: Uninline. + int ammoQuantityToAdd = ammoGetQuantity(itemToAdd); + + int ammoQuantity = ammoGetQuantity(inventory->items[index].item); + + // NOTE: Uninline. + int capacity = ammoGetCapacity(itemToAdd); + + ammoQuantity += ammoQuantityToAdd; + if (ammoQuantity > capacity) { + ammoSetQuantity(itemToAdd, ammoQuantity - capacity); + inventory->items[index].quantity++; + } else { + ammoSetQuantity(itemToAdd, ammoQuantity); + } + + inventory->items[index].quantity += quantity - 1; + } else { + inventory->items[index].quantity += quantity; + } + + objectDestroy(inventory->items[index].item, NULL); + inventory->items[index].item = itemToAdd; + itemToAdd->owner = owner; + + return 0; +} + +// 0x477490 +int itemRemove(Object* owner, Object* itemToRemove, int quantity) +{ + Inventory* inventory = &(owner->data.inventory); + Object* item1 = critterGetItem1(owner); + Object* item2 = critterGetItem2(owner); + + int index = 0; + for (; index < inventory->length; index++) { + InventoryItem* inventoryItem = &(inventory->items[index]); + if (inventoryItem->item == itemToRemove) { + break; + } + + if (itemGetType(inventoryItem->item) == ITEM_TYPE_CONTAINER) { + if (itemRemove(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 (itemGetType(itemToRemove) == ITEM_TYPE_AMMO) { + int capacity = ammoGetCapacity(itemToRemove); + ammoSetQuantity(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 = objectGetOwner(itemToRemove); + if (owner != NULL) { + stealthBoyTurnOff(owner, itemToRemove); + } + } + } + + itemToRemove->owner = NULL; + itemToRemove->flags &= ~OBJECT_EQUIPPED; + + return 0; +} + +// NOTE: Inlined. +// +// 0x4775D8 +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 +int _item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5) +{ + if (itemRemove(a1, a3, quantity) == -1) { + return -1; + } + + int rc; + if (a5) { + rc = itemAdd(a2, a3, quantity); + } else { + rc = itemAttemptAdd(a2, a3, quantity); + } + + if (rc != 0) { + if (itemAdd(a1, a3, quantity) != 0) { + Object* owner = objectGetOwner(a1); + if (owner == NULL) { + owner = a1; + } + + if (owner->tile != -1) { + Rect updatedRect; + _obj_connect(a3, owner->tile, owner->elevation, &updatedRect); + tileWindowRefreshRect(&updatedRect, gElevation); + } + } + 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 (inventoryItem->item->pid >> 24 == OBJ_TYPE_ITEM) { + Proto* proto; + if (protoGetProto(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 (inventoryItem->item->pid >> 24 == OBJ_TYPE_ITEM) { + Proto* proto; + if (protoGetProto(inventoryItem->item->pid, &proto) != -1) { + v5 = (proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon) == 0; + } else { + v5 = true; + } + } else { + v5 = true; + } + + if (!v5) { + itemRemove(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 (itemRemove(critter, item, inventoryItem->quantity) != 0) { + return -1; + } + + if (_obj_connect(item, tile, critter->elevation, NULL) != 0) { + if (itemAdd(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 (protoGetProto(critter->pid, &proto) == -1) { + return -1; + } + + frmId = proto->fid & 0xFFF; + _adjust_ac(critter, item, NULL); + } + } + + for (int index = 0; index < inventoryItem->quantity; index++) { + if (itemRemove(critter, item, 1) != 0) { + return -1; + } + + if (_obj_connect(item, tile, critter->elevation, NULL) != 0) { + if (itemAdd(critter, item, 1) != 0) { + _obj_destroy(item); + } + return -1; + } + } + } + } + + if (hasEquippedItems) { + Rect updatedRect; + int fid = buildFid(1, frmId, (critter->fid & 0xFF0000) >> 16, 0, (critter->fid & 0x70000000) >> 28); + objectSetFid(critter, fid, &updatedRect); + if (((critter->fid & 0xFF0000) >> 16) == 0) { + tileWindowRefreshRect(&updatedRect, gElevation); + } + } + + return -1; +} + +// 0x4779F0 +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_FLAG_0x2000)) != 0) { + return false; + } + + if ((a2->flags & (OBJECT_EQUIPPED | OBJECT_FLAG_0x2000)) != 0) { + return false; + } + + Proto* proto; + protoGetProto(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* itemGetName(Object* obj) +{ + _name_item = protoGetName(obj->pid); + return _name_item; +} + +// 0x477AF4 +char* itemGetDescription(Object* obj) +{ + return protoGetDescription(obj->pid); +} + +// 0x477AFC +int itemGetType(Object* item) +{ + if (item == NULL) { + return ITEM_TYPE_MISC; + } + + if ((item->pid >> 24) != OBJ_TYPE_ITEM) { + return ITEM_TYPE_MISC; + } + + if (item->pid == PROTO_ID_SHIV) { + return ITEM_TYPE_MISC; + } + + Proto* proto; + protoGetProto(item->pid, &proto); + + return proto->item.type; +} + +// NOTE: Unused. +// +// 0x477B4C +int itemGetMaterial(Object* item) +{ + Proto* proto; + protoGetProto(item->pid, &proto); + + return proto->item.material; +} + +// 0x477B68 +int itemGetSize(Object* item) +{ + if (item == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(item->pid, &proto); + + return proto->item.size; +} + +// 0x477B88 +int itemGetWeight(Object* item) +{ + if (item == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(item->pid, &proto); + int weight = proto->item.weight; + + // NOTE: Uninline. + if (weaponIsNatural(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 += objectGetInventoryWeight(item); + } else if (itemType == ITEM_TYPE_WEAPON) { + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(item); + if (ammoQuantity > 0) { + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(item); + if (ammoTypePid != -1) { + Proto* ammoProto; + if (protoGetProto(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 itemGetCost(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; + protoGetProto(obj->pid, &proto); + + int cost = proto->item.cost; + + switch (proto->item.type) { + case ITEM_TYPE_CONTAINER: + cost += objectGetCost(obj); + break; + case ITEM_TYPE_WEAPON: + if (1) { + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(obj); + if (ammoQuantity > 0) { + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(obj); + if (ammoTypePid != -1) { + Proto* ammoProto; + protoGetProto(ammoTypePid, &ammoProto); + + cost += ammoQuantity * ammoProto->item.cost / ammoProto->item.data.ammo.quantity; + } + } + } + break; + case ITEM_TYPE_AMMO: + if (1) { + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(obj); + cost *= ammoQuantity; + // NOTE: Uninline. + int ammoCapacity = ammoGetCapacity(obj); + cost /= ammoCapacity; + } + break; + } + + return cost; +} + +// Returns cost of object's items. +// +// 0x477DAC +int objectGetCost(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 (itemGetType(inventoryItem->item) == ITEM_TYPE_AMMO) { + Proto* proto; + protoGetProto(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 += itemGetCost(inventoryItem->item); + } else { + cost += itemGetCost(inventoryItem->item) * inventoryItem->quantity; + } + } + + if ((obj->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + Object* item2 = critterGetItem2(obj); + if (item2 != NULL && (item2->flags & OBJECT_IN_RIGHT_HAND) == 0) { + cost += itemGetCost(item2); + } + + Object* item1 = critterGetItem1(obj); + if (item1 != NULL && (item1->flags & OBJECT_IN_LEFT_HAND) == 0) { + cost += itemGetCost(item1); + } + + Object* armor = critterGetArmor(obj); + if (armor != NULL && (armor->flags & OBJECT_WORN) == 0) { + cost += itemGetCost(armor); + } + } + + return cost; +} + +// Calculates total weight of the items in inventory. +// +// 0x477E98 +int objectGetInventoryWeight(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 += itemGetWeight(item) * inventoryItem->quantity; + } + + if (((obj->fid & 0xF000000) >> 24) == OBJ_TYPE_CRITTER) { + Object* item2 = critterGetItem2(obj); + if (item2 != NULL) { + if ((item2->flags & OBJECT_IN_RIGHT_HAND) == 0) { + weight += itemGetWeight(item2); + } + } + + Object* item1 = critterGetItem1(obj); + if (item1 != NULL) { + if ((item1->flags & OBJECT_IN_LEFT_HAND) == 0) { + weight += itemGetWeight(item1); + } + } + + Object* armor = critterGetArmor(obj); + if (armor != NULL) { + if ((armor->flags & OBJECT_WORN) == 0) { + weight += itemGetWeight(armor); + } + } + } + + return weight; +} + +// 0x477F3C +bool _can_use_weapon(Object* weapon) +{ + if (weapon == NULL) { + return false; + } + + if (itemGetType(weapon) != ITEM_TYPE_WEAPON) { + return false; + } + + int flags = gDude->data.critter.combat.results; + if ((flags & DAM_CRIP_ARM_LEFT) != 0 && (flags & DAM_CRIP_ARM_RIGHT) != 0) { + return true; + } + + // NOTE: Uninline. + bool isTwoHanded = weaponIsTwoHanded(weapon); + if (isTwoHanded) { + if ((flags & DAM_CRIP_ARM_LEFT) != 0 || (flags & DAM_CRIP_ARM_RIGHT) != 0) { + return true; + } + } + + return false; +} + +// 0x477FB0 +int itemGetInventoryFid(Object* item) +{ + Proto* proto; + + if (item == NULL) { + return -1; + } + + protoGetProto(item->pid, &proto); + + return proto->item.inventoryFid; +} + +// 0x477FF8 +Object* critterGetWeaponForHitMode(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 critterGetItem1(critter); + case HIT_MODE_RIGHT_WEAPON_PRIMARY: + case HIT_MODE_RIGHT_WEAPON_SECONDARY: + case HIT_MODE_RIGHT_WEAPON_RELOAD: + return critterGetItem2(critter); + } + + return NULL; +} + +// 0x478040 +int _item_mp_cost(Object* obj, int hitMode, bool aiming) +{ + if (obj == NULL) { + return 0; + } + + Object* item_obj = critterGetWeaponForHitMode(obj, hitMode); + + if (item_obj != NULL && itemGetType(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 (itemGetType(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_FLAG_0x2000) != 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_FLAG_0x2000) != 0) { + return true; + } + + if (itemGetType(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 (itemRemove(a1, item, 1) == 0) { + item->flags |= a3; + if (itemAdd(a1, item, 1) == 0) { + return item; + } + + item->flags &= ~a3; + if (itemAdd(a1, item, 1) != 0) { + _obj_destroy(item); + } + } + } + + if (itemGetType(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 weaponIsNatural(Object* obj) +{ + Proto* proto; + + if ((obj->pid >> 24) != OBJ_TYPE_ITEM) { + return 0; + } + + if (protoGetProto(obj->pid, &proto) == -1) { + return 0; + } + + return proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon; +} + +// 0x478280 +int weaponGetAttackTypeForHitMode(Object* weapon, int hitMode) +{ + if (weapon == NULL) { + return ATTACK_TYPE_UNARMED; + } + + Proto* proto; + protoGetProto(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 weaponGetSkillForHitMode(Object* weapon, int hitMode) +{ + if (weapon == NULL) { + return SKILL_UNARMED; + } + + Proto* proto; + protoGetProto(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 = weaponGetDamageType(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 = critterGetWeaponForHitMode(critter, hitMode); + if (weapon != NULL) { + skill = weaponGetSkillForHitMode(weapon, hitMode); + } else { + skill = SKILL_UNARMED; + } + + return skillGetValue(critter, skill); +} + +// 0x4783B8 +int weaponGetDamageMinMax(Object* weapon, int* minDamagePtr, int* maxDamagePtr) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(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 weaponGetMeleeDamage(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 = critterGetWeaponForHitMode(critter, hitMode); + + if (weapon != NULL) { + Proto* proto; + protoGetProto(weapon->pid, &proto); + + minDamage = proto->item.data.weapon.minDamage; + maxDamage = proto->item.data.weapon.maxDamage; + + int attackType = weaponGetAttackTypeForHitMode(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 randomBetween(unarmedDamage + minDamage, unarmedDamage + meleeDamage + maxDamage); +} + +// 0x478570 +int weaponGetDamageType(Object* critter, Object* weapon) +{ + Proto* proto; + + if (weapon != NULL) { + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.damageType; + } + + if (critter != NULL) { + return critterGetDamageType(critter); + } + + return 0; +} + +// 0x478598 +int weaponIsTwoHanded(Object* weapon) +{ + Proto* proto; + + if (weapon == NULL) { + return 0; + } + + protoGetProto(weapon->pid, &proto); + + return (proto->item.extendedFlags & WEAPON_TWO_HAND) != 0; +} + +// 0x4785DC +int critterGetAnimationForHitMode(Object* critter, int hitMode) +{ + // NOTE: Uninline. + Object* weapon = critterGetWeaponForHitMode(critter, hitMode); + return weaponGetAnimationForHitMode(weapon, hitMode); +} + +// 0x47860C +int weaponGetAnimationForHitMode(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; + protoGetProto(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 ammoGetCapacity(Object* ammoOrWeapon) +{ + if (ammoOrWeapon == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(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 ammoGetQuantity(Object* ammoOrWeapon) +{ + if (ammoOrWeapon == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(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 ammoGetCaliber(Object* ammoOrWeapon) +{ + Proto* proto; + + if (ammoOrWeapon == NULL) { + return 0; + } + + protoGetProto(ammoOrWeapon->pid, &proto); + + if (proto->item.type != ITEM_TYPE_AMMO) { + if (protoGetProto(ammoOrWeapon->data.item.weapon.ammoTypePid, &proto) == -1) { + return 0; + } + } + + return proto->item.data.ammo.caliber; +} + +// 0x478714 +void ammoSetQuantity(Object* ammoOrWeapon, int quantity) +{ + if (ammoOrWeapon == NULL) { + return; + } + + // NOTE: Uninline. + int capacity = ammoGetCapacity(ammoOrWeapon); + if (quantity > capacity) { + quantity = capacity; + } + + Proto* proto; + protoGetProto(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 = ammoGetQuantity(weapon); + int capacity = ammoGetCapacity(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 (weaponCanBeReloadedWith(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 (weaponCanBeReloadedWith(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 weaponCanBeReloadedWith(Object* weapon, Object* ammo) +{ + if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { + // Check light level to recharge solar scorcher. + if (lightGetLightLevel() > 62259) { + return true; + } + + // There is not enough light to recharge this item. + MessageListItem messageListItem; + char* msg = getmsg(&gItemsMessageList, &messageListItem, 500); + displayMonitorAddMessage(msg); + + return false; + } + + if (ammo == NULL) { + return false; + } + + Proto* weaponProto; + protoGetProto(weapon->pid, &weaponProto); + + Proto* ammoProto; + protoGetProto(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 (ammoGetQuantity(weapon) != 0) { + if (weapon->data.item.weapon.ammoTypePid != ammo->pid) { + return false; + } + } + + return true; +} + +// 0x478918 +int _item_w_reload(Object* weapon, Object* ammo) +{ + if (!weaponCanBeReloadedWith(weapon, ammo)) { + return -1; + } + + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(weapon); + + // NOTE: Uninline. + int ammoCapacity = ammoGetCapacity(weapon); + + if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { + ammoSetQuantity(weapon, ammoCapacity); + return 0; + } + + // NOTE: Uninline. + int v10 = ammoGetQuantity(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; + + ammoSetQuantity(ammo, v11); + ammoSetQuantity(weapon, v12); + } + + return v11; +} + +// 0x478A1C +int _item_w_range(Object* critter, int hitMode) +{ + int range; + int v12; + + // NOTE: Uninline. + Object* weapon = critterGetWeaponForHitMode(critter, hitMode); + + if (weapon != NULL && hitMode != 4 && hitMode != 5 && (hitMode < 8 || hitMode > 19)) { + Proto* proto; + protoGetProto(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 (weaponGetAttackTypeForHitMode(weapon, hitMode) == ATTACK_TYPE_THROW) { + if (critter == gDude) { + v12 = critterGetStat(critter, STAT_STRENGTH) + 2 * perkGetRank(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, 0x2000)) { + 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 = critterGetWeaponForHitMode(critter, hitMode); + + if (hitMode == HIT_MODE_LEFT_WEAPON_RELOAD || hitMode == HIT_MODE_RIGHT_WEAPON_RELOAD) { + if (weapon != NULL) { + Proto* proto; + protoGetProto(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 = weaponGetActionPointCost1(weapon); + } else { + // NOTE: Uninline. + actionPoints = weaponGetActionPointCost2(weapon); + } + + if (critter == gDude) { + if (traitIsSelected(TRAIT_FAST_SHOT)) { + if (_item_w_range(critter, hitMode) > 2) { + actionPoints--; + } + } + } + } else { + actionPoints = 3; + } + break; + } + + if (critter == gDude) { + int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); + + if (perkHasRank(gDude, PERK_BONUS_HTH_ATTACKS)) { + if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { + actionPoints -= 1; + } + } + + if (perkHasRank(gDude, 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 weaponGetMinStrengthRequired(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.minStrength; +} + +// 0x478D30 +int weaponGetCriticalFailureType(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.criticalFailureType; +} + +// 0x478D58 +int weaponGetPerk(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.perk; +} + +// 0x478D80 +int weaponGetBurstRounds(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.rounds; +} + +// 0x478DA8 +int weaponGetAnimationCode(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.animationCode; +} + +// 0x478DD0 +int weaponGetProjectilePid(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.projectilePid; +} + +// 0x478DF8 +int weaponGetAmmoTypePid(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + if (itemGetType(weapon) != ITEM_TYPE_WEAPON) { + return -1; + } + + return weapon->data.item.weapon.ammoTypePid; +} + +// 0x478E18 +char weaponGetSoundId(Object* weapon) +{ + if (weapon == NULL) { + return '\0'; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.soundCode & 0xFF; +} + +// 0x478E5C +int _item_w_called_shot(Object* critter, int hitMode) +{ + if (critter == gDude && traitIsSelected(TRAIT_FAST_SHOT)) { + return 0; + } + + // NOTE: Uninline. + int anim = critterGetAnimationForHitMode(critter, hitMode); + if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) { + return 0; + } + + // NOTE: Uninline. + Object* weapon = critterGetWeaponForHitMode(critter, hitMode); + int damageType = weaponGetDamageType(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 (itemGetType(weapon) != ITEM_TYPE_WEAPON) { + return false; + } + + // NOTE: Uninline. + int ammoCapacity = ammoGetCapacity(weapon); + if (ammoCapacity <= 0) { + return false; + } + + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(weapon); + if (ammoQuantity <= 0) { + return false; + } + + if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { + return false; + } + + if (weaponGetAmmoTypePid(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 = weaponGetAmmoTypePid(weapon); + if (ammoTypePid == -1) { + return NULL; + } + + Object* ammo; + if (objectCreateWithPid(&ammo, ammoTypePid) != 0) { + return NULL; + } + + _obj_disconnect(ammo, NULL); + + // NOTE: Uninline. + int ammoQuantity = ammoGetQuantity(weapon); + + // NOTE: Uninline. + int ammoCapacity = ammoGetCapacity(ammo); + + int remainingQuantity; + if (ammoQuantity <= ammoCapacity) { + ammoSetQuantity(ammo, ammoQuantity); + remainingQuantity = 0; + } else { + ammoSetQuantity(ammo, ammoCapacity); + remainingQuantity = ammoQuantity - ammoCapacity; + } + ammoSetQuantity(weapon, remainingQuantity); + + return ammo; +} + +// 0x47905C +int weaponGetActionPointCost1(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(weapon->pid, &proto); + + return proto->item.data.weapon.actionPointCost1; +} + +// NOTE: Inlined. +// +// 0x479084 +int weaponGetActionPointCost2(Object* weapon) +{ + if (weapon == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(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; +} + +// Returns true if weapon's damage is explosion, plasma, or emp. +// Probably checks if weapon is granade. +// +// 0x4790E8 +bool _item_w_is_grenade(Object* weapon) +{ + int damageType = weaponGetDamageType(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 = weaponGetAttackTypeForHitMode(weapon, hitMode); + int anim = weaponGetAnimationForHitMode(weapon, hitMode); + int damageType = weaponGetDamageType(NULL, weapon); + + int v1 = 0; + if (attackType == ATTACK_TYPE_RANGED) { + if (anim == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_EXPLOSION) { + // NOTE: Uninline. + v1 = _item_w_rocket_dmg_radius(weapon); + } + } else if (attackType == ATTACK_TYPE_THROW) { + // NOTE: Uninline. + if (_item_w_is_grenade(weapon)) { + // NOTE: Uninline. + v1 = _item_w_grenade_dmg_radius(weapon); + } + } + return v1; +} + +// 0x479180 +int _item_w_grenade_dmg_radius(Object* weapon) +{ + return 2; +} + +// 0x479188 +int _item_w_rocket_dmg_radius(Object* weapon) +{ + return 3; +} + +// 0x479190 +int weaponGetAmmoArmorClassModifier(Object* weapon) +{ + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(weapon); + if (ammoTypePid == -1) { + return 0; + } + + Proto* proto; + if (protoGetProto(ammoTypePid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.armorClassModifier; +} + +// 0x4791E0 +int weaponGetAmmoDamageResistanceModifier(Object* weapon) +{ + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(weapon); + if (ammoTypePid == -1) { + return 0; + } + + Proto* proto; + if (protoGetProto(ammoTypePid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.damageResistanceModifier; +} + +// 0x479230 +int weaponGetAmmoDamageMultiplier(Object* weapon) +{ + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(weapon); + if (ammoTypePid == -1) { + return 1; + } + + Proto* proto; + if (protoGetProto(ammoTypePid, &proto) == -1) { + return 1; + } + + return proto->item.data.ammo.damageMultiplier; +} + +// 0x479294 +int weaponGetAmmoDamageDivisor(Object* weapon) +{ + // NOTE: Uninline. + int ammoTypePid = weaponGetAmmoTypePid(weapon); + if (ammoTypePid == -1) { + return 1; + } + + Proto* proto; + if (protoGetProto(ammoTypePid, &proto) == -1) { + return 1; + } + + return proto->item.data.ammo.damageDivisor; +} + +// 0x4792F8 +int armorGetArmorClass(Object* armor) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.armorClass; +} + +// 0x479318 +int armorGetDamageResistance(Object* armor, int damageType) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.damageResistance[damageType]; +} + +// 0x479338 +int armorGetDamageThreshold(Object* armor, int damageType) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.damageThreshold[damageType]; +} + +// 0x479358 +int armorGetPerk(Object* armor) +{ + if (armor == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.perk; +} + +// 0x479380 +int armorGetMaleFid(Object* armor) +{ + if (armor == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.maleFid; +} + +// 0x4793A8 +int armorGetFemaleFid(Object* armor) +{ + if (armor == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(armor->pid, &proto); + + return proto->item.data.armor.femaleFid; +} + +// 0x4793D0 +int miscItemGetMaxCharges(Object* miscItem) +{ + if (miscItem == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(miscItem->pid, &proto); + + return proto->item.data.misc.charges; +} + +// 0x4793F0 +int miscItemGetCharges(Object* miscItem) +{ + if (miscItem == NULL) { + return 0; + } + + return miscItem->data.item.misc.charges; +} + +// 0x4793F8 +int miscItemSetCharges(Object* miscItem, int charges) +{ + // NOTE: Uninline. + int maxCharges = miscItemGetMaxCharges(miscItem); + + if (charges > maxCharges) { + charges = maxCharges; + } + + miscItem->data.item.misc.charges = charges; + + return 0; +} + +// NOTE: Unused. +// +// 0x479434 +int miscItemGetPowerType(Object* miscItem) +{ + if (miscItem == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(miscItem->pid, &proto); + + return proto->item.data.misc.powerType; +} + +// NOTE: Inlined. +// +// 0x479454 +int miscItemGetPowerTypePid(Object* miscItem) +{ + if (miscItem == NULL) { + return -1; + } + + Proto* proto; + protoGetProto(miscItem->pid, &proto); + + return proto->item.data.misc.powerTypePid; +} + +// 0x47947C +bool miscItemIsConsumable(Object* miscItem) +{ + if (miscItem == NULL) { + return false; + } + + Proto* proto; + protoGetProto(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 = miscItemIsOn(miscItem); + + if (isOn) { + miscItemTurnOff(miscItem); + } else { + miscItemTurnOn(miscItem); + } + } else if (pid == PROTO_ID_MOTION_SENSOR) { + // NOTE: Uninline. + if (miscItemConsumeCharge(miscItem) == 0) { + automapShow(true, true); + } else { + MessageListItem messageListItem; + // %s has no charges left. + messageListItem.num = 5; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + char text[80]; + const char* itemName = objectGetName(miscItem); + sprintf(text, messageListItem.text, itemName); + displayMonitorAddMessage(text); + } + } + } + + return 0; +} + +// 0x4795A4 +int miscItemConsumeCharge(Object* item) +{ + // NOTE: Uninline. + int charges = miscItemGetCharges(item); + if (charges <= 0) { + return -1; + } + + // NOTE: Uninline. + miscItemSetCharges(item, charges - 1); + + return 0; +} + +// 0x4795F0 +int miscItemTrickleEventProcess(Object* item, void* data) +{ + // NOTE: Uninline. + if (miscItemConsumeCharge(item) == 0) { + int delay; + if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) { + delay = 600; + } else { + delay = 3000; + } + + queueAddEvent(delay, item, NULL, EVENT_TYPE_ITEM_TRICKLE); + } else { + Object* critter = objectGetOwner(item); + if (critter == gDude) { + MessageListItem messageListItem; + // %s has no charges left. + messageListItem.num = 5; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + char text[80]; + const char* itemName = objectGetName(item); + sprintf(text, messageListItem.text, itemName); + displayMonitorAddMessage(text); + } + } + miscItemTurnOff(item); + } + + return 0; +} + +// 0x4796A8 +bool miscItemIsOn(Object* obj) +{ + if (obj == NULL) { + return false; + } + + if (!miscItemIsConsumable(obj)) { + return false; + } + + return queueHasEvent(obj, EVENT_TYPE_ITEM_TRICKLE); +} + +// Turns on geiger counter or stealth boy. +// +// 0x4796D0 +int miscItemTurnOn(Object* item) +{ + MessageListItem messageListItem; + char text[80]; + + Object* critter = objectGetOwner(item); + if (critter == NULL) { + // This item can only be used from the interface bar. + messageListItem.num = 9; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + } + + // NOTE: Uninline. + if (miscItemConsumeCharge(item) != 0) { + if (critter == gDude) { + messageListItem.num = 5; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + char* name = objectGetName(item); + sprintf(text, messageListItem.text, name); + displayMonitorAddMessage(text); + } + } + + return -1; + } + + if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) { + queueAddEvent(600, item, 0, EVENT_TYPE_ITEM_TRICKLE); + item->pid = PROTO_ID_STEALTH_BOY_II; + + if (critter != NULL) { + // NOTE: Uninline. + stealthBoyTurnOn(critter); + } + } else { + queueAddEvent(3000, item, 0, EVENT_TYPE_ITEM_TRICKLE); + item->pid = PROTO_ID_GEIGER_COUNTER_II; + } + + if (critter == gDude) { + // %s is on. + messageListItem.num = 6; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + char* name = objectGetName(item); + sprintf(text, messageListItem.text, name); + displayMonitorAddMessage(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 (messageListGetItem(&gItemsMessageList, &messageListItem)) { + int radiation = critterGetRadiation(critter); + sprintf(text, messageListItem.text, radiation); + displayMonitorAddMessage(text); + } + } + } + + return 0; +} + +// Turns off geiger counter or stealth boy. +// +// 0x479898 +int miscItemTurnOff(Object* item) +{ + Object* owner = objectGetOwner(item); + + queueRemoveEventsByType(item, EVENT_TYPE_ITEM_TRICKLE); + + if (owner != NULL && item->pid == PROTO_ID_STEALTH_BOY_II) { + stealthBoyTurnOff(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 == gDude) { + interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); + } + + if (owner == gDude) { + // %s is off. + MessageListItem messageListItem; + messageListItem.num = 7; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + const char* name = objectGetName(item); + char text[80]; + sprintf(text, messageListItem.text, name); + displayMonitorAddMessage(text); + } + } + + return 0; +} + +// 0x479954 +int _item_m_turn_off_from_queue(Object* obj, void* data) +{ + miscItemTurnOff(obj); + return 1; +} + +// NOTE: Inlined. +// +// 0x479960 +int stealthBoyTurnOn(Object* object) +{ + if ((object->flags & OBJECT_FLAG_0x20000) != 0) { + return -1; + } + + object->flags |= OBJECT_FLAG_0x20000; + + Rect rect; + objectGetRect(object, &rect); + tileWindowRefreshRect(&rect, object->elevation); + + return 0; +} + +// 0x479998 +int stealthBoyTurnOff(Object* critter, Object* item) +{ + Object* item1 = critterGetItem1(critter); + if (item1 != NULL && item1 != item && item1->pid == PROTO_ID_STEALTH_BOY_II) { + return -1; + } + + Object* item2 = critterGetItem2(critter); + if (item2 != NULL && item2 != item && item2->pid == PROTO_ID_STEALTH_BOY_II) { + return -1; + } + + if ((critter->flags & OBJECT_FLAG_0x20000) == 0) { + return -1; + } + + critter->flags &= ~OBJECT_FLAG_0x20000; + + Rect rect; + objectGetRect(critter, &rect); + tileWindowRefreshRect(&rect, critter->elevation); + + return 0; +} + +// 0x479A00 +int containerGetMaxSize(Object* container) +{ + if (container == NULL) { + return 0; + } + + Proto* proto; + protoGetProto(container->pid, &proto); + + return proto->item.data.container.maxSize; +} + +// 0x479A20 +int containerGetTotalSize(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 = itemGetSize(inventoryItem->item); + totalSize += inventory->items[index].quantity * size; + } + + return totalSize; +} + +// 0x479A74 +int ammoGetArmorClassModifier(Object* armor) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + if (protoGetProto(armor->pid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.armorClassModifier; +} + +// 0x479AA4 +int ammoGetDamageResistanceModifier(Object* armor) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + if (protoGetProto(armor->pid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.damageResistanceModifier; +} + +// 0x479AD4 +int ammoGetDamageMultiplier(Object* armor) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + if (protoGetProto(armor->pid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.damageMultiplier; +} + +// 0x479B04 +int ammoGetDamageDivisor(Object* armor) +{ + if (armor == NULL) { + return 0; + } + + Proto* proto; + if (protoGetProto(armor->pid, &proto) == -1) { + return 0; + } + + return proto->item.data.ammo.damageDivisor; +} + +// 0x479B44 +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 = internal_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 == gDude) { + if (traitIsSelected(TRAIT_CHEM_RESISTANT)) { + delay /= 2; + } + } + + if (queueAddEvent(delay, critter, drugEffectEvent, EVENT_TYPE_DRUG) == -1) { + internal_free(drugEffectEvent); + return -1; + } + + return 0; +} + +// 0x479C20 +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 = critterGetBonusStat(critter, stat); + + int before; + if (critter == gDude) { + before = critterGetStat(gDude, stat); + } + + if (v32) { + v11 = randomBetween(mods[index - 1], mods[index]) + v10; + v32 = false; + } else { + v11 = mods[index] + v10; + } + + if (stat == STAT_CURRENT_HIT_POINTS) { + v12 = critterGetBaseStatWithTraitModifier(critter, STAT_CURRENT_HIT_POINTS); + if (v11 + v12 <= 0 && critter != gDude) { + name = critterGetName(critter); + // %s succumbs to the adverse effects of chems. + text = getmsg(&gItemsMessageList, &messageListItem, 600); + sprintf(v24, text, name); + _combatKillCritterOutsideCombat(critter, v24); + } + } + + critterSetBonusStat(critter, stat, v11); + + if (critter == gDude) { + if (stat == STAT_CURRENT_HIT_POINTS) { + interfaceRenderHitPoints(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 (messageListGetItem(&gItemsMessageList, &messageListItem)) { + char* statName = statGetName(stat); + sprintf(str, messageListItem.text, after < before ? before - after : after - before, statName); + displayMonitorAddMessage(str); + statsChanged = true; + } + } + } + } + + if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) > 0) { + if (critter == gDude && !statsChanged && isImmediate) { + // Nothing happens. + messageListItem.num = 10; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } else { + if (critter == gDude) { + // You suffer a fatal heart attack from chem overdose. + messageListItem.num = 4; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + strcpy(v24, messageListItem.text); + // TODO: Why message is ignored? + } + } else { + name = critterGetName(critter); + // %s succumbs to the adverse effects of chems. + text = getmsg(&gItemsMessageList, &messageListItem, 600); + sprintf(v24, text, name); + // TODO: Why message is ignored? + } + } +} + +// 0x479EE4 +bool _drug_effect_allowed(Object* critter, int pid) +{ + int index; + DrugDescription* drugDescription; + for (index = 0; index < ADDICTION_COUNT; index++) { + drugDescription = &(gDrugDescriptions[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 = queueFindFirstEvent(critter, EVENT_TYPE_DRUG); + while (drugEffectEvent != NULL) { + if (drugEffectEvent->drugPid == pid) { + count++; + if (count >= drugDescription->field_8) { + return false; + } + } + drugEffectEvent = queueFindNextEvent(critter, EVENT_TYPE_DRUG); + } + + return true; +} + +// 0x479F60 +int _item_d_take_drug(Object* critter, Object* item) +{ + if (critterIsDead(critter)) { + return -1; + } + + if (critterGetBodyType(critter) == BODY_TYPE_ROBOTIC) { + return -1; + } + + Proto* proto; + protoGetProto(item->pid, &proto); + + if (item->pid == PROTO_ID_JET_ANTIDOTE) { + if (dudeIsAddicted(PROTO_ID_JET)) { + performWithdrawalEnd(critter, PERK_JET_ADDICTION); + + if (critter == gDude) { + // NOTE: Uninline. + dudeClearAddiction(PROTO_ID_JET); + } + + return 0; + } + } + + _wd_obj = critter; + _wd_gvar = drugGetAddictionGvarByPid(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 == gDude) { + MessageListItem messageListItem; + // That didn't seem to do that much. + char* msg = getmsg(&gItemsMessageList, &messageListItem, 50); + displayMonitorAddMessage(msg); + } + } + + if (!dudeIsAddicted(item->pid)) { + int addictionChance = proto->item.data.drug.addictionChance; + if (critter == gDude) { + if (traitIsSelected(TRAIT_CHEM_RELIANT)) { + addictionChance *= 2; + } + + if (traitIsSelected(TRAIT_CHEM_RESISTANT)) { + addictionChance /= 2; + } + + if (perkGetRank(gDude, PERK_FLOWER_CHILD)) { + addictionChance /= 2; + } + } + + if (randomBetween(1, 100) <= addictionChance) { + _insert_withdrawal(critter, 1, proto->item.data.drug.withdrawalOnset, proto->item.data.drug.withdrawalEffect, item->pid); + + if (critter == gDude) { + // NOTE: Uninline. + dudeSetAddiction(item->pid); + } + } + } + + return 1; +} + +// 0x47A178 +int _item_d_clear(Object* obj, void* data) +{ + if (objectIsPartyMember(obj)) { + return 0; + } + + drugEffectEventProcess(obj, data); + + return 1; +} + +// 0x47A198 +int drugEffectEventProcess(Object* obj, void* data) +{ + DrugEffectEvent* drugEffectEvent = data; + + if (obj == NULL) { + return 0; + } + + if ((obj->pid >> 24) != 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 drugEffectEventRead(File* stream, void** dataPtr) +{ + DrugEffectEvent* drugEffectEvent = internal_malloc(sizeof(*drugEffectEvent)); + if (drugEffectEvent == NULL) { + return -1; + } + + if (fileReadInt32List(stream, drugEffectEvent->stats, 3) == -1) goto err; + if (fileReadInt32List(stream, drugEffectEvent->modifiers, 3) == -1) goto err; + + *dataPtr = drugEffectEvent; + return 0; + +err: + + internal_free(drugEffectEvent); + return -1; +} + +// 0x47A254 +int drugEffectEventWrite(File* stream, void* data) +{ + DrugEffectEvent* drugEffectEvent = data; + + if (fileWriteInt32List(stream, drugEffectEvent->stats, 3) == -1) return -1; + if (fileWriteInt32List(stream, drugEffectEvent->modifiers, 3) == -1) return -1; + + return 0; +} + +// 0x47A290 +int _insert_withdrawal(Object* obj, int a2, int duration, int perk, int pid) +{ + WithdrawalEvent* withdrawalEvent = internal_malloc(sizeof(*withdrawalEvent)); + if (withdrawalEvent == NULL) { + return -1; + } + + withdrawalEvent->field_0 = a2; + withdrawalEvent->pid = pid; + withdrawalEvent->perk = perk; + + if (queueAddEvent(600 * duration, obj, withdrawalEvent, EVENT_TYPE_WITHDRAWAL) == -1) { + internal_free(withdrawalEvent); + return -1; + } + + return 0; +} + +// 0x47A2FC +int _item_wd_clear(Object* obj, void* data) +{ + WithdrawalEvent* withdrawalEvent = data; + + if (objectIsPartyMember(obj)) { + return 0; + } + + if (!withdrawalEvent->field_0) { + performWithdrawalEnd(obj, withdrawalEvent->perk); + } + + return 1; +} + +// 0x47A324 +int _item_wd_clear_all(Object* a1, void* data) +{ + WithdrawalEvent* withdrawalEvent = data; + + if (a1 != _wd_obj) { + return 0; + } + + if (drugGetAddictionGvarByPid(withdrawalEvent->pid) != _wd_gvar) { + return 0; + } + + if (!withdrawalEvent->field_0) { + performWithdrawalEnd(_wd_obj, withdrawalEvent->perk); + } + + _insert_withdrawal(a1, 1, _wd_onset, withdrawalEvent->perk, withdrawalEvent->pid); + + _wd_obj = NULL; + + return 1; +} + +// 0x47A384 +int withdrawalEventProcess(Object* obj, void* data) +{ + WithdrawalEvent* withdrawalEvent = data; + + if (withdrawalEvent->field_0) { + performWithdrawalStart(obj, withdrawalEvent->perk, withdrawalEvent->pid); + } else { + if (withdrawalEvent->perk == PERK_JET_ADDICTION) { + return 0; + } + + performWithdrawalEnd(obj, withdrawalEvent->perk); + + if (obj == gDude) { + // NOTE: Uninline. + dudeClearAddiction(withdrawalEvent->pid); + } + } + + if (obj == gDude) { + return 1; + } + + return 0; +} + +// read withdrawal event +// 0x47A404 +int withdrawalEventRead(File* stream, void** dataPtr) +{ + WithdrawalEvent* withdrawalEvent = internal_malloc(sizeof(*withdrawalEvent)); + if (withdrawalEvent == NULL) { + return -1; + } + + if (fileReadInt32(stream, &(withdrawalEvent->field_0)) == -1) goto err; + if (fileReadInt32(stream, &(withdrawalEvent->pid)) == -1) goto err; + if (fileReadInt32(stream, &(withdrawalEvent->perk)) == -1) goto err; + + *dataPtr = withdrawalEvent; + return 0; + +err: + + internal_free(withdrawalEvent); + return -1; +} + +// 0x47A484 +int withdrawalEventWrite(File* stream, void* data) +{ + WithdrawalEvent* withdrawalEvent = data; + + if (fileWriteInt32(stream, withdrawalEvent->field_0) == -1) return -1; + if (fileWriteInt32(stream, withdrawalEvent->pid) == -1) return -1; + if (fileWriteInt32(stream, withdrawalEvent->perk) == -1) return -1; + + return 0; +} + +// perform_withdrawal_start +// 0x47A4C4 +void performWithdrawalStart(Object* obj, int perk, int pid) +{ + if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) { + debugPrint("\nERROR: perform_withdrawal_start: Was called on non-critter!"); + return; + } + + perkAddEffect(obj, perk); + + if (obj == gDude) { + char* description = perkGetDescription(perk); + displayMonitorAddMessage(description); + } + + int duration = 10080; + if (obj == gDude) { + if (traitIsSelected(TRAIT_CHEM_RELIANT)) { + duration /= 2; + } + + if (perkGetRank(obj, PERK_FLOWER_CHILD)) { + duration /= 2; + } + } + + _insert_withdrawal(obj, 0, duration, perk, pid); +} + +// perform_withdrawal_end +// 0x47A558 +void performWithdrawalEnd(Object* obj, int perk) +{ + if ((obj->pid >> 24) != OBJ_TYPE_CRITTER) { + debugPrint("\nERROR: perform_withdrawal_end: Was called on non-critter!"); + return; + } + + perkRemoveEffect(obj, perk); + + if (obj == gDude) { + MessageListItem messageListItem; + messageListItem.num = 3; + if (messageListGetItem(&gItemsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } +} + +// 0x47A5B4 +int drugGetAddictionGvarByPid(int drugPid) +{ + for (int index = 0; index < ADDICTION_COUNT; index++) { + DrugDescription* drugDescription = &(gDrugDescriptions[index]); + if (drugDescription->drugPid == drugPid) { + return drugDescription->gvar; + } + } + + return -1; +} + +// NOTE: Inlined. +// +// 0x47A5E8 +void dudeSetAddiction(int drugPid) +{ + int gvar = drugGetAddictionGvarByPid(drugPid); + if (gvar != -1) { + gGameGlobalVars[gvar] = 1; + } + + dudeEnableState(DUDE_STATE_ADDICTED); +} + +// NOTE: Inlined. +// +// 0x47A60C +void dudeClearAddiction(int drugPid) +{ + int gvar = drugGetAddictionGvarByPid(drugPid); + if (gvar != -1) { + gGameGlobalVars[gvar] = 0; + } + + if (!dudeIsAddicted(-1)) { + dudeDisableState(DUDE_STATE_ADDICTED); + } +} + +// Returns `true` if dude has addiction to item with given pid or any addition +// if [pid] is -1. +// +// 0x47A640 +bool dudeIsAddicted(int drugPid) +{ + for (int index = 0; index < ADDICTION_COUNT; index++) { + DrugDescription* drugDescription = &(gDrugDescriptions[index]); + if (drugPid == -1 || drugPid == drugDescription->drugPid) { + if (gGameGlobalVars[drugDescription->gvar] != 0) { + return true; + } else { + return false; + } + } + } + + return false; +} + +// item_caps_total +// 0x47A6A8 +int itemGetTotalCaps(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 (itemGetType(item) == ITEM_TYPE_CONTAINER) { + // recursively collect amount of caps in container + amount += itemGetTotalCaps(item); + } + } + } + + return amount; +} + +// item_caps_adjust +// 0x47A6F8 +int itemCapsAdjust(Object* obj, int amount) +{ + int caps = itemGetTotalCaps(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) { + objectDestroy(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 (itemGetType(item) == ITEM_TYPE_CONTAINER) { + int capsInContainer = itemGetTotalCaps(item); + if (amount <= 0 || capsInContainer <= 0) { + if (amount < 0) { + if (capsInContainer < -amount) { + if (itemCapsAdjust(item, capsInContainer) == 0) { + amount += capsInContainer; + } + } else { + if (itemCapsAdjust(item, amount) == 0) { + amount = 0; + } + } + } + } else { + if (itemCapsAdjust(item, amount) == 0) { + amount = 0; + } + } + } + } + + return 0; + } + + Object* item; + if (objectCreateWithPid(&item, PROTO_ID_MONEY) == 0) { + _obj_disconnect(item, NULL); + if (itemAdd(obj, item, amount) != 0) { + objectDestroy(item, NULL); + return -1; + } + } + + return 0; +} + +// 0x47A8C8 +int itemGetMoney(Object* item) +{ + if (item->pid != PROTO_ID_MONEY) { + return -1; + } + + return item->data.item.misc.charges; +} + +// 0x47A8D8 +int itemSetMoney(Object* item, int amount) +{ + if (item->pid != PROTO_ID_MONEY) { + return -1; + } + + item->data.item.misc.charges = amount; + + return 0; +} diff --git a/src/item.h b/src/item.h new file mode 100644 index 0000000..d02183d --- /dev/null +++ b/src/item.h @@ -0,0 +1,167 @@ +#ifndef ITEM_H +#define ITEM_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" + +#include + +#define ADDICTION_COUNT (9) + +typedef enum _WeaponClass { + ATTACK_TYPE_NONE, + ATTACK_TYPE_UNARMED, // unarmed + ATTACK_TYPE_MELEE, // melee + ATTACK_TYPE_THROW, + ATTACK_TYPE_RANGED, + ATTACK_TYPE_COUNT, +} WeaponClass; + +typedef struct DrugDescription { + int drugPid; + int gvar; + int field_8; +} DrugDescription; + +extern char _aItem_1[]; + +extern const int _attack_skill[9]; +extern const int _attack_anim[9]; +extern const int _attack_subtype[9]; +extern DrugDescription gDrugDescriptions[ADDICTION_COUNT]; +extern char* _name_item; + +extern MessageList gItemsMessageList; +extern int _wd_onset; +extern Object* _wd_obj; +extern int _wd_gvar; + +int itemsInit(); +void itemsReset(); +void itemsExit(); +int _item_load_(File* stream); +int itemsLoad(File* stream); +int itemsSave(File* stream); +int itemAttemptAdd(Object* owner, Object* itemToAdd, int quantity); +int itemAdd(Object* owner, Object* itemToAdd, int quantity); +int itemRemove(Object* a1, Object* a2, int quantity); +void _item_compact(int inventoryItemIndex, Inventory* inventory); +int _item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5); +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); +bool _item_identical(Object* a1, Object* a2); +char* itemGetName(Object* obj); +char* itemGetDescription(Object* obj); +int itemGetType(Object* item); +int itemGetMaterial(Object* item); +int itemGetSize(Object* obj); +int itemGetWeight(Object* item); +int itemGetCost(Object* obj); +int objectGetCost(Object* obj); +int objectGetInventoryWeight(Object* obj); +bool _can_use_weapon(Object* item_obj); +int itemGetInventoryFid(Object* obj); +Object* critterGetWeaponForHitMode(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 weaponIsNatural(Object* obj); +int weaponGetAttackTypeForHitMode(Object* a1, int a2); +int weaponGetSkillForHitMode(Object* a1, int a2); +int _item_w_skill_level(Object* a1, int a2); +int weaponGetDamageMinMax(Object* weapon, int* minDamagePtr, int* maxDamagePtr); +int weaponGetMeleeDamage(Object* critter, int hitMode); +int weaponGetDamageType(Object* critter, Object* weapon); +int weaponIsTwoHanded(Object* weapon); +int critterGetAnimationForHitMode(Object* critter, int hitMode); +int weaponGetAnimationForHitMode(Object* weapon, int hitMode); +int ammoGetCapacity(Object* ammoOrWeapon); +int ammoGetQuantity(Object* ammoOrWeapon); +int ammoGetCaliber(Object* ammoOrWeapon); +void ammoSetQuantity(Object* ammoOrWeapon, int quantity); +int _item_w_try_reload(Object* critter, Object* weapon); +bool weaponCanBeReloadedWith(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 weaponGetMinStrengthRequired(Object* weapon); +int weaponGetCriticalFailureType(Object* weapon); +int weaponGetPerk(Object* weapon); +int weaponGetBurstRounds(Object* weapon); +int weaponGetAnimationCode(Object* weapon); +int weaponGetProjectilePid(Object* weapon); +int weaponGetAmmoTypePid(Object* weapon); +char weaponGetSoundId(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 weaponGetActionPointCost1(Object* weapon); +int weaponGetActionPointCost2(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 weaponGetAmmoArmorClassModifier(Object* weapon); +int weaponGetAmmoDamageResistanceModifier(Object* weapon); +int weaponGetAmmoDamageMultiplier(Object* weapon); +int weaponGetAmmoDamageDivisor(Object* weapon); +int armorGetArmorClass(Object* armor); +int armorGetDamageResistance(Object* armor, int damageType); +int armorGetDamageThreshold(Object* armor, int damageType); +int armorGetPerk(Object* armor); +int armorGetMaleFid(Object* armor); +int armorGetFemaleFid(Object* armor); +int miscItemGetMaxCharges(Object* miscItem); +int miscItemGetCharges(Object* miscItem); +int miscItemSetCharges(Object* miscItem, int charges); +int miscItemGetPowerType(Object* miscItem); +int miscItemGetPowerTypePid(Object* miscItem); +bool miscItemIsConsumable(Object* obj); +int _item_m_use_charged_item(Object* critter, Object* item); +int miscItemConsumeCharge(Object* miscItem); +int miscItemTrickleEventProcess(Object* item_obj, void* data); +bool miscItemIsOn(Object* obj); +int miscItemTurnOn(Object* item_obj); +int miscItemTurnOff(Object* item_obj); +int _item_m_turn_off_from_queue(Object* obj, void* data); +int stealthBoyTurnOn(Object* object); +int stealthBoyTurnOff(Object* critter, Object* item); +int containerGetMaxSize(Object* container); +int containerGetTotalSize(Object* container); +int ammoGetArmorClassModifier(Object* armor); +int ammoGetDamageResistanceModifier(Object* armor); +int ammoGetDamageMultiplier(Object* armor); +int ammoGetDamageDivisor(Object* armor); +int _insert_drug_effect(Object* critter_obj, Object* item_obj, int a3, int* stats, int* mods); +void _perform_drug_effect(Object* critter_obj, int* stats, int* mods, bool is_immediate); +bool _drug_effect_allowed(Object* critter, int pid); +int _item_d_take_drug(Object* critter_obj, Object* item_obj); +int _item_d_clear(Object* obj, void* data); +int drugEffectEventProcess(Object* obj, void* data); +int drugEffectEventRead(File* stream, void** dataPtr); +int drugEffectEventWrite(File* stream, void* data); +int _insert_withdrawal(Object* obj, int a2, int a3, int a4, int a5); +int _item_wd_clear(Object* obj, void* a2); +int _item_wd_clear_all(Object* a1, void* data); +int withdrawalEventProcess(Object* obj, void* data); +int withdrawalEventRead(File* stream, void** dataPtr); +int withdrawalEventWrite(File* stream, void* data); +void performWithdrawalStart(Object* obj, int perk, int a3); +void performWithdrawalEnd(Object* obj, int a2); +int drugGetAddictionGvarByPid(int drugPid); +void dudeSetAddiction(int drugPid); +void dudeClearAddiction(int drugPid); +bool dudeIsAddicted(int drugPid); +int itemGetTotalCaps(Object* obj); +int itemCapsAdjust(Object* obj, int amount); +int itemGetMoney(Object* obj); +int itemSetMoney(Object* obj, int a2); + +#endif /* ITEM_H */ diff --git a/src/light.c b/src/light.c new file mode 100644 index 0000000..7bc2d1a --- /dev/null +++ b/src/light.c @@ -0,0 +1,136 @@ +#include "light.h" + +#include "perk.h" +#include "tile.h" +#include "object.h" + +#include + +// 0x51923C +int gLightLevel = LIGHT_LEVEL_MAX; + +// light intensity per elevation per tile +// 0x59E994 +int gLightIntensity[ELEVATION_COUNT][HEX_GRID_SIZE]; + +// 0x47A8F0 +int lightInit() +{ + lightResetIntensity(); + return 0; +} + +// 0x47A8F8 +int lightGetLightLevel() +{ + return gLightLevel; +} + +// 0x47A908 +void lightSetLightLevel(int lightLevel, bool shouldUpdateScreen) +{ + int normalizedLightLevel = lightLevel + perkGetRank(gDude, 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; + } + + int oldLightLevel = gLightLevel; + gLightLevel = normalizedLightLevel; + + if (shouldUpdateScreen) { + if (oldLightLevel != normalizedLightLevel) { + tileWindowRefresh(); + } + } +} + +// TODO: Looks strange - it tries to clamp intensity as light level? +int _light_get_tile(int elevation, int tile) +{ + if (!elevationIsValid(elevation)) { + return 0; + } + + if (!hexGridTileIsValid(tile)) { + return 0; + } + + int result = gLightIntensity[elevation][tile]; + + if (result >= 0x10000) { + result = 0x10000; + } + + return result; +} + +// 0x47A9C4 +int lightGetIntensity(int elevation, int tile) +{ + if (!elevationIsValid(elevation)) { + return 0; + } + + if (!hexGridTileIsValid(tile)) { + return 0; + } + + return gLightIntensity[elevation][tile]; +} + +// 0x47A9EC +void lightSetIntensity(int elevation, int tile, int lightIntensity) +{ + if (!elevationIsValid(elevation)) { + return; + } + + if (!hexGridTileIsValid(tile)) { + return; + } + + gLightIntensity[elevation][tile] = lightIntensity; +} + +// 0x47AA10 +void lightIncreaseIntensity(int elevation, int tile, int lightIntensity) +{ + if (!elevationIsValid(elevation)) { + return; + } + + if (!hexGridTileIsValid(tile)) { + return; + } + + gLightIntensity[elevation][tile] += lightIntensity; +} + +// 0x47AA48 +void lightDecreaseIntensity(int elevation, int tile, int lightIntensity) +{ + if (!elevationIsValid(elevation)) { + return; + } + + if (!hexGridTileIsValid(tile)) { + return; + } + + gLightIntensity[elevation][tile] -= lightIntensity; +} + +// 0x47AA84 +void lightResetIntensity() +{ + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { + gLightIntensity[elevation][tile] = 655; + } + } +} diff --git a/src/light.h b/src/light.h new file mode 100644 index 0000000..90b75b0 --- /dev/null +++ b/src/light.h @@ -0,0 +1,29 @@ +#ifndef LIGHT_H +#define LIGHT_H + +#include "map_defs.h" + +#include + +#define LIGHT_LEVEL_MIN (65536 / 4) +#define LIGHT_LEVEL_MAX 65536 + +// 20% of max light per "Night Vision" rank +#define LIGHT_LEVEL_NIGHT_VISION_BONUS (65536 / 5) + +typedef void AdjustLightIntensityProc(int elevation, int tile, int intensity); + +extern int gLightLevel; +extern int gLightIntensity[ELEVATION_COUNT][HEX_GRID_SIZE]; + +int lightInit(); +int lightGetLightLevel(); +void lightSetLightLevel(int lightLevel, bool shouldUpdateScreen); +int _light_get_tile(int elevation, int tile); +int lightGetIntensity(int elevation, int tile); +void lightSetIntensity(int elevation, int tile, int intensity); +void lightIncreaseIntensity(int elevation, int tile, int intensity); +void lightDecreaseIntensity(int elevation, int tile, int intensity); +void lightResetIntensity(); + +#endif /* LIGHT_H */ diff --git a/src/lips.c b/src/lips.c new file mode 100644 index 0000000..c4fd516 --- /dev/null +++ b/src/lips.c @@ -0,0 +1,461 @@ +#include "lips.h" + +#include "audio.h" +#include "core.h" +#include "debug.h" +#include "game_sound.h" +#include "memory.h" +#include "sound.h" + +#include +#include + +// 0x519240 +unsigned char gLipsCurrentPhoneme = 0; + +// 0x519241 +unsigned char gLipsPreviousPhoneme = 0; + +// 0x519244 +int _head_marker_current = 0; + +// 0x519248 +bool gLipsPhonemeChanged = true; + +// 0x51924C +LipsData gLipsData = { + 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 +char _lips_subdir_name[14]; + +// 0x613CAE +char _tmp_str[50]; + +// 0x47AAC0 +char* _lips_fix_string(const char* fileName, size_t length) +{ + strncpy(_tmp_str, fileName, length); + return _tmp_str; +} + +// 0x47AAD8 +void lipsTicker() +{ + int v0; + SpeechMarker* speech_marker; + int v5; + + v0 = _head_marker_current; + + if ((gLipsData.flags & LIPS_FLAG_0x02) != 0) { + int v1 = _soundGetPosition(gLipsData.sound); + + speech_marker = &(gLipsData.markers[v0]); + while (v1 > speech_marker->position) { + gLipsCurrentPhoneme = gLipsData.phonemes[v0]; + v0++; + + if (v0 >= gLipsData.field_2C) { + v0 = 0; + gLipsCurrentPhoneme = gLipsData.phonemes[0]; + + if ((gLipsData.flags & LIPS_FLAG_0x01) == 0) { + _head_marker_current = 0; + soundStop(gLipsData.sound); + v0 = _head_marker_current; + gLipsData.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02); + } + + break; + } + + speech_marker = &(gLipsData.markers[v0]); + } + + if (v0 >= gLipsData.field_2C - 1) { + _head_marker_current = v0; + + v5 = 0; + if (gLipsData.field_2C <= 5) { + debugPrint("Error: Too few markers to stop speech!"); + } else { + v5 = 3; + } + + speech_marker = &(gLipsData.markers[v5]); + if (v1 < speech_marker->position) { + v0 = 0; + gLipsCurrentPhoneme = gLipsData.phonemes[0]; + + if ((gLipsData.flags & LIPS_FLAG_0x01) == 0) { + _head_marker_current = 0; + soundStop(gLipsData.sound); + v0 = _head_marker_current; + gLipsData.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02); + } + } + } + } + + if (gLipsPreviousPhoneme != gLipsCurrentPhoneme) { + gLipsPreviousPhoneme = gLipsCurrentPhoneme; + gLipsPhonemeChanged = true; + } + + _head_marker_current = v0; + + soundContinueAll(); +} + +// 0x47AC2C +int lipsStart() +{ + gLipsData.flags |= LIPS_FLAG_0x02; + _head_marker_current = 0; + + if (_soundSetPosition(gLipsData.sound, gLipsData.field_20) != 0) { + debugPrint("Failed set of start_offset!\n"); + } + + int v2 = _head_marker_current; + while (1) { + _head_marker_current = v2; + + SpeechMarker* speechEntry = &(gLipsData.markers[v2]); + if (gLipsData.field_20 <= speechEntry->position) { + break; + } + + v2++; + + gLipsCurrentPhoneme = gLipsData.phonemes[v2]; + } + + int speechVolume = speechGetVolume(); + soundSetVolume(gLipsData.sound, (int)(speechVolume * 0.69)); + + _speechStartTime = _get_time(); + + if (soundPlay(gLipsData.sound) != 0) { + debugPrint("Failed play!\n"); + _head_marker_current = 0; + + soundStop(gLipsData.sound); + gLipsData.flags |= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02); + } + + return 0; +} + +// 0x47AD98 +int lipsReadV1(LipsData* a1, File* stream) +{ + int field_C; + int field_14; + int field_18; + int field_30; + + if (fileReadInt32(stream, &(a1->version)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_4)) == -1) return -1; + if (fileReadInt32(stream, &(a1->flags)) == -1) return -1; + if (fileReadInt32(stream, &(field_C)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_10)) == -1) return -1; + if (fileReadInt32(stream, &(field_14)) == -1) return -1; + if (fileReadInt32(stream, &(field_18)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_1C)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_20)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_24)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_28)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_2C)) == -1) return -1; + if (fileReadInt32(stream, &(field_30)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_34)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_38)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_3C)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_40)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_44)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_48)) == -1) return -1; + if (fileReadInt32(stream, &(a1->field_4C)) == -1) return -1; + if (fileReadFixedLengthString(stream, a1->field_50, 8) == -1) return -1; + if (fileReadFixedLengthString(stream, a1->field_58, 4) == -1) return -1; + if (fileReadFixedLengthString(stream, a1->field_5C, 4) == -1) return -1; + if (fileReadFixedLengthString(stream, a1->field_60, 4) == -1) return -1; + if (fileReadFixedLengthString(stream, a1->field_64, 260) == -1) return -1; + + // TODO: What for? + a1->sound = (Sound*)field_C; + a1->field_14 = (void*)field_14; + a1->phonemes = (unsigned char*)field_18; + a1->markers = (SpeechMarker*)field_30; + + return 0; +} + +// lips_load_file +// 0x47AFAC +int lipsLoad(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(gLipsData.field_50, v60); + + strcat(path, _lips_fix_string(gLipsData.field_50, sizeof(gLipsData.field_50))); + strcat(path, "."); + strcat(path, gLipsData.field_60); + + lipsFree(); + + // FIXME: stream is not closed if any error is encountered during reading. + File* stream = fileOpen(path, "rb"); + if (stream != NULL) { + if (fileReadInt32(stream, &(gLipsData.version)) == -1) { + return -1; + } + + if (gLipsData.version == 1) { + debugPrint("\nLoading old save-file version (1)"); + + if (fileSeek(stream, 0, SEEK_SET) != 0) { + return -1; + } + + if (lipsReadV1(&gLipsData, stream) != 0) { + return -1; + } + } else if (gLipsData.version == 2) { + debugPrint("\nLoading current save-file version (2)"); + + if (fileReadInt32(stream, &(gLipsData.field_4)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.flags)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.field_10)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.field_1C)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.field_24)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.field_28)) == -1) return -1; + if (fileReadInt32(stream, &(gLipsData.field_2C)) == -1) return -1; + if (fileReadFixedLengthString(stream, gLipsData.field_50, 8) == -1) return -1; + if (fileReadFixedLengthString(stream, gLipsData.field_58, 4) == -1) return -1; + } else { + debugPrint("\nError: Lips file WRONG version: %s!", path); + } + } + + gLipsData.phonemes = internal_malloc(gLipsData.field_24); + if (gLipsData.phonemes == NULL) { + debugPrint("Out of memory in lips_load_file.'\n"); + return -1; + } + + if (stream != NULL) { + for (i = 0; i < gLipsData.field_24; i++) { + if (fileReadUInt8(stream, &(gLipsData.phonemes[i])) == -1) { + debugPrint("lips_load_file: Error reading phoneme type.\n"); + return -1; + } + } + + for (i = 0; i < gLipsData.field_24; i++) { + unsigned char phoneme = gLipsData.phonemes[i]; + if (phoneme >= PHONEME_COUNT) { + debugPrint("\nLoad error: Speech phoneme %d is invalid (%d)!", i, phoneme); + } + } + } + + gLipsData.markers = internal_malloc(sizeof(*speech_marker) * gLipsData.field_2C); + if (gLipsData.markers == NULL) { + debugPrint("Out of memory in lips_load_file.'\n"); + return -1; + } + + if (stream != NULL) { + for (i = 0; i < gLipsData.field_2C; i++) { + speech_marker = &(gLipsData.markers[i]); + + if (fileReadInt32(stream, &(speech_marker->marker)) == -1) break; + if (fileReadInt32(stream, &(speech_marker->position)) == -1) break; + } + + if (i != gLipsData.field_2C) { + debugPrint("lips_load_file: Error reading marker type."); + return -1; + } + + speech_marker = &(gLipsData.markers[0]); + + if (speech_marker->marker != 1 && speech_marker->marker != 0) { + debugPrint("\nLoad error: Speech marker 0 is invalid (%d)!", speech_marker->marker); + } + + if (speech_marker->position != 0) { + debugPrint("Load error: Speech marker 0 has invalid position(%d)!", speech_marker->position); + } + + for (i = 1; i < gLipsData.field_2C; i++) { + speech_marker = &(gLipsData.markers[i]); + prev_speech_marker = &(gLipsData.markers[i - 1]); + + if (speech_marker->marker != 1 && speech_marker->marker != 0) { + debugPrint("\nLoad error: Speech marker %d is invalid (%d)!", i, speech_marker->marker); + } + + if (speech_marker->position < prev_speech_marker->position) { + debugPrint("Load error: Speech marker %d has invalid position(%d)!", i, speech_marker->position); + } + } + } + + if (stream != NULL) { + fileClose(stream); + } + + gLipsData.field_38 = 0; + gLipsData.field_34 = 0; + gLipsData.field_48 = 0; + gLipsData.field_20 = 0; + gLipsData.field_3C = 50; + gLipsData.field_40 = 100; + + if (gLipsData.version == 1) { + gLipsData.field_4 = 22528; + } + + strcpy(gLipsData.field_58, "ACM"); + strcpy(gLipsData.field_5C, "TXT"); + strcpy(gLipsData.field_60, "LIP"); + + _lips_make_speech(); + + _head_marker_current = 0; + gLipsCurrentPhoneme = gLipsData.phonemes[0]; + + return 0; +} + +// lips_make_speech +// 0x47B5D0 +int _lips_make_speech() +{ + if (gLipsData.field_14 != NULL) { + internal_free(gLipsData.field_14); + gLipsData.field_14 = NULL; + } + + char path[MAX_PATH]; + char* v1 = _lips_fix_string(gLipsData.field_50, sizeof(gLipsData.field_50)); + sprintf(path, "%s%s\\%s.%s", "SOUND\\SPEECH\\", _lips_subdir_name, v1, "ACM"); + + if (gLipsData.sound != NULL) { + soundDelete(gLipsData.sound); + gLipsData.sound = NULL; + } + + gLipsData.sound = soundAllocate(1, 8); + if (gLipsData.sound == NULL) { + debugPrint("\nsoundAllocate falied in lips_make_speech!"); + return -1; + } + + if (soundSetFileIO(gLipsData.sound, audioOpen, audioClose, audioRead, NULL, audioSeek, NULL, audioGetSize)) { + debugPrint("Ack!"); + debugPrint("Error!"); + } + + if (soundLoad(gLipsData.sound, path)) { + soundDelete(gLipsData.sound); + gLipsData.sound = NULL; + + debugPrint("lips_make_speech: soundLoad failed with path "); + debugPrint("%s -- file probably doesn't exist.\n", path); + return -1; + } + + gLipsData.field_34 = 8 * (gLipsData.field_1C / gLipsData.field_2C); + + return 0; +} + +// 0x47B730 +int lipsFree() +{ + if (gLipsData.field_14 != NULL) { + internal_free(gLipsData.field_14); + gLipsData.field_14 = NULL; + } + + if (gLipsData.sound != NULL) { + _head_marker_current = 0; + + soundStop(gLipsData.sound); + + gLipsData.flags &= ~0x03; + + soundDelete(gLipsData.sound); + + gLipsData.sound = NULL; + } + + if (gLipsData.phonemes != NULL) { + internal_free(gLipsData.phonemes); + gLipsData.phonemes = NULL; + } + + if (gLipsData.markers != NULL) { + internal_free(gLipsData.markers); + gLipsData.markers = NULL; + } + + return 0; +} diff --git a/src/lips.h b/src/lips.h new file mode 100644 index 0000000..f0e5d4c --- /dev/null +++ b/src/lips.h @@ -0,0 +1,68 @@ +#ifndef LIPS_H +#define LIPS_H + +#include "db.h" +#include "sound.h" + +#include +#include + +#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 field_24; + int field_28; + int field_2C; + 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 gLipsCurrentPhoneme; +extern unsigned char gLipsPreviousPhoneme; +extern int _head_marker_current; +extern bool gLipsPhonemeChanged; +extern LipsData gLipsData; +extern int _speechStartTime; + +extern char _lips_subdir_name[14]; +extern char _tmp_str[50]; + +char* _lips_fix_string(const char* fileName, size_t length); +void lipsTicker(); +int lipsStart(); +int lipsReadV1(LipsData* a1, File* stream); +int lipsLoad(const char* audioFileName, const char* headFileName); +int _lips_make_speech(); +int lipsFree(); + +#endif /* LIPS_H */ diff --git a/src/loadsave.c b/src/loadsave.c new file mode 100644 index 0000000..edf34a8 --- /dev/null +++ b/src/loadsave.c @@ -0,0 +1,2621 @@ +#include "loadsave.h" + +#include "automap.h" +#include "character_editor.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "dbox.h" +#include "debug.h" +#include "display_monitor.h" +#include "draw.h" +#include "file_utils.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "options.h" +#include "perk.h" +#include "pipboy.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "text_font.h" +#include "tile.h" +#include "trait.h" +#include "version.h" +#include "window_manager.h" +#include "word_wrap.h" +#include "world_map.h" + +#include +#include +#include +#include + +#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)) + +// 0x47B7C0 +const int gLoadSaveFrmIds[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 +int _slot_cursor = 0; + +// 0x5193BC +bool _quick_done = false; + +// 0x5193C0 +bool gLoadSaveWindowIsoWasEnabled = false; + +// 0x5193C4 +int _map_backup_count = -1; + +// 0x5193C8 +int _automap_db_flag = 0; + +// 0x5193CC +char* _patches = NULL; + +// 0x5193D0 +char _emgpath[] = "\\FALLOUT\\CD\\DATA\\SAVEGAME"; + +// 0x5193EC +SaveGameHandler* _master_save_list[LOAD_SAVE_HANDLER_COUNT] = { + _DummyFunc, + _SaveObjDudeCid, + scriptsSaveGameGlobalVars, + _GameMap2Slot, + scriptsSaveGameGlobalVars, + _obj_save_dude, + critterSave, + killsSave, + skillsSave, + randomSave, + perksSave, + combatSave, + aiSave, + statsSave, + itemsSave, + traitsSave, + automapSave, + preferencesSave, + _editor_save, + worldmapSave, + pipboySave, + gameMoviesSave, + skillsUsageSave, + partyMembersSave, + queueSave, + interfaceSave, + _DummyFunc, +}; + +// 0x519458 +LoadGameHandler* _master_load_list[LOAD_SAVE_HANDLER_COUNT] = { + _PrepLoad, + _LoadObjDudeCid, + scriptsLoadGameGlobalVars, + _SlotMap2Game, + scriptsSkipGameGlobalVars, + _obj_load_dude, + critterLoad, + killsLoad, + skillsLoad, + randomLoad, + perksLoad, + combatLoad, + aiLoad, + statsLoad, + itemsLoad, + traitsLoad, + automapLoad, + preferencesLoad, + _editor_load, + worldmapLoad, + pipboyLoad, + gameMoviesLoad, + skillsUsageLoad, + partyMembersLoad, + queueLoad, + interfaceLoad, + _EndLoad, +}; + +// 0x5194C4 +int _loadingGame = 0; + +// 0x613CE0 +Size gLoadSaveFrmSizes[LOAD_SAVE_FRM_COUNT]; + +// lsgame.msg +// +// 0x613D28 +MessageList gLoadSaveMessageList; + +// 0x613D30 +STRUCT_613D30 _LSData[10]; + +// 0x614280 +int _LSstatus[10]; + +// 0x6142A8 +unsigned char* _thumbnail_image; + +// 0x6142AC +unsigned char* _snapshotBuf; + +// 0x6142B0 +MessageListItem gLoadSaveMessageListItem; + +// 0x6142C0 +int _dbleclkcntr; + +// 0x6142C4 +int gLoadSaveWindow; + +// 0x6142C8 +unsigned char* gLoadSaveFrmData[LOAD_SAVE_FRM_COUNT]; + +// 0x6142EC +unsigned char* _snapshot; + +// 0x6142F0 +char _str2[MAX_PATH]; + +// 0x6143F4 +char _str0[MAX_PATH]; + +// 0x6144F8 +char _str1[MAX_PATH]; + +// 0x6145FC +char _str[MAX_PATH]; + +// 0x614700 +unsigned char* gLoadSaveWindowBuffer; + +// 0x614704 +char _gmpath[MAX_PATH]; + +// 0x614808 +File* _flptr; + +// 0x61480C +int _ls_error_code; + +// 0x614810 +int gLoadSaveWindowOldFont; + +// 0x614814 +CacheEntry* gLoadSaveFrmHandles[LOAD_SAVE_FRM_COUNT]; + +// 0x47B7E4 +void _InitLoadSave() +{ + _quick_done = false; + _slot_cursor = 0; + + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &_patches)) { + debugPrint("\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 lsgSaveGame(int mode) +{ + MessageListItem messageListItem; + + _ls_error_code = 0; + + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &_patches)) { + debugPrint("\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 = fileOpen(_gmpath, "rb"); + if (_flptr != NULL) { + lsgLoadHeaderInSlot(_slot_cursor); + fileClose(_flptr); + } + + _snapshotBuf = NULL; + int v6 = _QuickSnapShot(); + if (v6 == 1) { + int v7 = lsgPerformSaveGame(); + if (v7 != -1) { + v6 = v7; + } + } + + if (_snapshotBuf != NULL) { + internal_free(_snapshot); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (v6 != -1) { + return 1; + } + + if (!messageListInit(&gLoadSaveMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "LSGAME.MSG"); + if (!messageListLoad(&gLoadSaveMessageList, path)) { + return -1; + } + + soundPlayFile("iisxxxx1"); + + // Error saving game! + strcpy(_str0, getmsg(&gLoadSaveMessageList, &messageListItem, 132)); + // Unable to save game. + strcpy(_str1, getmsg(&gLoadSaveMessageList, &messageListItem, 133)); + + const char* body[] = { + _str1, + }; + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + + messageListFree(&gLoadSaveMessageList); + + 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 (lsgWindowInit(windowType) == -1) { + debugPrint("\nLOADSAVE: ** Error loading save game screen data! **\n"); + return -1; + } + + if (_GetSlotList() == -1) { + windowRefresh(gLoadSaveWindow); + + soundPlayFile("iisxxxx1"); + + // Error loading save game list! + strcpy(_str0, getmsg(&gLoadSaveMessageList, &messageListItem, 106)); + // Save game directory: + strcpy(_str1, getmsg(&gLoadSaveMessageList, &messageListItem, 107)); + + sprintf(_str2, "\"%s\\\"", "SAVEGAME"); + + // TODO: Check. + strcpy(_str2, getmsg(&gLoadSaveMessageList, &messageListItem, 108)); + + const char* body[] = { + _str1, + _str2, + }; + showDialogBox(_str0, body, 2, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + + lsgWindowFree(0); + + return -1; + } + + unsigned char* src; + int v33 = _LSstatus[_slot_cursor]; + if (v33 != 0 && v33 != 2 && v33 != 3) { + _LoadTumbSlot(_slot_cursor); + src = _thumbnail_image; + } else { + src = _snapshotBuf; + } + + blitBufferToBuffer(src, 223, 132, LS_PREVIEW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); + _ShowSlotList(0); + _DrawInfoBox(_slot_cursor); + windowRefresh(gLoadSaveWindow); + + _dbleclkcntr = 24; + + int rc = -1; + int v103 = -1; + while (rc == -1) { + int tick = _get_time(); + int keyCode = _get_input(); + int v37 = 0; + int v102 = 0; + + 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; + } + v37 = 1; + v103 = -1; + break; + case KEY_ARROW_DOWN: + _slot_cursor += 1; + if (_slot_cursor > 9) { + _slot_cursor = 9; + } + v37 = 1; + v103 = -1; + break; + case KEY_HOME: + v37 = 1; + v103 = -1; + _slot_cursor = 0; + break; + case KEY_END: + v103 = -1; + v37 = 1; + _slot_cursor = 9; + break; + case 506: + v102 = 1; + break; + case 504: + v102 = 2; + break; + case 502: + if (1) { + int x; + int y; + mouseGetPosition(&x, &y); + + _slot_cursor = (y - 79) / (3 * fontGetLineHeight() + 4); + if (_slot_cursor < 0) { + _slot_cursor = 0; + } + if (_slot_cursor > 9) { + _slot_cursor = 9; + } + + v37 = 1; + + if (_slot_cursor == v103) { + keyCode = 500; + soundPlayFile("ib1p1xx1"); + } + + v103 = _slot_cursor; + v102 = 0; + } + break; + case KEY_CTRL_Q: + case KEY_CTRL_X: + case KEY_F10: + showQuitConfirmationDialog(); + + if (_game_user_wants_to_quit != 0) { + rc = 0; + } + break; + case KEY_PLUS: + case KEY_EQUAL: + brightnessIncrease(); + break; + case KEY_MINUS: + case KEY_UNDERSCORE: + brightnessDecrease(); + break; + case KEY_RETURN: + keyCode = 500; + break; + } + } + + if (keyCode == 500) { + rc = _LSstatus[_slot_cursor]; + if (rc == 1) { + // Save game already exists, overwrite? + const char* title = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 131); + if (showDialogBox(title, NULL, 0, 169, 131, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_YES_NO) == 0) { + rc = -1; + } + } else { + rc = 1; + } + + v37 = 1; + v102 = 0; + } + + if (v102) { + // TODO: Incomplete. + } else { + if (v37) { + unsigned char* src; + int v49 = _LSstatus[_slot_cursor]; + if (v49 != 0 && v49 != 2 && v49 != 3) { + _LoadTumbSlot(_slot_cursor); + src = _thumbnail_image; + } else { + src = _snapshotBuf; + } + + blitBufferToBuffer(src, 223, 132, LS_PREVIEW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); + + _DrawInfoBox(_slot_cursor); + + _ShowSlotList(0); + } + + windowRefresh(gLoadSaveWindow); + + _dbleclkcntr -= 1; + if (_dbleclkcntr == 0) { + _dbleclkcntr = 24; + v103 = -1; + } + + while (getTicksSince(tick) < 1000 / 24) { + } + } + + if (rc == 1) { + int v50 = _GetComment(_slot_cursor); + if (v50 == -1) { + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + soundPlayFile("iisxxxx1"); + debugPrint("\nLOADSAVE: ** Error getting save file comment **\n"); + + // Error saving game! + strcpy(_str0, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 132)); + // Unable to save game. + strcpy(_str1, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 133)); + + rc = -1; + + const char* body[1] = { + _str1, + }; + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + } else if (v50 == 0) { + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + rc = -1; + } else if (v50 == 1) { + if (lsgPerformSaveGame() == -1) { + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + soundPlayFile("iisxxxx1"); + + // Error saving game! + strcpy(_str0, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 132)); + // Unable to save game. + strcpy(_str1, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 133)); + + rc = -1; + + const char* body[1] = { + _str1, + }; + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + + if (_GetSlotList() == -1) { + windowRefresh(gLoadSaveWindow); + soundPlayFile("iisxxxx1"); + + // Error loading save agme list! + strcpy(_str0, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 106)); + // Save game directory: + strcpy(_str1, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 107)); + + sprintf(_str2, "\"%s\\\"", "SAVEGAME"); + + char text[260]; + // Doesn't exist or is corrupted. + strcpy(text, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 107)); + + const char* body[2] = { + _str1, + _str2, + }; + showDialogBox(_str0, body, 2, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE); + + lsgWindowFree(0); + + return -1; + } + + unsigned char* src; + int state = _LSstatus[_slot_cursor]; + if (state == SLOT_STATE_EMPTY || state == SLOT_STATE_ERROR || state == SLOT_STATE_UNSUPPORTED_VERSION) { + src = _snapshotBuf; + } else { + _LoadTumbSlot(_slot_cursor); + src = _thumbnail_image; + } + + blitBufferToBuffer(src, 223, 132, LS_PREVIEW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); + _ShowSlotList(0); + _DrawInfoBox(_slot_cursor); + windowRefresh(gLoadSaveWindow); + _dbleclkcntr = 24; + } + } + } + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + lsgWindowFree(0); + + tileWindowRefresh(); + + if (mode == LOAD_SAVE_MODE_QUICK) { + if (rc == 1) { + _quick_done = true; + } + } + + return rc; +} + +// 0x47C5B4 +int _QuickSnapShot() +{ + _snapshot = internal_malloc(LS_PREVIEW_SIZE); + if (_snapshot == NULL) { + return -1; + } + + bool gameMouseWasVisible = gameMouseObjectsIsVisible(); + if (gameMouseWasVisible) { + gameMouseObjectsHide(); + } + + mouseHideCursor(); + tileWindowRefresh(); + mouseShowCursor(); + + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + unsigned char* windowBuffer = windowGetBuffer(gIsoWindow); + blitBufferToBufferStretch(windowBuffer, 640, 380, 640, _snapshot, LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH); + + _snapshotBuf = _snapshot; + + return 1; +} + +// LoadGame +// 0x47C640 +int lsgLoadGame(int mode) +{ + MessageListItem messageListItem; + const char* messageListItemText; + + const char* body[] = { + _str1, + _str2, + }; + + _ls_error_code = 0; + + if (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &_patches)) { + debugPrint("\nLOADSAVE: Error reading patches config variable! Using default.\n"); + _patches = _emgpath; + } + + if (mode == LOAD_SAVE_MODE_QUICK && _quick_done) { + int window = windowCreate(0, 0, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, 256, 18); + if (window != -1) { + unsigned char* windowBuffer = windowGetBuffer(window); + bufferFill(windowBuffer, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, LS_WINDOW_WIDTH, _colorTable[0]); + windowRefresh(window); + } + + if (lsgLoadGameInSlot(_slot_cursor) != -1) { + if (window != -1) { + windowDestroy(window); + } + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return 1; + } + + if (!messageListInit(&gLoadSaveMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s\\%s", asc_5186C8, "LSGAME.MSG"); + if (!messageListLoad(&gLoadSaveMessageList, path)) { + return -1; + } + + if (window != -1) { + windowDestroy(window); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + soundPlayFile("iisxxxx1"); + + messageListItemText = getmsg(&gLoadSaveMessageList, &messageListItem, 134); + strcpy(_str0, messageListItemText); + + messageListItemText = getmsg(&gLoadSaveMessageList, &messageListItem, 135); + strcpy(_str1, messageListItemText); + + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + + messageListFree(&gLoadSaveMessageList); + + _map_new_map(); + + _game_user_wants_to_quit = 2; + + return -1; + } else { + _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 (lsgWindowInit(windowType) == -1) { + debugPrint("\nLOADSAVE: ** Error loading save game screen data! **\n"); + return -1; + } + + if (_GetSlotList() == -1) { + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + windowRefresh(gLoadSaveWindow); + soundPlayFile("iisxxxx1"); + + const char* text1 = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 106); + strcpy(_str0, text1); + + const char* text2 = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 107); + strcpy(_str1, text1); + + sprintf(_str2, "\"%s\\\"", "SAVEGAME"); + + showDialogBox(_str0, body, 2, 169, 116, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + + return -1; + } + + int v36 = _LSstatus[_slot_cursor]; + if (v36 != 0 && v36 != 2 && v36 != 3) { + _LoadTumbSlot(_slot_cursor); + blitBufferToBuffer(_thumbnail_image, 223, 132, LS_PREVIEW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); + } else { + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_PREVIEW_COVER], + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].height, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 39 + 340, + LS_WINDOW_WIDTH); + } + + _ShowSlotList(2); + _DrawInfoBox(_slot_cursor); + windowRefresh(gLoadSaveWindow); + _dbleclkcntr = 24; + + int rc = -1; + while (rc == -1) { + while (rc == -1) { + int v37 = _get_time(); + int keyCode = _get_input(); + int v39 = 0; + int v107 = 0; + int v108 = -1; + + 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; + } + v39 = 1; + v108 = -1; + break; + case KEY_ARROW_DOWN: + if (++_slot_cursor > 9) { + _slot_cursor = 9; + } + v39 = 1; + v108 = -1; + break; + case KEY_HOME: + _slot_cursor = 0; + v108 = -1; + v39 = 1; + break; + case KEY_END: + v39 = 1; + v108 = -1; + _slot_cursor = 9; + break; + case 506: + v107 = 1; + break; + case 504: + v107 = 2; + break; + case 502: + do { + int v103; + int v102; + mouseGetPosition(&v103, &v102); + int v41 = (v102 - 79) / (3 * fontGetLineHeight() + 4); + if (v41 < 0) { + v41 = 0; + } else if (v41 > 9) { + v41 = 9; + } + + _slot_cursor = v41; + if (v41 == v108) { + soundPlayFile("ib1p1xx1"); + } + + v39 = 1; + v107 = 0; + v108 = _slot_cursor; + } while (0); + break; + case KEY_MINUS: + case KEY_UNDERSCORE: + brightnessDecrease(); + break; + case KEY_EQUAL: + case KEY_PLUS: + brightnessIncrease(); + break; + case KEY_RETURN: + keyCode = 500; + break; + case KEY_CTRL_Q: + case KEY_CTRL_X: + case KEY_F10: + showQuitConfirmationDialog(); + if (_game_user_wants_to_quit != 0) { + rc = 0; + } + break; + } + } + + if (keyCode == 500) { + if (_LSstatus[_slot_cursor]) { + rc = 1; + } else { + rc = -1; + } + + v39 = 1; + v107 = 0; + } + + if (v107) { + unsigned int v42 = 4; + int v106 = 0; + int v109 = 0; + do { + int v45 = _get_time(); + int v44 = v109 + 1; + + if (v106 == 0 && v44 == 1 || v106 == 1 && v109 > 14.4) { + v106 = 1; + + if (v109 > 14.4) { + v42 += 1; + if (v42 > 24) { + v42 = 24; + } + } + + if (v107 == 1) { + _slot_cursor -= 1; + if (_slot_cursor < 0) { + _slot_cursor = 0; + } + } else { + _slot_cursor += 1; + if (_slot_cursor > 9) { + _slot_cursor = 9; + } + } + + int v46 = _LSstatus[_slot_cursor]; + if (v46 != 0 && v46 != 2 && v46 != 3) { + _LoadTumbSlot(_slot_cursor); + + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].height, + LS_WINDOW_WIDTH, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 39 + 340, + LS_WINDOW_WIDTH); + + blitBufferToBuffer(_thumbnail_image, + 223, + 132, + LS_PREVIEW_WIDTH, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, + LS_WINDOW_WIDTH); + } else { + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_PREVIEW_COVER], + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].height, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 39 + 340, + LS_WINDOW_WIDTH); + } + + _ShowSlotList(2); + _DrawInfoBox(_slot_cursor); + windowRefresh(gLoadSaveWindow); + } + + if (v109 > 14.4) { + while (getTicksSince(v45) < 1000 / v42) { } + } else { + while (getTicksSince(v45) < 1000 / 24) { } + } + + keyCode = _get_input(); + } while (keyCode != 505 && keyCode != 503); + } else { + if (v39 != 0) { + int v48 = _LSstatus[_slot_cursor]; + if (v48 != 0 && v48 != 2 && v48 != 3) { + _LoadTumbSlot(_slot_cursor); + + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].height, + LS_WINDOW_WIDTH, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 39 + 340, + LS_WINDOW_WIDTH); + + blitBufferToBuffer(_thumbnail_image, + 223, + 132, + LS_PREVIEW_WIDTH, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 58 + 366, + LS_WINDOW_WIDTH); + } else { + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_PREVIEW_COVER], + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].height, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_PREVIEW_COVER].width, + gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 39 + 340, + LS_WINDOW_WIDTH); + } + _DrawInfoBox(_slot_cursor); + _ShowSlotList(2); + } + + windowRefresh(gLoadSaveWindow); + + _dbleclkcntr -= 1; + if (_dbleclkcntr == 0) { + _dbleclkcntr = 24; + v108 = -1; + } + + while (getTicksSince(v37) < 1000 / 24) { } + } + } + + if (rc == 1) { + int v50 = _LSstatus[_slot_cursor]; + if (v50 == 3) { + const char* text; + + soundPlayFile("iisxxxx1"); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 134); + strcpy(_str0, text); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 136); + strcpy(_str1, text); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 135); + strcpy(_str2, text); + + showDialogBox(_str0, body, 2, 169, 116, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + + rc = -1; + } else if (v50 == 2) { + const char* text; + + soundPlayFile("iisxxxx1"); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 134); + strcpy(_str0, text); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 136); + strcpy(_str1, text); + + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + + rc = -1; + } else { + if (lsgLoadGameInSlot(_slot_cursor) == -1) { + const char* text; + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + soundPlayFile("iisxxxx1"); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 134); + strcpy(_str0, text); + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 135); + strcpy(_str1, text); + + showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + + _map_new_map(); + + _game_user_wants_to_quit = 2; + + rc = -1; + } + } + } + } + + lsgWindowFree(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 +int lsgWindowInit(int windowType) +{ + gLoadSaveWindowOldFont = fontGetCurrent(); + fontSetCurrent(103); + + gLoadSaveWindowIsoWasEnabled = false; + if (!messageListInit(&gLoadSaveMessageList)) { + return -1; + } + + sprintf(_str, "%s%s", asc_5186C8, LSGAME_MSG_NAME); + if (!messageListLoad(&gLoadSaveMessageList, _str)) { + return -1; + } + + _snapshot = internal_malloc(61632); + if (_snapshot == NULL) { + messageListFree(&gLoadSaveMessageList); + fontSetCurrent(gLoadSaveWindowOldFont); + return -1; + } + + _thumbnail_image = _snapshot; + _snapshotBuf = _snapshot + LS_PREVIEW_SIZE; + + if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { + gLoadSaveWindowIsoWasEnabled = isoDisable(); + } + + colorCycleDisable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (windowType == LOAD_SAVE_WINDOW_TYPE_SAVE_GAME || windowType == LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT) { + bool gameMouseWasVisible = gameMouseObjectsIsVisible(); + if (gameMouseWasVisible) { + gameMouseObjectsHide(); + } + + mouseHideCursor(); + tileWindowRefresh(); + mouseShowCursor(); + + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + unsigned char* windowBuf = windowGetBuffer(gIsoWindow); + blitBufferToBufferStretch(windowBuf, 640, 380, 640, _snapshotBuf, LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH); + } + + for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) { + int fid = buildFid(6, gLoadSaveFrmIds[index], 0, 0, 0); + gLoadSaveFrmData[index] = artLockFrameDataReturningSize(fid, + &(gLoadSaveFrmHandles[index]), + &(gLoadSaveFrmSizes[index].width), + &(gLoadSaveFrmSizes[index].height)); + + if (gLoadSaveFrmData[index] == NULL) { + while (--index >= 0) { + artUnlock(gLoadSaveFrmHandles[index]); + } + internal_free(_snapshot); + messageListFree(&gLoadSaveMessageList); + fontSetCurrent(gLoadSaveWindowOldFont); + + if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { + if (gLoadSaveWindowIsoWasEnabled) { + isoEnable(); + } + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + } + + gLoadSaveWindow = windowCreate(0, 0, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, 256, 20); + if (gLoadSaveWindow == -1) { + // FIXME: Leaking frms. + internal_free(_snapshot); + messageListFree(&gLoadSaveMessageList); + fontSetCurrent(gLoadSaveWindowOldFont); + + if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { + if (gLoadSaveWindowIsoWasEnabled) { + isoEnable(); + } + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return -1; + } + + gLoadSaveWindowBuffer = windowGetBuffer(gLoadSaveWindow); + memcpy(gLoadSaveWindowBuffer, gLoadSaveFrmData[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(&gLoadSaveMessageList, &gLoadSaveMessageListItem, messageId); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 27 + 48, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, _colorTable[18979]); + + // DONE + msg = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 104); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 348 + 410, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, _colorTable[18979]); + + // CANCEL + msg = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 105); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 348 + 515, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, _colorTable[18979]); + + int btn; + + btn = buttonCreate(gLoadSaveWindow, + 391, + 349, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, + -1, + -1, + -1, + 500, + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate(gLoadSaveWindow, + 495, + 349, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, + -1, + -1, + -1, + 501, + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate(gLoadSaveWindow, + 35, + 58, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_ARROW_UP_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height, + -1, + 505, + 506, + 505, + gLoadSaveFrmData[LOAD_SAVE_FRM_ARROW_UP_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_ARROW_UP_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + btn = buttonCreate(gLoadSaveWindow, + 35, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height + 58, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].height, + -1, + 503, + 504, + 503, + gLoadSaveFrmData[LOAD_SAVE_FRM_ARROW_DOWN_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + buttonCreate(gLoadSaveWindow, 55, 87, 230, 353, -1, -1, -1, 502, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); + fontSetCurrent(101); + + return 0; +} + +// 0x47D824 +int lsgWindowFree(int windowType) +{ + windowDestroy(gLoadSaveWindow); + fontSetCurrent(gLoadSaveWindowOldFont); + messageListFree(&gLoadSaveMessageList); + + for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) { + artUnlock(gLoadSaveFrmHandles[index]); + } + + internal_free(_snapshot); + + if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { + if (gLoadSaveWindowIsoWasEnabled) { + isoEnable(); + } + } + + colorCycleEnable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + return 0; +} + +// 0x47D88C +int lsgPerformSaveGame() +{ + _ls_error_code = 0; + _map_backup_count = -1; + gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); + + backgroundSoundPause(); + + 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) { + debugPrint("\nLOADSAVE: Warning, can't backup save file!\n"); + } + + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + strcat(_gmpath, "SAVE.DAT"); + + debugPrint("\nLOADSAVE: Save name: %s\n", _gmpath); + + _flptr = fileOpen(_gmpath, "wb"); + if (_flptr == NULL) { + debugPrint("\nLOADSAVE: ** Error opening save game for writing! **\n"); + _RestoreSave(); + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + _MapDirErase(_gmpath, "BAK"); + _partyMemberUnPrepSave(); + backgroundSoundResume(); + return -1; + } + + long pos = fileTell(_flptr); + if (lsgSaveHeaderInSlot(_slot_cursor) == -1) { + debugPrint("\nLOADSAVE: ** Error writing save game header! **\n"); + debugPrint("LOADSAVE: Save file header size written: %d bytes.\n", fileTell(_flptr) - pos); + fileClose(_flptr); + _RestoreSave(); + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + _MapDirErase(_gmpath, "BAK"); + _partyMemberUnPrepSave(); + backgroundSoundResume(); + return -1; + } + + for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index++) { + long pos = fileTell(_flptr); + SaveGameHandler* handler = _master_save_list[index]; + if (handler(_flptr) == -1) { + debugPrint("\nLOADSAVE: ** Error writing save function #%d data! **\n", index); + fileClose(_flptr); + _RestoreSave(); + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + _MapDirErase(_gmpath, "BAK"); + _partyMemberUnPrepSave(); + backgroundSoundResume(); + return -1; + } + + debugPrint("LOADSAVE: Save function #%d data size written: %d bytes.\n", index, fileTell(_flptr) - pos); + } + + debugPrint("LOADSAVE: Total save data written: %ld bytes.\n", fileTell(_flptr)); + + fileClose(_flptr); + + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + _MapDirErase(_gmpath, "BAK"); + + gLoadSaveMessageListItem.num = 140; + if (messageListGetItem(&gLoadSaveMessageList, &gLoadSaveMessageListItem)) { + displayMonitorAddMessage(gLoadSaveMessageListItem.text); + } else { + debugPrint("\nError: Couldn't find LoadSave Message!"); + } + + backgroundSoundResume(); + + return 0; +} + +// 0x47DC60 +int _isLoadingGame() +{ + return _loadingGame; +} + +// 0x47DC68 +int lsgLoadGameInSlot(int slot) +{ + _loadingGame = 1; + + if (isInCombat()) { + interfaceBarEndButtonsHide(false); + _combat_over_from_load(); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); + } + + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + strcat(_gmpath, "SAVE.DAT"); + + STRUCT_613D30* ptr = &(_LSData[slot]); + debugPrint("\nLOADSAVE: Load name: %s\n", ptr->description); + + _flptr = fileOpen(_gmpath, "rb"); + if (_flptr == NULL) { + debugPrint("\nLOADSAVE: ** Error opening load game file for reading! **\n"); + _loadingGame = 0; + return -1; + } + + long pos = fileTell(_flptr); + if (lsgLoadHeaderInSlot(slot) == -1) { + debugPrint("\nLOADSAVE: ** Error reading save game header! **\n"); + fileClose(_flptr); + gameReset(); + _loadingGame = 0; + return -1; + } + + debugPrint("LOADSAVE: Load file header size read: %d bytes.\n", fileTell(_flptr) - pos); + + for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index += 1) { + long pos = fileTell(_flptr); + LoadGameHandler* handler = _master_load_list[index]; + if (handler(_flptr) == -1) { + debugPrint("\nLOADSAVE: ** Error reading load function #%d data! **\n", index); + int v12 = fileTell(_flptr); + debugPrint("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, fileTell(_flptr) - pos); + fileClose(_flptr); + gameReset(); + _loadingGame = 0; + return -1; + } + + debugPrint("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, fileTell(_flptr) - pos); + } + + debugPrint("LOADSAVE: Total load data read: %ld bytes.\n", fileTell(_flptr)); + fileClose(_flptr); + + sprintf(_str, "%s\\", "MAPS"); + _MapDirErase(_str, "BAK"); + _proto_dude_update_gender(); + + // Game Loaded. + gLoadSaveMessageListItem.num = 141; + if (messageListGetItem(&gLoadSaveMessageList, &gLoadSaveMessageListItem) == 1) { + displayMonitorAddMessage(gLoadSaveMessageListItem.text); + } else { + debugPrint("\nError: Couldn't find LoadSave Message!"); + } + + _loadingGame = 0; + + return 0; +} + +// 0x47DF10 +int lsgSaveHeaderInSlot(int slot) +{ + _ls_error_code = 4; + + STRUCT_613D30* ptr = &(_LSData[slot]); + strncpy(ptr->field_0, "FALLOUT SAVE FILE", 24); + + if (fileWrite(ptr->field_0, 1, 24, _flptr) == -1) { + return -1; + } + + short temp[3]; + temp[0] = VERSION_MAJOR; + temp[1] = VERSION_MINOR; + + ptr->field_18 = temp[0]; + ptr->field_1A = temp[1]; + + if (fileWriteInt16List(_flptr, temp, 2) == -1) { + return -1; + } + + ptr->field_1C = VERSION_RELEASE; + if (fileWriteUInt8(_flptr, VERSION_RELEASE) == -1) { + return -1; + } + + char* characterName = critterGetName(gDude); + strncpy(ptr->character_name, characterName, 32); + + if (fileWrite(ptr->character_name, 32, 1, _flptr) != 1) { + return -1; + } + + if (fileWrite(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->field_5E = temp[0]; + ptr->field_5C = temp[1]; + ptr->field_60 = temp[2]; + ptr->field_64 = local->tm_hour + local->tm_min; + + if (fileWriteInt16List(_flptr, temp, 3) == -1) { + return -1; + } + + if (_db_fwriteLong(_flptr, ptr->field_64) == -1) { + return -1; + } + + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + temp[0] = month; + temp[1] = day; + temp[2] = year; + ptr->field_70 = gameTimeGetTime(); + + if (fileWriteInt16List(_flptr, temp, 3) == -1) { + return -1; + } + + if (_db_fwriteLong(_flptr, ptr->field_70) == -1) { + return -1; + } + + ptr->field_74 = gElevation; + if (fileWriteInt16(_flptr, ptr->field_74) == -1) { + return -1; + } + + ptr->field_76 = mapGetCurrentMap(); + if (fileWriteInt16(_flptr, ptr->field_76) == -1) { + return -1; + } + + char mapName[128]; + strcpy(mapName, gMapHeader.name); + + char* v1 = _strmfe(_str, mapName, "sav"); + strncpy(ptr->file_name, v1, 16); + if (fileWrite(ptr->file_name, 16, 1, _flptr) != 1) { + return -1; + } + + if (fileWrite(_snapshotBuf, LS_PREVIEW_SIZE, 1, _flptr) != 1) { + return -1; + } + + memset(mapName, 0, 128); + if (fileWrite(mapName, 1, 128, _flptr) != 128) { + return -1; + } + + _ls_error_code = 0; + + return 0; +} + +// 0x47E2E4 +int lsgLoadHeaderInSlot(int slot) +{ + _ls_error_code = 3; + + STRUCT_613D30* ptr = &(_LSData[slot]); + + if (fileRead(ptr->field_0, 1, 24, _flptr) != 24) { + return -1; + } + + if (strncmp(ptr->field_0, "FALLOUT SAVE FILE", 18) != 0) { + debugPrint("\nLOADSAVE: ** Invalid save file on load! **\n"); + _ls_error_code = 2; + return -1; + } + + short v8[3]; + if (fileReadInt16List(_flptr, v8, 2) == -1) { + return -1; + } + + ptr->field_18 = v8[0]; + ptr->field_1A = v8[1]; + + if (fileReadUInt8(_flptr, &(ptr->field_1C)) == -1) { + return -1; + } + + if (ptr->field_18 != 1 || ptr->field_1A != 2 || ptr->field_1C != 'R') { + debugPrint("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->field_18, ptr->field_1A, ptr->field_1C); + _ls_error_code = 1; + return -1; + } + + if (fileRead(ptr->character_name, 32, 1, _flptr) != 1) { + return -1; + } + + if (fileRead(ptr->description, 30, 1, _flptr) != 1) { + return -1; + } + + if (fileReadInt16List(_flptr, v8, 3) == -1) { + return -1; + } + + ptr->field_5C = v8[0]; + ptr->field_5E = v8[1]; + ptr->field_60 = v8[2]; + + if (_db_freadInt(_flptr, &(ptr->field_64)) == -1) { + return -1; + } + + if (fileReadInt16List(_flptr, v8, 3) == -1) { + return -1; + } + + ptr->field_68 = v8[0]; + ptr->field_6A = v8[1]; + ptr->field_6C = v8[2]; + + if (_db_freadInt(_flptr, &(ptr->field_70)) == -1) { + return -1; + } + + if (fileReadInt16(_flptr, &(ptr->field_74)) == -1) { + return -1; + } + + if (fileReadInt16(_flptr, &(ptr->field_76)) == -1) { + return -1; + } + + if (fileRead(ptr->file_name, 1, 16, _flptr) != 16) { + return -1; + } + + if (fileSeek(_flptr, LS_PREVIEW_SIZE, SEEK_CUR) != 0) { + return -1; + } + + if (fileSeek(_flptr, 128, 1) != 0) { + return -1; + } + + _ls_error_code = 0; + + return 0; +} + +// 0x47E5D0 +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 (dbGetFileSize(_str, &fileSize) != 0) { + _LSstatus[index] = SLOT_STATE_EMPTY; + } else { + _flptr = fileOpen(_str, "rb"); + + if (_flptr == NULL) { + debugPrint("\nLOADSAVE: ** Error opening save game for reading! **\n"); + return -1; + } + + if (lsgLoadHeaderInSlot(index) == -1) { + if (_ls_error_code == 1) { + debugPrint("LOADSAVE: ** save file #%d is an older version! **\n", _slot_cursor); + _LSstatus[index] = SLOT_STATE_UNSUPPORTED_VERSION; + } else { + debugPrint("LOADSAVE: ** Save file #%d corrupt! **", index); + _LSstatus[index] = SLOT_STATE_ERROR; + } + } else { + _LSstatus[index] = SLOT_STATE_OCCUPIED; + } + + fileClose(_flptr); + } + } + return index; +} + +// 0x47E6D8 +void _ShowSlotList(int a1) +{ + bufferFill(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 87 + 55, 230, 353, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer[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(&gLoadSaveMessageList, &gLoadSaveMessageListItem, a1 != 0 ? 110 : 109); + sprintf(_str, "[ %s %.2d: ]", text, index + 1); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * y + 55, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); + + y += fontGetLineHeight(); + switch (_LSstatus[index]) { + case SLOT_STATE_OCCUPIED: + strcpy(_str, _LSData[index].description); + break; + case SLOT_STATE_EMPTY: + // - EMPTY - + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 111); + sprintf(_str, " %s", text); + break; + case SLOT_STATE_ERROR: + // - CORRUPT SAVE FILE - + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 112); + sprintf(_str, "%s", text); + color = _colorTable[32328]; + break; + case SLOT_STATE_UNSUPPORTED_VERSION: + // - OLD VERSION - + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 113); + sprintf(_str, " %s", text); + color = _colorTable[32328]; + break; + } + + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * y + 55, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); + y += 2 * fontGetLineHeight() + 4; + } +} + +// 0x47E8E0 +void _DrawInfoBox(int a1) +{ + blitBufferToBuffer(gLoadSaveFrmData[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 254 + 396, 164, 60, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, 640); + + unsigned char* dest; + const char* text; + int color = _colorTable[992]; + + switch (_LSstatus[a1]) { + case SLOT_STATE_OCCUPIED: + do { + STRUCT_613D30* ptr = &(_LSData[a1]); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, ptr->character_name, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); + + int v4 = ptr->field_70 / 600; + int v5 = v4 % 60; + int v6 = 25 * (v4 / 60 % 24); + int v21 = 4 * v6 + v5; + + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116 + ptr->field_68); + sprintf(_str, "%.2d %s %.4d %.4d", ptr->field_6A, text, ptr->field_6C, v21); + + int v2 = fontGetLineHeight(); + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * (256 + v2) + 397, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); + + const char* v22 = mapGetName(ptr->field_76, ptr->field_74); + const char* v9 = mapGetCityName(ptr->field_76); + sprintf(_str, "%s %s", v9, v22); + + int y = v2 + 3 + v2 + 256; + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(_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'; + fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * y + 399, beginning, 164, LS_WINDOW_WIDTH, color); + y += v2 + 2; + } + } + } while (0); + return; + case SLOT_STATE_EMPTY: + // Empty. + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 114); + dest = gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 262 + 404; + break; + case SLOT_STATE_ERROR: + // Error! + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 115); + dest = gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 262 + 404; + color = _colorTable[32328]; + break; + case SLOT_STATE_UNSUPPORTED_VERSION: + // Old version. + text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116); + dest = gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 262 + 400; + color = _colorTable[32328]; + break; + default: + assert(false && "Should be unreachable"); + } + + fontDrawText(dest, text, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); +} + +// 0x47EC48 +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"); + debugPrint(" Filename %s\n", _str); + + stream = fileOpen(_str, "rb"); + if (stream == NULL) { + debugPrint("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", a1); + return -1; + } + + if (fileSeek(stream, 131, SEEK_SET) != 0) { + debugPrint("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", a1); + fileClose(stream); + return -1; + } + + if (fileRead(_thumbnail_image, LS_PREVIEW_SIZE, 1, stream) != 1) { + debugPrint("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", a1); + fileClose(stream); + return -1; + } + + fileClose(stream); + } + + return 0; +} + +// 0x47ED5C +int _GetComment(int a1) +{ + int window = windowCreate(169, + 116, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].height, + 256, + WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + if (window == -1) { + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(window); + memcpy(windowBuffer, + gLoadSaveFrmData[LOAD_SAVE_FRM_BOX], + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].height * gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width); + + fontSetCurrent(103); + + const char* msg; + + // DONE + msg = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 104); + fontDrawText(windowBuffer + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width * 57 + 56, + msg, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + _colorTable[18979]); + + // CANCEL + msg = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 105); + fontDrawText(windowBuffer + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width * 57 + 181, + msg, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + _colorTable[18979]); + + // DESCRIPTION + msg = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 130); + + char title[260]; + strcpy(title, msg); + + int width = fontGetStringWidth(title); + fontDrawText(windowBuffer + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width * 7 + (gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width - width) / 2, + title, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_BOX].width, + _colorTable[18979]); + + fontSetCurrent(101); + + int btn; + + // DONE + btn = buttonCreate(window, + 34, + 58, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, + -1, + -1, + -1, + 507, + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn == -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // CANCEL + btn = buttonCreate(window, + 160, + 58, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, + gLoadSaveFrmSizes[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, + -1, + -1, + -1, + 508, + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], + gLoadSaveFrmData[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn == -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + windowRefresh(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], gLoadSaveFrmData[LOAD_SAVE_FRM_BOX][gLoadSaveFrmSizes[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; + } + + windowDestroy(window); + + return rc; +} + +// 0x47F084 +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 = fontGetStringWidth("_") - 4; + int windowWidth = windowGetWidth(win); + int lineHeight = fontGetLineHeight(); + unsigned char* windowBuffer = windowGetBuffer(win); + if (maxLength > 255) { + maxLength = 255; + } + + char text[256]; + strcpy(text, description); + + int textLength = strlen(text); + text[textLength] = ' '; + text[textLength + 1] = '\0'; + + int nameWidth = fontGetStringWidth(text); + + bufferFill(windowBuffer + windowWidth * y + x, nameWidth, lineHeight, windowWidth, backgroundColor); + fontDrawText(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor); + + windowRefresh(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) { + bufferFill(windowBuffer + windowWidth * y + x, fontGetStringWidth(text), lineHeight, windowWidth, backgroundColor); + + // TODO: Probably incorrect, needs testing. + if (v1 == 1) { + textLength = 1; + } + + text[textLength - 1] = ' '; + text[textLength] = '\0'; + fontDrawText(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; + } + } + + bufferFill(windowBuffer + windowWidth * y + x, fontGetStringWidth(text), lineHeight, windowWidth, backgroundColor); + + text[textLength] = keyCode & 0xFF; + text[textLength + 1] = ' '; + text[textLength + 2] = '\0'; + fontDrawText(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor); + textLength++; + + windowRefresh(win); + } + } + + blinkCounter -= 1; + if (blinkCounter == 0) { + blinkCounter = 3; + blink = !blink; + + int color = blink ? backgroundColor : textColor; + bufferFill(windowBuffer + windowWidth * y + x + fontGetStringWidth(text) - cursorWidth, cursorWidth, lineHeight - 2, windowWidth, color); + windowRefresh(win); + } + + while (getTicksSince(tick) < 1000 / 24) { + } + } + + if (rc == 0) { + text[textLength] = '\0'; + strcpy(description, text); + } + + return rc; +} + +// 0x47F48C +int _DummyFunc(File* stream) +{ + return 0; +} + +// 0x47F490 +int _PrepLoad(File* stream) +{ + gameReset(); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); + gMapHeader.name[0] = '\0'; + gameTimeSetTime(_LSData[_slot_cursor].field_70); + return 0; +} + +// 0x47F4C8 +int _EndLoad(File* stream) +{ + worldmapStartMapMusic(); + dudeSetName(_LSData[_slot_cursor].character_name); + interfaceBarRefresh(); + indicatorBarRefresh(); + tileWindowRefresh(); + if (isInCombat()) { + scriptsRequestCombat(NULL); + } + return 0; +} + +// 0x47F510 +int _GameMap2Slot(File* stream) +{ + if (_partyMemberPrepSave() == -1) { + return -1; + } + + if (_map_save_in_game(false) == -1) { + return -1; + } + + for (int index = 1; index < gPartyMemberDescriptionsLength; index += 1) { + int pid = gPartyMemberPids[index]; + if (pid == -2) { + continue; + } + + char path[MAX_PATH]; + if (_proto_list_str(pid, path) != 0) { + continue; + } + + const char* critterItemPath = (pid >> 24) == 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 (fileCopyCompressed(_str0, _str1) == -1) { + return -1; + } + } + + sprintf(_str0, "%s\\*.%s", "MAPS", "SAV"); + + char** fileNameList; + int fileNameListLength = fileNameListInit(_str0, &fileNameList, 0, 0); + if (fileNameListLength == -1) { + return -1; + } + + if (fileWriteInt32(stream, fileNameListLength) == -1) { + fileNameListFree(&fileNameList, 0); + return -1; + } + + if (fileNameListLength == 0) { + fileNameListFree(&fileNameList, 0); + return -1; + } + + sprintf(_gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1); + + if (_MapDirErase(_gmpath, "SAV") == -1) { + fileNameListFree(&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 (fileWrite(string, strlen(string) + 1, 1, stream) == -1) { + fileNameListFree(&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 (fileCopyCompressed(_str0, _str1) == -1) { + fileNameListFree(&fileNameList, 0); + return -1; + } + } + + fileNameListFree(&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 (fileCopyCompressed(_str0, _str1) == -1) { + return -1; + } + + sprintf(_str0, "%s\\%s", "MAPS", "AUTOMAP.DB"); + File* inStream = fileOpen(_str0, "rb"); + if (inStream == NULL) { + return -1; + } + + int fileSize = fileGetSize(inStream); + if (fileSize == -1) { + fileClose(inStream); + return -1; + } + + fileClose(inStream); + + if (fileWriteInt32(stream, fileSize) == -1) { + return -1; + } + + if (_partyMemberUnPrepSave() == -1) { + return -1; + } + + return 0; +} + +// SlotMap2Game +// 0x47F990 +int _SlotMap2Game(File* stream) +{ + debugPrint("LOADSAVE: in SlotMap2Game\n"); + + int v2; + if (fileReadInt32(stream, &v2) == 1) { + debugPrint("LOADSAVE: returning 1\n"); + return -1; + } + + if (v2 == 0) { + debugPrint("LOADSAVE: returning 2\n"); + return -1; + } + + sprintf(_str0, "%s\\", "PROTO\\CRITTERS"); + + if (_MapDirErase(_str0, "PRO") == -1) { + debugPrint("LOADSAVE: returning 3\n"); + return -1; + } + + sprintf(_str0, "%s\\", "PROTO\\ITEMS"); + if (_MapDirErase(_str0, "PRO") == -1) { + debugPrint("LOADSAVE: returning 4\n"); + return -1; + } + + sprintf(_str0, "%s\\", "MAPS"); + if (_MapDirErase(_str0, "SAV") == -1) { + debugPrint("LOADSAVE: returning 5\n"); + return -1; + } + + sprintf(_str0, "%s\\%s\\%s", _patches, "MAPS", "AUTOMAP.DB"); + remove(_str0); + + if (gPartyMemberDescriptionsLength > 1) { + for (int index = 1; index < gPartyMemberDescriptionsLength; index += 1) { + int pid = gPartyMemberPids[index]; + if (pid != -2) { + char protoPath[MAX_PATH]; + if (_proto_list_str(pid, protoPath) == 0) { + const char* basePath = pid >> 24 == 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) { + debugPrint("LOADSAVE: returning 6\n"); + return -1; + } + } + } + } + } + + if (v2 > 0) { + for (int index = 0; index < v2; index += 1) { + char v11[64]; // TODO: Size is probably wrong. + if (_mygets(v11, stream) == -1) { + break; + } + + sprintf(_str0, "%s\\%s\\%s%.2d\\%s", _patches, "SAVEGAME", "SLOT", _slot_cursor + 1, v11); + sprintf(_str1, "%s\\%s\\%s", _patches, "MAPS", v11); + + if (_gzdecompress_file(_str0, _str1) == -1) { + debugPrint("LOADSAVE: returning 7\n"); + return -1; + } + } + } + + const char* v9 = _strmfe(_str1, "AUTOMAP.DB", "SAV"); + sprintf(_str0, "%s\\%s\\%s%.2d\\%s", _patches, "SAVEGAME", "SLOT", _slot_cursor + 1, v9); + sprintf(_str1, "%s\\%s\\%s", _patches, "MAPS", "AUTOMAP.DB"); + if (fileCopyDecompressed(_str0, _str1) == -1) { + debugPrint("LOADSAVE: returning 8\n"); + return -1; + } + + sprintf(_str1, "%s\\%s", "MAPS", "AUTOMAP.DB"); + + int v12; + if (fileReadInt32(stream, &v12) == -1) { + debugPrint("LOADSAVE: returning 9\n"); + return -1; + } + + if (mapLoadSaved(_LSData[_slot_cursor].file_name) == -1) { + debugPrint("LOADSAVE: returning 13\n"); + return -1; + } + + return 0; +} + +// 0x47FE14 +int _mygets(char* dest, File* stream) +{ + int index = 14; + while (true) { + int c = fileReadChar(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 +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 = fileOpen(a1, "rb"); + if (stream1 == NULL) { + goto out; + } + + length = fileGetSize(stream1); + if (length == -1) { + goto out; + } + + stream2 = fileOpen(a2, "wb"); + if (stream2 == NULL) { + goto out; + } + + buf = internal_malloc(0xFFFF); + if (buf == NULL) { + goto out; + } + + while (length != 0) { + chunk_length = min(length, 0xFFFF); + + if (fileRead(buf, chunk_length, 1, stream1) != 1) { + break; + } + + if (fileWrite(buf, chunk_length, 1, stream2) != 1) { + break; + } + + length -= chunk_length; + } + + if (length != 0) { + goto out; + } + + result = 0; + +out: + + if (stream1 != NULL) { + fileClose(stream1); + } + + if (stream2 != NULL) { + fileClose(stream1); + } + + if (buf != NULL) { + internal_free(buf); + } + + return result; +} + +// InitLoadSave +// 0x48000C +void lsgInit() +{ + 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 = fileNameListInit(path, &fileList, 0, 0); + while (--fileListLength >= 0) { + sprintf(path, "%s\\%s%s", _patches, relativePath, fileList[fileListLength]); + remove(path); + } + fileNameListFree(&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 +int _SaveBackup() +{ + debugPrint("\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 = fileOpen(_str0, "rb"); + if (stream1 != NULL) { + fileClose(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 = fileNameListInit(_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) { + fileNameListFree(&fileList, 0); + return -1; + } + } + + fileNameListFree(&fileList, 0); + + debugPrint("\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 = fileOpen(_str0, "rb"); + if (stream2 != NULL) { + fileClose(stream2); + + if (_copy_file(_str0, _str1) == -1) { + return -1; + } + + _automap_db_flag = 1; + } + + return 0; +} + +// 0x4803D8 +int _RestoreSave() +{ + debugPrint("\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 = fileNameListInit(_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; + } + } + + fileNameListFree(&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 +int _LoadObjDudeCid(File* stream) +{ + int value; + + if (fileReadInt32(stream, &value) == -1) { + return -1; + } + + gDude->cid = value; + + return 0; +} + +// 0x480734 +int _SaveObjDudeCid(File* stream) +{ + return fileWriteInt32(stream, gDude->cid); +} + +// 0x480754 +int _EraseSave() +{ + debugPrint("\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 = fileNameListInit(_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); + } + + fileNameListFree(&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; +} diff --git a/src/loadsave.h b/src/loadsave.h new file mode 100644 index 0000000..596b6bf --- /dev/null +++ b/src/loadsave.h @@ -0,0 +1,153 @@ +#ifndef LOAD_SAVE_GAME_H +#define LOAD_SAVE_GAME_H + +#include "art.h" +#include "db.h" +#include "geometry.h" +#include "message.h" + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#define LOAD_SAVE_DESCRIPTION_LENGTH (30) +#define LOAD_SAVE_HANDLER_COUNT (27) + +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; + +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 int LoadGameHandler(File* stream); +typedef int SaveGameHandler(File* stream); + +#define LSGAME_MSG_NAME ("LSGAME.MSG") + +typedef struct STRUCT_613D30 { + char field_0[24]; + short field_18; + short field_1A; + // 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 field_1C; + char character_name[32]; + char description[LOAD_SAVE_DESCRIPTION_LENGTH]; + short field_5C; + short field_5E; + short field_60; + int field_64; + short field_68; + short field_6A; + short field_6C; + int field_70; + short field_74; + short field_76; + char file_name[16]; +} STRUCT_613D30; + +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; + +extern const int gLoadSaveFrmIds[LOAD_SAVE_FRM_COUNT]; + +extern int _slot_cursor; +extern bool _quick_done; +extern bool gLoadSaveWindowIsoWasEnabled; +extern int _map_backup_count; +extern int _automap_db_flag; +extern char* _patches; +extern char _emgpath[]; +extern SaveGameHandler* _master_save_list[LOAD_SAVE_HANDLER_COUNT]; +extern LoadGameHandler* _master_load_list[LOAD_SAVE_HANDLER_COUNT]; +extern int _loadingGame; + +extern Size gLoadSaveFrmSizes[LOAD_SAVE_FRM_COUNT]; +extern MessageList gLoadSaveMessageList; +extern STRUCT_613D30 _LSData[10]; +extern int _LSstatus[10]; +extern unsigned char* _thumbnail_image; +extern unsigned char* _snapshotBuf; +extern MessageListItem gLoadSaveMessageListItem; +extern int _dbleclkcntr; +extern int gLoadSaveWindow; +extern unsigned char* gLoadSaveFrmData[LOAD_SAVE_FRM_COUNT]; +extern unsigned char* _snapshot; +extern char _str2[MAX_PATH]; +extern char _str0[MAX_PATH]; +extern char _str1[MAX_PATH]; +extern char _str[MAX_PATH]; +extern unsigned char* gLoadSaveWindowBuffer; +extern char _gmpath[MAX_PATH]; +extern File* _flptr; +extern int _ls_error_code; +extern int gLoadSaveWindowOldFont; +extern CacheEntry* gLoadSaveFrmHandles[LOAD_SAVE_FRM_COUNT]; + +void _InitLoadSave(); +void _ResetLoadSave(); +int lsgSaveGame(int mode); +int _QuickSnapShot(); +int lsgLoadGame(int mode); +int lsgWindowInit(int windowType); +int lsgWindowFree(int windowType); +int lsgPerformSaveGame(); +int _isLoadingGame(); +int lsgLoadGameInSlot(int slot); +int lsgSaveHeaderInSlot(int slot); +int lsgLoadHeaderInSlot(int slot); +int _GetSlotList(); +void _ShowSlotList(int a1); +void _DrawInfoBox(int a1); +int _LoadTumbSlot(int a1); +int _GetComment(int a1); +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 _DummyFunc(File* stream); +int _PrepLoad(File* stream); +int _EndLoad(File* stream); +int _GameMap2Slot(File* stream); +int _SlotMap2Game(File* stream); +int _mygets(char* dest, File* stream); +int _copy_file(const char* a1, const char* a2); +void lsgInit(); +int _MapDirErase(const char* path, const char* a2); +int _MapDirEraseFile_(const char* a1, const char* a2); +int _SaveBackup(); +int _RestoreSave(); +int _LoadObjDudeCid(File* stream); +int _SaveObjDudeCid(File* stream); +int _EraseSave(); + +#endif /* LOAD_SAVE_GAME_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..aee206b --- /dev/null +++ b/src/main.c @@ -0,0 +1,807 @@ +#include "main.h" + +#include "autorun.h" +#include "character_selector.h" +#include "color.h" +#include "core.h" +#include "credits.h" +#include "cycle.h" +#include "db.h" +#include "debug.h" +#include "draw.h" +#include "endgame.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "loadsave.h" +#include "map.h" +#include "object.h" +#include "options.h" +#include "palette.h" +#include "random.h" +#include "scripts.h" +#include "selfrun.h" +#include "text_font.h" +#include "version.h" +#include "window_manager.h" +#include "word_wrap.h" +#include "world_map.h" + +// 0x5194C8 +char _mainMap[] = "artemple.map"; + +// 0x5194D8 +int _main_game_paused = 0; + +// 0x5194DC +char** _main_selfrun_list = NULL; + +// 0x5194E0 +int _main_selfrun_count = 0; + +// 0x5194E4 +int _main_selfrun_index = 0; + +// 0x5194E8 +bool _main_show_death_scene = false; + +// 0x5194F0 +int gMainMenuWindow = -1; + +// 0x5194F4 +unsigned char* gMainMenuWindowBuffer = NULL; + +// 0x5194F8 +unsigned char* gMainMenuBackgroundFrmData = NULL; + +// 0x5194FC +unsigned char* gMainMenuButtonUpFrmData = NULL; + +// 0x519500 +unsigned char* gMainMenuButtonDownFrmData = NULL; + +// 0x519504 +bool _in_main_menu = false; + +// 0x519508 +bool gMainMenuWindowInitialized = false; + +// 0x51950C +unsigned int gMainMenuScreensaverDelay = 120000; + +// 0x519510 +const int gMainMenuButtonKeyBindings[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 +const 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, +}; + +// 0x614838 +bool _main_death_voiceover_done; + +// 0x614840 +int gMainMenuButtons[MAIN_MENU_BUTTON_COUNT]; + +// 0x614858 +bool gMainMenuWindowHidden; + +// 0x61485C +CacheEntry* gMainMenuButtonUpFrmHandle; + +// 0x614860 +CacheEntry* gMainMenuButtonDownFrmHandle; + +// 0x614864 +CacheEntry* gMainMenuBackgroundFrmHandle; + +// 0x48099C +int falloutMain(int argc, char** argv) +{ + if (!autorunMutexCreate()) { + return 1; + } + + if (!falloutInit(argc, argv)) { + return 1; + } + + gameMoviePlay(MOVIE_IPLOGO, GAME_MOVIE_FADE_IN); + gameMoviePlay(MOVIE_INTRO, 0); + gameMoviePlay(MOVIE_CREDITS, 0); + + if (mainMenuWindowInit() == 0) { + bool done = false; + while (!done) { + keyboardReset(); + _gsound_background_play_level_music("07desert", 11); + mainMenuWindowUnhide(1); + + mouseShowCursor(); + int mainMenuRc = mainMenuWindowHandleEvents(); + mouseHideCursor(); + + switch (mainMenuRc) { + case MAIN_MENU_INTRO: + mainMenuWindowHide(true); + gameMoviePlay(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC); + gameMoviePlay(MOVIE_CREDITS, 0); + break; + case MAIN_MENU_NEW_GAME: + mainMenuWindowHide(true); + mainMenuWindowFree(); + if (characterSelectorOpen() == 2) { + gameMoviePlay(MOVIE_ELDER, GAME_MOVIE_STOP_MUSIC); + randomSeedPrerandom(-1); + _main_load_new(_mainMap); + mainLoop(); + paletteFadeTo(gPaletteWhite); + objectHide(gDude, NULL); + _map_exit(); + gameReset(); + if (_main_show_death_scene != 0) { + showDeath(); + _main_show_death_scene = 0; + } + } + + mainMenuWindowInit(); + + break; + case MAIN_MENU_LOAD_GAME: + if (1) { + int win = windowCreate(0, 0, 640, 480, _colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + mainMenuWindowHide(true); + mainMenuWindowFree(); + backgroundSoundDelete(); + _game_user_wants_to_quit = 0; + gDude->flags &= ~OBJECT_FLAG_0x08; + _main_show_death_scene = 0; + objectShow(gDude, NULL); + mouseHideCursor(); + _map_init(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + mouseShowCursor(); + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + int loadGameRc = lsgLoadGame(LOAD_SAVE_MODE_FROM_MAIN_MENU); + if (loadGameRc == -1) { + debugPrint("\n ** Error running LoadGame()! **\n"); + } else if (loadGameRc != 0) { + windowDestroy(win); + win = -1; + mainLoop(); + } + paletteFadeTo(gPaletteWhite); + if (win != -1) { + windowDestroy(win); + } + objectHide(gDude, NULL); + _map_exit(); + gameReset(); + if (_main_show_death_scene != 0) { + showDeath(); + _main_show_death_scene = 0; + } + mainMenuWindowInit(); + } + break; + case MAIN_MENU_TIMEOUT: + debugPrint("Main menu timed-out\n"); + // FALLTHROUGH + case MAIN_MENU_3: + // _main_selfrun_play(); + break; + case MAIN_MENU_OPTIONS: + mainMenuWindowHide(false); + mouseShowCursor(); + showOptionsWithInitialKeyCode(112); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + mouseShowCursor(); + mainMenuWindowUnhide(0); + break; + case MAIN_MENU_CREDITS: + mainMenuWindowHide(true); + creditsOpen("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. + mainMenuWindowHide(true); + creditsOpen("quotes.txt", -1, true); + break; + case MAIN_MENU_EXIT: + case -1: + done = true; + mainMenuWindowHide(true); + mainMenuWindowFree(); + backgroundSoundDelete(); + break; + case MAIN_MENU_SELFRUN: + // _main_selfrun_record(); + break; + } + } + } + + backgroundSoundDelete(); + _main_selfrun_exit(); + gameExit(); + + autorunMutexClose(); + + return 0; +} + +// 0x480CC0 +bool falloutInit(int argc, char** argv) +{ + if (gameInitWithOptions("FALLOUT II", false, 0, 0, argc, argv) == -1) { + return false; + } + + if (_main_selfrun_list != NULL) { + _main_selfrun_exit(); + } + + if (_selfrun_get_list(&_main_selfrun_list, &_main_selfrun_count) == 0) { + _main_selfrun_index = 0; + } + + return true; +} + +// 0x480D4C +int _main_load_new(char* mapFileName) +{ + _game_user_wants_to_quit = 0; + _main_show_death_scene = 0; + gDude->flags &= ~OBJECT_FLAG_0x08; + objectShow(gDude, NULL); + mouseHideCursor(); + + int win = windowCreate(0, 0, 640, 480, _colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); + windowRefresh(win); + + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + _map_init(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + mouseShowCursor(); + mapLoadByName(mapFileName); + worldmapStartMapMusic(); + paletteFadeTo(gPaletteWhite); + windowDestroy(win); + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + return 0; +} + +// 0x480E48 +void mainLoop() +{ + bool cursorWasHidden = cursorIsHidden(); + if (cursorWasHidden) { + mouseShowCursor(); + } + + _main_game_paused = 0; + + scriptsEnable(); + + while (_game_user_wants_to_quit == 0) { + int keyCode = _get_input(); + gameHandleKey(keyCode, false); + + scriptsHandleRequests(); + + mapHandleTransition(); + + if (_main_game_paused != 0) { + _main_game_paused = 0; + } + + if ((gDude->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; + } + } + + scriptsDisable(); + + if (cursorWasHidden) { + mouseHideCursor(); + } +} + +// 0x480F38 +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; +} + +// 0x48118C +void showDeath() +{ + artCacheFlush(); + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + bool oldCursorIsHidden = cursorIsHidden(); + if (oldCursorIsHidden) { + mouseShowCursor(); + } + + int win = windowCreate(0, 0, 640, 480, 0, WINDOW_FLAG_0x04); + if (win != -1) { + do { + unsigned char* windowBuffer = windowGetBuffer(win); + if (windowBuffer == NULL) { + break; + } + + // DEATH.FRM + CacheEntry* backgroundHandle; + int fid = buildFid(6, 309, 0, 0, 0); + unsigned char* background = artLockFrameData(fid, 0, 0, &backgroundHandle); + if (background == NULL) { + break; + } + + while (mouseGetEvent() != 0) { + _get_input(); + } + + keyboardReset(); + inputEventQueueReset(); + + blitBufferToBuffer(background, 640, 480, 640, windowBuffer, 640); + artUnlock(backgroundHandle); + + const char* deathFileName = endgameDeathEndingGetFileName(); + + int subtitles = 0; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitles); + if (subtitles != 0) { + char text[512]; + if (_mainDeathGrabTextFile(deathFileName, text) == 0) { + debugPrint("\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 - fontGetLineHeight() * count - 8); + bufferFill(p - 602, 564, fontGetLineHeight() * count + 2, 640, 0); + p += 40; + for (int index = 0; index < count; index++) { + fontDrawText(p, text + beginnings[index], 560, 640, _colorTable[32767]); + p += 640 * fontGetLineHeight(); + } + } + } + } + + windowRefresh(win); + + colorPaletteLoad("art\\intrface\\death.pal"); + paletteFadeTo(_cmap); + + _main_death_voiceover_done = false; + speechSetEndCallback(_main_death_voiceover_callback); + + unsigned int delay; + if (speechLoad(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 && getTicksSince(time) < delay); + + speechSetEndCallback(NULL); + + speechDelete(); + + while (mouseGetEvent() != 0) { + _get_input(); + } + + if (keyCode == -1) { + coreDelayProcessingEvents(500); + } + + paletteFadeTo(gPaletteBlack); + colorPaletteLoad("color.pal"); + } while (0); + windowDestroy(win); + } + + if (oldCursorIsHidden) { + mouseHideCursor(); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + colorCycleEnable(); +} + +// 0x4814A8 +void _main_death_voiceover_callback() +{ + _main_death_voiceover_done = true; +} + +// Read endgame subtitle. +// +// 0x4814B4 +int _mainDeathGrabTextFile(const char* fileName, char* dest) +{ + const char* p = strrchr(fileName, '\\'); + if (p == NULL) { + return -1; + } + + char* language = NULL; + if (!configGetString(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { + debugPrint("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 = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + while (true) { + int c = fileReadChar(stream); + if (c == -1) { + break; + } + + if (c == '\n') { + c = ' '; + } + + *dest++ = (c & 0xFF); + } + + fileClose(stream); + + *dest = '\0'; + + return 0; +} + +// 0x481598 +int _mainDeathWordWrap(char* text, int width, short* beginnings, short* count) +{ + // TODO: Probably wrong. + while (true) { + char* p = text; + while (*p != ':') { + if (*p != '\0') { + p++; + if (*p == ':') { + break; + } + if (*p != '\0') { + continue; + } + } + p = NULL; + break; + } + + if (p == NULL) { + break; + } + + if (p - 1 < text) { + break; + } + p[0] = ' '; + p[-1] = ' '; + } + + if (wordWrap(text, width, beginnings, count) == -1) { + return -1; + } + + // TODO: Probably wrong. + *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; +} + +// 0x481650 +int mainMenuWindowInit() +{ + int fid; + MessageListItem msg; + int len; + + if (gMainMenuWindowInitialized) { + return 0; + } + + colorPaletteLoad("color.pal"); + + gMainMenuWindow = windowCreate(0, 0, 640, 480, 0, 12); + if (gMainMenuWindow == -1) { + mainMenuWindowFree(); + return -1; + } + + gMainMenuWindowBuffer = windowGetBuffer(gMainMenuWindow); + + // mainmenu.frm + int backgroundFid = buildFid(6, 140, 0, 0, 0); + gMainMenuBackgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &gMainMenuBackgroundFrmHandle); + if (gMainMenuBackgroundFrmData == NULL) { + mainMenuWindowFree(); + return -1; + } + + blitBufferToBuffer(gMainMenuBackgroundFrmData, 640, 480, 640, gMainMenuWindowBuffer, 640); + artUnlock(gMainMenuBackgroundFrmHandle); + + int oldFont = fontGetCurrent(); + fontSetCurrent(100); + + // Copyright. + msg.num = 20; + if (messageListGetItem(&gMiscMessageList, &msg)) { + windowDrawText(gMainMenuWindow, msg.text, 0, 15, 460, _colorTable[21091] | 0x6000000); + } + + // Version. + char version[VERSION_MAX]; + versionGetVersion(version); + len = fontGetStringWidth(version); + windowDrawText(gMainMenuWindow, version, 0, 615 - len, 460, _colorTable[21091] | 0x6000000); + + // menuup.frm + fid = buildFid(6, 299, 0, 0, 0); + gMainMenuButtonUpFrmData = artLockFrameData(fid, 0, 0, &gMainMenuButtonUpFrmHandle); + if (gMainMenuButtonUpFrmData == NULL) { + mainMenuWindowFree(); + return -1; + } + + // menudown.frm + fid = buildFid(6, 300, 0, 0, 0); + gMainMenuButtonDownFrmData = artLockFrameData(fid, 0, 0, &gMainMenuButtonDownFrmHandle); + if (gMainMenuButtonDownFrmData == NULL) { + mainMenuWindowFree(); + return -1; + } + + for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { + gMainMenuButtons[index] = -1; + } + + for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { + gMainMenuButtons[index] = buttonCreate(gMainMenuWindow, 30, 19 + index * 42 - index, 26, 26, -1, -1, 1111, gMainMenuButtonKeyBindings[index], gMainMenuButtonUpFrmData, gMainMenuButtonDownFrmData, 0, 32); + if (gMainMenuButtons[index] == -1) { + mainMenuWindowFree(); + return -1; + } + + buttonSetMask(gMainMenuButtons[index], gMainMenuButtonUpFrmData); + } + + fontSetCurrent(104); + + for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { + msg.num = 9 + index; + if (messageListGetItem(&gMiscMessageList, &msg)) { + len = fontGetStringWidth(msg.text); + fontDrawText(gMainMenuWindowBuffer + 640 * (42 * index - index + 20) + 126 - (len / 2), msg.text, 640 - (126 - (len / 2)) - 1, 640, _colorTable[21091]); + } + } + + fontSetCurrent(oldFont); + + gMainMenuWindowInitialized = true; + gMainMenuWindowHidden = true; + + return 0; +} + +// 0x481968 +void mainMenuWindowFree() +{ + if (!gMainMenuWindowInitialized) { + return; + } + + for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { + // FIXME: Why it tries to free only invalid buttons? + if (gMainMenuButtons[index] == -1) { + buttonDestroy(gMainMenuButtons[index]); + } + } + + if (gMainMenuButtonDownFrmData) { + artUnlock(gMainMenuButtonDownFrmHandle); + gMainMenuButtonDownFrmHandle = NULL; + gMainMenuButtonDownFrmData = NULL; + } + + if (gMainMenuButtonUpFrmData) { + artUnlock(gMainMenuButtonUpFrmHandle); + gMainMenuButtonUpFrmHandle = NULL; + gMainMenuButtonUpFrmData = NULL; + } + + if (gMainMenuWindow != -1) { + windowDestroy(gMainMenuWindow); + } + + gMainMenuWindowInitialized = false; +} + +// 0x481A00 +void mainMenuWindowHide(bool animate) +{ + if (!gMainMenuWindowInitialized) { + return; + } + + if (gMainMenuWindowHidden) { + return; + } + + soundContinueAll(); + + if (animate) { + paletteFadeTo(gPaletteBlack); + soundContinueAll(); + } + + windowHide(gMainMenuWindow); + + gMainMenuWindowHidden = true; +} + +// 0x481A48 +void mainMenuWindowUnhide(bool animate) +{ + if (!gMainMenuWindowInitialized) { + return; + } + + if (!gMainMenuWindowHidden) { + return; + } + + windowUnhide(gMainMenuWindow); + + if (animate) { + colorPaletteLoad("color.pal"); + paletteFadeTo(_cmap); + } + + gMainMenuWindowHidden = false; +} + +// 0x481AA8 +int _main_menu_is_enabled() +{ + return 1; +} + +// 0x481AEC +int mainMenuWindowHandleEvents() +{ + _in_main_menu = true; + + bool oldCursorIsHidden = cursorIsHidden(); + if (oldCursorIsHidden) { + mouseShowCursor(); + } + + 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 == gMainMenuButtonKeyBindings[buttonIndex] || keyCode == toupper(gMainMenuButtonKeyBindings[buttonIndex])) { + soundPlayFile("nmselec1"); + + rc = _return_values[buttonIndex]; + + if (buttonIndex == MAIN_MENU_BUTTON_CREDITS && (gPressedPhysicalKeys[DIK_RSHIFT] != KEY_STATE_UP || gPressedPhysicalKeys[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) { + brightnessIncrease(); + } else if (keyCode == KEY_MINUS || keyCode == KEY_UNDERSCORE) { + brightnessDecrease(); + } else if (keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) { + rc = MAIN_MENU_3; + continue; + } else if (keyCode == 1111) { + if (!(mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT)) { + soundPlayFile("nmselec0"); + } + continue; + } + } + + if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit == 3) { + rc = MAIN_MENU_EXIT; + soundPlayFile("nmselec1"); + break; + } else if (_game_user_wants_to_quit == 2) { + _game_user_wants_to_quit = 0; + } else { + if (getTicksSince(tick) >= gMainMenuScreensaverDelay) { + rc = MAIN_MENU_TIMEOUT; + } + } + } + + if (oldCursorIsHidden) { + mouseHideCursor(); + } + + _in_main_menu = false; + + return rc; +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..c878d3c --- /dev/null +++ b/src/main.h @@ -0,0 +1,71 @@ +#ifndef MAIN_H +#define MAIN_H + +#include "art.h" + +#include + +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; + +typedef enum MainMenuOption { + MAIN_MENU_INTRO, + MAIN_MENU_NEW_GAME, + MAIN_MENU_LOAD_GAME, + MAIN_MENU_3, + MAIN_MENU_TIMEOUT, + MAIN_MENU_CREDITS, + MAIN_MENU_QUOTES, + MAIN_MENU_EXIT, + MAIN_MENU_SELFRUN, + MAIN_MENU_OPTIONS, +} MainMenuOption; + +extern char _mainMap[]; +extern int _main_game_paused; +extern char** _main_selfrun_list; +extern int _main_selfrun_count; +extern int _main_selfrun_index; +extern bool _main_show_death_scene; +extern int mainMenuWindowHandle; +extern unsigned char* mainMenuWindowBuf; +extern unsigned char* gMainMenuBackgroundFrmData; +extern unsigned char* gMainMenuButtonUpFrmData; +extern unsigned char* gMainMenuButtonDownFrmData; +extern bool _in_main_menu; +extern bool gMainMenuWindowInitialized; +extern unsigned int gMainMenuScreensaverDelay; +extern const int gMainMenuButtonKeyBindings[MAIN_MENU_BUTTON_COUNT]; +extern const int _return_values[MAIN_MENU_BUTTON_COUNT]; + +extern bool _main_death_voiceover_done; +extern int gMainMenuButtons[MAIN_MENU_BUTTON_COUNT]; +extern bool gMainMenuWindowHidden; +extern CacheEntry* gMainMenuButtonUpFrmHandle; +extern CacheEntry* gMainMenuButtonDownFrmHandle; +extern CacheEntry* gMainMenuBackgroundFrmHandle; + +int falloutMain(int argc, char** argv); +bool falloutInit(int argc, char** argv); +int _main_load_new(char* fname); +void mainLoop(); +void _main_selfrun_exit(); +void showDeath(); +void _main_death_voiceover_callback(); +int _mainDeathGrabTextFile(const char* fileName, char* dest); +int _mainDeathWordWrap(char* text, int width, short* beginnings, short* count); +int mainMenuWindowInit(); +void mainMenuWindowFree(); +void mainMenuWindowHide(bool animate); +void mainMenuWindowUnhide(bool animate); +int _main_menu_is_enabled(); +int mainMenuWindowHandleEvents(); + +#endif /* MAIN_H */ diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000..bf9732e --- /dev/null +++ b/src/map.c @@ -0,0 +1,1648 @@ +#include "map.h" + +#include "animation.h" +#include "automap.h" +#include "character_editor.h" +#include "color.h" +#include "combat.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "light.h" +#include "loadsave.h" +#include "memory.h" +#include "object.h" +#include "palette.h" +#include "pipboy.h" +#include "proto.h" +#include "proto_instance.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "text_object.h" +#include "tile.h" +#include "window_manager.h" +#include "world_map.h" + +#include +#include + +// 0x50B058 +char byte_50B058[] = ""; + +// 0x50B30C +char _aErrorF2[] = "ERROR! F2"; + +// 0x519540 +IsoWindowRefreshProc* _map_scroll_refresh = isoWindowRefreshRectGame; + +// 0x519544 +const int _map_data_elev_flags[ELEVATION_COUNT] = { + 2, + 4, + 8, +}; + +// 0x519550 +unsigned int gIsoWindowScrollTimestamp = 0; + +// 0x519554 +bool gIsoEnabled = false; + +// 0x519558 +int gEnteringElevation = 0; + +// 0x51955C +int gEnteringTile = -1; + +// 0x519560 +int gEnteringRotation = ROTATION_NE; + +// 0x519564 +int gMapSid = -1; + +// local_vars +// 0x519568 +int* gMapLocalVars = NULL; + +// map_vars +// 0x51956C +int* gMapGlobalVars = NULL; + +// local_vars_num +// 0x519570 +int gMapLocalVarsLength = 0; + +// map_vars_num +// 0x519574 +int gMapGlobalVarsLength = 0; + +// Current elevation. +// +// 0x519578 +int gElevation = 0; + +// 0x51957C +char* _errMapName = byte_50B058; + +// 0x519584 +int _wmMapIdx = -1; + +// 0x614868 +TileData _square_data[ELEVATION_COUNT]; + +// 0x631D28 +MapTransition gMapTransition; + +// 0x631D38 +Rect gIsoWindowRect; + +// map.msg +// +// map_msg_file +// 0x631D48 +MessageList gMapMessageList; + +// 0x631D50 +unsigned char* gIsoWindowBuffer; + +// 0x631D54 +MapHeader gMapHeader; + +// 0x631E40 +TileData* _square[ELEVATION_COUNT]; + +// 0x631E4C +int gIsoWindow; + +// 0x631E50 +char _scratchStr[40]; + +// Last map file name. +// +// 0x631E78 +char _map_path[MAX_PATH]; + +// iso_init +// 0x481CA0 +int isoInit() +{ + _tile_disable_scroll_limiting(); + _tile_disable_scroll_blocking(); + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + _square[elevation] = &(_square_data[elevation]); + } + + gIsoWindow = windowCreate(0, 0, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99, 256, 10); + if (gIsoWindow == -1) { + debugPrint("win_add failed in iso_init\n"); + return -1; + } + + gIsoWindowBuffer = windowGetBuffer(gIsoWindow); + if (gIsoWindowBuffer == NULL) { + debugPrint("win_get_buf failed in iso_init\n"); + return -1; + } + + if (windowGetRect(gIsoWindow, &gIsoWindowRect) != 0) { + debugPrint("win_get_rect failed in iso_init\n"); + return -1; + } + + if (artInit() != 0) { + debugPrint("art_init failed in iso_init\n"); + return -1; + } + + debugPrint(">art_init\t\t"); + + if (tileInit(_square, SQUARE_GRID_WIDTH, SQUARE_GRID_HEIGHT, HEX_GRID_WIDTH, HEX_GRID_HEIGHT, gIsoWindowBuffer, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99, _scr_size.right - _scr_size.left + 1, isoWindowRefreshRect) != 0) { + debugPrint("tile_init failed in iso_init\n"); + return -1; + } + + debugPrint(">tile_init\t\t"); + + if (objectsInit(gIsoWindowBuffer, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99, _scr_size.right - _scr_size.left + 1) != 0) { + debugPrint("obj_init failed in iso_init\n"); + return -1; + } + + debugPrint(">obj_init\t\t"); + + colorCycleInit(); + debugPrint(">cycle_init\t\t"); + + _tile_enable_scroll_blocking(); + _tile_enable_scroll_limiting(); + + if (interfaceInit() != 0) { + debugPrint("intface_init failed in iso_init\n"); + return -1; + } + + debugPrint(">intface_init\t\t"); + + mapMakeMapsDirectory(); + + gEnteringElevation = -1; + gEnteringTile = -1; + gEnteringRotation = -1; + + return 0; +} + +// 0x481ED4 +void isoReset() +{ + if (gMapGlobalVars != NULL) { + internal_free(gMapGlobalVars); + gMapGlobalVars = NULL; + gMapGlobalVarsLength = 0; + } + + if (gMapLocalVars != NULL) { + internal_free(gMapLocalVars); + gMapLocalVars = NULL; + gMapLocalVarsLength = 0; + } + + artReset(); + tileReset(); + objectsReset(); + colorCycleReset(); + interfaceReset(); + gEnteringElevation = -1; + gEnteringTile = -1; + gEnteringRotation = -1; +} + +// 0x481F48 +void isoExit() +{ + interfaceFree(); + colorCycleFree(); + objectsExit(); + tileExit(); + artExit(); + + if (gMapGlobalVars != NULL) { + internal_free(gMapGlobalVars); + gMapGlobalVars = NULL; + gMapGlobalVarsLength = 0; + } + + if (gMapLocalVars != NULL) { + internal_free(gMapLocalVars); + gMapLocalVars = NULL; + gMapLocalVarsLength = 0; + } +} + +// 0x481FB4 +void _map_init() +{ + char* executable; + configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, "executable", &executable); + if (stricmp(executable, "mapper") == 0) { + _map_scroll_refresh = isoWindowRefreshRectMapper; + } + + if (messageListInit(&gMapMessageList)) { + char path[FILENAME_MAX]; + sprintf(path, "%smap.msg", asc_5186C8); + + if (!messageListLoad(&gMapMessageList, path)) { + debugPrint("\nError loading map_msg_file!"); + } + } else { + debugPrint("\nError initing map_msg_file!"); + } + + _map_new_map(); + tickersAdd(gameMouseRefresh); + _gmouse_disable(0); + windowUnhide(gIsoWindow); +} + +// 0x482084 +void _map_exit() +{ + windowHide(gIsoWindow); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + tickersRemove(gameMouseRefresh); + if (!messageListFree(&gMapMessageList)) { + debugPrint("\nError exiting map_msg_file!"); + } +} + +// 0x4820C0 +void isoEnable() +{ + if (!gIsoEnabled) { + textObjectsEnable(); + if (!gameUiIsDisabled()) { + _gmouse_enable(); + } + tickersAdd(_object_animate); + tickersAdd(_dude_fidget); + _scr_enable_critters(); + gIsoEnabled = true; + } +} + +// 0x482104 +bool isoDisable() +{ + if (!gIsoEnabled) { + return false; + } + + _scr_disable_critters(); + tickersRemove(_dude_fidget); + tickersRemove(_object_animate); + _gmouse_disable(0); + textObjectsDisable(); + + gIsoEnabled = false; + + return true; +} + +// 0x482148 +bool isoIsDisabled() +{ + return gIsoEnabled == false; +} + +// map_set_elevation +// 0x482158 +int mapSetElevation(int elevation) +{ + if (!elevationIsValid(elevation)) { + return -1; + } + + bool gameMouseWasVisible = false; + if (gameMouseGetCursor() != MOUSE_CURSOR_WAIT_PLANET) { + gameMouseWasVisible = gameMouseObjectsIsVisible(); + gameMouseObjectsHide(); + gameMouseSetCursor(MOUSE_CURSOR_NONE); + } + + if (elevation != gElevation) { + _wmMapMarkMapEntranceState(gMapHeader.field_34, elevation, 1); + } + + gElevation = elevation; + + reg_anim_clear(gDude); + _dude_stand(gDude, gDude->rotation, gDude->fid); + _partyMemberSyncPosition(); + + if (gMapSid != -1) { + scriptsExecMapUpdateProc(); + } + + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + return 0; +} + +// 0x482220 +int mapSetGlobalVar(int var, int value) +{ + if (var < 0 || var >= gMapGlobalVarsLength) { + debugPrint("ERROR: attempt to reference map var out of range: %d", var); + return -1; + } + + gMapGlobalVars[var] = value; + + return 0; +} + +// 0x482250 +int mapGetGlobalVar(int var) +{ + if (var < 0 || var >= gMapGlobalVarsLength) { + debugPrint("ERROR: attempt to reference map var out of range: %d", var); + return 0; + } + + return gMapGlobalVars[var]; +} + +// 0x482280 +int mapSetLocalVar(int var, int value) +{ + if (var < 0 || var >= gMapLocalVarsLength) { + debugPrint("ERROR: attempt to reference local var out of range: %d", var); + return -1; + } + + gMapLocalVars[var] = value; + + return 0; +} + +// 0x4822B0 +int mapGetLocalVar(int var) +{ + if (var < 0 || var >= gMapLocalVarsLength) { + debugPrint("ERROR: attempt to reference local var out of range: %d", var); + return 0; + } + + return gMapLocalVars[var]; +} + +// Make a room to store more local variables. +// +// 0x4822E0 +int _map_malloc_local_var(int a1) +{ + int oldMapLocalVarsLength = gMapLocalVarsLength; + gMapLocalVarsLength += a1; + + int* vars = internal_realloc(gMapLocalVars, sizeof(*vars) * gMapLocalVarsLength); + if (vars == NULL) { + debugPrint("\nError: Ran out of memory!"); + } + + gMapLocalVars = vars; + memset((unsigned char*)vars + sizeof(*vars) * oldMapLocalVarsLength, 0, sizeof(*vars) * a1); + + return oldMapLocalVarsLength; +} + +// 0x48234C +void mapSetStart(int tile, int elevation, int rotation) +{ + gMapHeader.enteringTile = tile; + gMapHeader.enteringElevation = elevation; + gMapHeader.enteringRotation = rotation; +} + +// 0x4824CC +char* mapGetName(int map, int elevation) +{ + if (map < 0 || map >= mapGetCount()) { + return NULL; + } + + if (!elevationIsValid(elevation)) { + return NULL; + } + + MessageListItem messageListItem; + return getmsg(&gMapMessageList, &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 >= mapGetCount()) { + return 0; + } + + if (map2 < 0 || map2 >= mapGetCount()) { + 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* mapGetCityName(int map) +{ + int city; + if (_wmMatchAreaContainingMapIdx(map, &city) == -1) { + return _aErrorF2; + } + + MessageListItem messageListItem; + char* name = getmsg(&gMapMessageList, &messageListItem, 1500 + city); + return name; +} + +// 0x48268C +char* _map_get_description_idx_(int map) +{ + int city; + if (_wmMatchAreaContainingMapIdx(map, &city) == 0) { + _wmGetAreaIdxName(city, _scratchStr); + } else { + strcpy(_scratchStr, _errMapName); + } + + return _scratchStr; +} + +// 0x4826B8 +int mapGetCurrentMap() +{ + return gMapHeader.field_34; +} + +// 0x4826C0 +int mapScroll(int dx, int dy) +{ + if (getTicksSince(gIsoWindowScrollTimestamp) < 33) { + return -2; + } + + gIsoWindowScrollTimestamp = _get_time(); + + int screenDx = dx * 32; + int screenDy = dy * 24; + + if (screenDx == 0 && screenDy == 0) { + return -1; + } + + gameMouseObjectsHide(); + + int centerScreenX; + int centerScreenY; + tileToScreenXY(gCenterTile, ¢erScreenX, ¢erScreenY, gElevation); + centerScreenX += screenDx + 16; + centerScreenY += screenDy + 8; + + int newCenterTile = tileFromScreenXY(centerScreenX, centerScreenY, gElevation); + if (newCenterTile == -1) { + return -1; + } + + if (tileSetCenter(newCenterTile, 0) == -1) { + return -1; + } + + Rect r1; + rectCopy(&r1, &gIsoWindowRect); + + Rect r2; + rectCopy(&r2, &r1); + + int width = _scr_size.right - _scr_size.left + 1; + int pitch = width; + int height = _scr_size.bottom - _scr_size.top - 99; + + if (screenDx != 0) { + width -= 32; + } + + if (screenDy != 0) { + height -= 24; + } + + if (screenDx < 0) { + r2.right = r2.left - screenDx; + } else { + r2.left = r2.right - screenDx; + } + + unsigned char* src; + unsigned char* dest; + int step; + if (screenDy < 0) { + r1.bottom = r1.top - screenDy; + src = gIsoWindowBuffer + pitch * (height - 1); + dest = gIsoWindowBuffer + pitch * (_scr_size.bottom - _scr_size.top - 100); + if (screenDx < 0) { + dest -= screenDx; + } else { + src += screenDx; + } + step = -pitch; + } else { + r1.top = r1.bottom - screenDy; + dest = gIsoWindowBuffer; + src = gIsoWindowBuffer + 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); + } + + windowRefresh(gIsoWindow); + + return 0; +} + +// 0x482900 +char* mapBuildPath(char* name) +{ + if (*name != '\\') { + sprintf(_map_path, "maps\\%s", name); + return _map_path; + } + return name; +} + +// 0x482924 +int mapSetEnteringLocation(int elevation, int tile_num, int orientation) +{ + gEnteringElevation = elevation; + gEnteringTile = tile_num; + gEnteringRotation = orientation; + return 0; +} + +// 0x482938 +void _map_new_map() +{ + mapSetElevation(0); + tileSetCenter(20100, TILE_SET_CENTER_FLAG_0x02); + memset(&gMapTransition, 0, sizeof(gMapTransition)); + gMapHeader.enteringElevation = 0; + gMapHeader.enteringRotation = 0; + gMapHeader.localVariablesCount = 0; + gMapHeader.version = 20; + gMapHeader.name[0] = '\0'; + gMapHeader.enteringTile = 20100; + _obj_remove_all(); + _anim_stop(); + + if (gMapGlobalVars != NULL) { + internal_free(gMapGlobalVars); + gMapGlobalVars = NULL; + gMapGlobalVarsLength = 0; + } + + if (gMapLocalVars != NULL) { + internal_free(gMapLocalVars); + gMapLocalVars = NULL; + gMapLocalVarsLength = 0; + } + + _square_reset(); + _map_place_dude_and_mouse(); + tileWindowRefresh(); +} + +// 0x482A68 +int mapLoadByName(char* fileName) +{ + int rc; + + strupr(fileName); + + rc = -1; + + char* extension = strstr(fileName, ".MAP"); + if (extension != NULL) { + strcpy(extension, ".SAV"); + + const char* filePath = mapBuildPath(fileName); + + File* stream = fileOpen(filePath, "rb"); + + strcpy(extension, ".MAP"); + + if (stream != NULL) { + fileClose(stream); + rc = mapLoadSaved(fileName); + worldmapStartMapMusic(); + } + } + + if (rc == -1) { + const char* filePath = mapBuildPath(fileName); + File* stream = fileOpen(filePath, "rb"); + if (stream != NULL) { + rc = mapLoad(stream); + fileClose(stream); + } + + if (rc == 0) { + strcpy(gMapHeader.name, fileName); + gDude->data.critter.combat.whoHitMe = NULL; + } + } + + return rc; +} + +// 0x482B34 +int mapLoadById(int map) +{ + scriptSetFixedParam(gMapSid, map); + + char name[16]; + if (mapGetFileName(map, name) == -1) { + return -1; + } + + _wmMapIdx = map; + + int rc = mapLoadByName(name); + + worldmapStartMapMusic(); + + return rc; +} + +// 0x482B74 +int mapLoad(File* stream) +{ + _map_save_in_game(true); + backgroundSoundLoad("wind2", 12, 13, 16); + isoDisable(); + _partyMemberPrepLoad(); + _gmouse_disable_scrolling(); + + int savedMouseCursorId = gameMouseGetCursor(); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); + fileSetReadProgressHandler(gameMouseRefresh, 32768); + tileDisable(); + + int rc = 0; + + windowFill(gIsoWindow, 0, 0, _scr_size.right - _scr_size.left + 1, _scr_size.bottom - _scr_size.top - 99, _colorTable[0]); + windowRefresh(gIsoWindow); + _anim_stop(); + scriptsDisable(); + + gMapSid = -1; + + const char* error = NULL; + + error = "Invalid file handle"; + if (stream == NULL) { + goto err; + } + + error = "Error reading header"; + if (mapHeaderRead(&gMapHeader, stream) != 0) { + goto err; + } + + error = "Invalid map version"; + if (gMapHeader.version != 19 && gMapHeader.version != 20) { + goto err; + } + + if (gEnteringElevation == -1) { + gEnteringElevation = gMapHeader.enteringElevation; + gEnteringTile = gMapHeader.enteringTile; + gEnteringRotation = gMapHeader.enteringRotation; + } + + _obj_remove_all(); + + if (gMapHeader.globalVariablesCount < 0) { + gMapHeader.globalVariablesCount = 0; + } + + if (gMapHeader.localVariablesCount < 0) { + gMapHeader.localVariablesCount = 0; + } + + error = "Error loading global vars"; + mapGlobalVariablesFree(); + + if (gMapHeader.globalVariablesCount != 0) { + gMapGlobalVars = internal_malloc(sizeof(*gMapGlobalVars) * gMapHeader.globalVariablesCount); + if (gMapGlobalVars == NULL) { + goto err; + } + + gMapGlobalVarsLength = gMapHeader.globalVariablesCount; + } + + if (fileReadInt32List(stream, gMapGlobalVars, gMapGlobalVarsLength) != 0) { + goto err; + } + + error = "Error loading local vars"; + mapLocalVariablesFree(); + + if (gMapHeader.localVariablesCount != 0) { + gMapLocalVars = internal_malloc(sizeof(*gMapLocalVars) * gMapHeader.localVariablesCount); + if (gMapLocalVars == NULL) { + goto err; + } + + gMapLocalVarsLength = gMapHeader.localVariablesCount; + } + + if (fileReadInt32List(stream, gMapLocalVars, gMapLocalVarsLength) != 0) { + goto err; + } + + if (_square_load(stream, gMapHeader.flags) != 0) { + goto err; + } + + error = "Error reading scripts"; + if (scriptLoadAll(stream) != 0) { + goto err; + } + + error = "Error reading objects"; + if (objectLoadAll(stream) != 0) { + goto err; + } + + if ((gMapHeader.flags & 1) == 0) { + _map_fix_critter_combat_data(); + } + + error = "Error setting map elevation"; + if (mapSetElevation(gEnteringElevation) != 0) { + goto err; + } + + error = "Error setting tile center"; + if (tileSetCenter(gEnteringTile, TILE_SET_CENTER_FLAG_0x02) != 0) { + goto err; + } + + lightSetLightLevel(LIGHT_LEVEL_MAX, false); + objectSetLocation(gDude, gCenterTile, gElevation, NULL); + objectSetRotation(gDude, gEnteringRotation, NULL); + gMapHeader.field_34 = mapGetIndexByFileName(gMapHeader.name); + + if ((gMapHeader.flags & 1) == 0) { + char path[MAX_PATH]; + sprintf(path, "maps\\%s", gMapHeader.name); + + char* extension = strstr(path, ".MAP"); + if (extension == NULL) { + extension = strstr(path, ".map"); + } + + if (extension != NULL) { + *extension = '\0'; + } + + strcat(path, ".GAM"); + globalVarsRead(path, "MAP_GLOBAL_VARS:", &gMapGlobalVarsLength, &gMapGlobalVars); + gMapHeader.globalVariablesCount = gMapGlobalVarsLength; + } + + scriptsEnable(); + + if (gMapHeader.scriptIndex > 0) { + error = "Error creating new map script"; + if (scriptAdd(&gMapSid, SCRIPT_TYPE_SYSTEM) == -1) { + goto err; + } + + Object* object; + int fid = buildFid(5, 12, 0, 0, 0); + objectCreateWithFidPid(&object, fid, -1); + object->flags |= (OBJECT_FLAG_0x20000000 | OBJECT_TEMPORARY | OBJECT_HIDDEN); + objectSetLocation(object, 1, 0, NULL); + object->sid = gMapSid; + scriptSetFixedParam(gMapSid, (gMapHeader.flags & 1) == 0); + + Script* script; + scriptGetScript(gMapSid, &script); + script->field_14 = gMapHeader.scriptIndex - 1; + script->flags |= SCRIPT_FLAG_0x08; + object->id = scriptsNewObjectId(); + script->field_1C = object->id; + script->owner = object; + _scr_spatials_disable(); + scriptExecProc(gMapSid, SCRIPT_PROC_MAP_ENTER); + _scr_spatials_enable(); + + error = "Error Setting up random encounter"; + if (worldmapSetupRandomEncounter() == -1) { + goto err; + } + } + + error = NULL; + +err: + + if (error != NULL) { + char message[100]; // TODO: Size is probably wrong. + sprintf(message, "%s while loading map.", error); + debugPrint(message); + _map_new_map(); + rc = -1; + } else { + _obj_preload_art_cache(gMapHeader.flags); + } + + _partyMemberRecoverLoad(); + _intface_show(); + _proto_dude_update_gender(); + _map_place_dude_and_mouse(); + fileSetReadProgressHandler(NULL, 0); + isoEnable(); + _gmouse_disable_scrolling(); + gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET); + + if (scriptsExecStartProc() == -1) { + debugPrint("\n Error: scr_load_all_scripts failed!"); + } + + scriptsExecMapEnterProc(); + scriptsExecMapUpdateProc(); + tileEnable(); + + if (gMapTransition.map > 0) { + if (gMapTransition.rotation >= 0) { + objectSetRotation(gDude, gMapTransition.rotation, NULL); + } + } else { + tileWindowRefresh(); + } + + gameTimeScheduleUpdateEvent(); + + if (_gsound_sfx_q_start() == -1) { + rc = -1; + } + + _wmMapMarkVisited(gMapHeader.field_34); + _wmMapMarkMapEntranceState(gMapHeader.field_34, gElevation, 1); + + if (_wmCheckGameAreaEvents() != 0) { + rc = -1; + } + + fileSetReadProgressHandler(NULL, 0); + + if (gameUiIsDisabled() == 0) { + _gmouse_enable_scrolling(); + } + + gameMouseSetCursor(savedMouseCursorId); + + gEnteringElevation = -1; + gEnteringTile = -1; + gEnteringRotation = -1; + + gameMovieFadeOut(); + + gMapHeader.version = 20; + + return rc; +} + +// 0x483188 +int mapLoadSaved(char* fileName) +{ + debugPrint("\nMAP: Loading SAVED map."); + + char mapName[16]; // TODO: Size is probably wrong. + _strmfe(mapName, fileName, "SAV"); + + int rc = mapLoadByName(mapName); + + if (gameTimeGetTime() >= gMapHeader.field_38) { + if (((gameTimeGetTime() - gMapHeader.field_38) / 36000) >= 24) { + objectUnjamAll(); + } + + if (_map_age_dead_critters() == -1) { + debugPrint("\nError: Critter aging failed on map load!"); + return -1; + } + } + + if (!_wmMapIsSaveable()) { + debugPrint("\nDestroying RANDOM encounter map."); + + char v15[16]; + strcpy(v15, gMapHeader.name); + + _strmfe(gMapHeader.name, v15, "SAV"); + + _MapDirEraseFile_("MAPS\\", gMapHeader.name); + + strcpy(gMapHeader.name, v15); + } + + return rc; +} + +// 0x48328C +int _map_age_dead_critters() +{ + if (!_wmMapDeadBodiesAge()) { + return 0; + } + + int v4 = (gameTimeGetTime() - gMapHeader.field_38) / 36000; + if (v4 == 0) { + return 0; + } + + Object* obj = objectFindFirst(); + while (obj != NULL) { + if (obj->pid >> 24 == OBJ_TYPE_CRITTER + && obj != gDude + && !objectIsPartyMember(obj) + && !critterIsDead(obj)) { + obj->data.critter.combat.maneuver &= 0x04; + if (critterGetKillType(obj) != KILL_TYPE_ROBOT && _critter_flag_check(obj->pid, 512) == 0) { + _critter_heal_hours(obj, v4); + } + } + obj = objectFindNext(); + } + + int v20; + if (v4 <= 336) { + if (v4 > 144) { + v20 = 1; + } else { + v20 = 0; + } + } else { + v20 = 2; + } + + if (v20 == 0) { + return 0; + } + + int capacity = 100; + int count = 0; + Object** objects = internal_malloc(sizeof(*objects) * capacity); + + obj = objectFindFirst(); + while (obj != NULL) { + int type = obj->pid >> 24; + if (type == OBJ_TYPE_CRITTER) { + if (obj != gDude && critterIsDead(obj)) { + if (critterGetKillType(obj) != KILL_TYPE_ROBOT && _critter_flag_check(obj->pid, 512) == 0) { + objects[count++] = obj; + + if (count >= capacity) { + capacity *= 2; + objects = internal_realloc(objects, sizeof(*objects) * capacity); + if (objects == NULL) { + debugPrint("\nError: Out of Memory!"); + return -1; + } + } + } + } + } else if (v20 == 2 && type == OBJ_TYPE_MISC && obj->pid == 0x500000B) { + objects[count++] = obj; + if (count >= capacity) { + capacity *= 2; + objects = internal_realloc(objects, sizeof(*objects) * capacity); + if (objects == NULL) { + debugPrint("\nError: Out of Memory!"); + return -1; + } + } + } + obj = objectFindNext(); + } + + int rc = 0; + for (int index = 0; index < count; index++) { + Object* obj = objects[index]; + if (obj->pid >> 24 == OBJ_TYPE_CRITTER) { + if (_critter_flag_check(obj->pid, 64) == 0) { + _item_drop_all(obj, obj->tile); + } + + Object* a1; + if (objectCreateWithPid(&a1, 0x5000004) == -1) { + rc = -1; + break; + } + + objectSetLocation(a1, obj->tile, obj->elevation, NULL); + + Proto* proto; + protoGetProto(obj->pid, &proto); + + int frame = randomBetween(0, 3); + if ((proto->critter.flags & 0x800)) { + frame += 6; + } else { + if (critterGetKillType(obj) != KILL_TYPE_RAT + && critterGetKillType(obj) != KILL_TYPE_MANTIS) { + frame += 3; + } + } + + objectSetFrame(a1, frame, NULL); + } + + reg_anim_clear(obj); + objectDestroy(obj, NULL); + } + + internal_free(objects); + + return rc; +} + +// 0x48358C +int _map_target_load_area() +{ + int city = -1; + if (_wmMatchAreaContainingMapIdx(gMapHeader.field_34, &city) == -1) { + city = -1; + } + return city; +} + +// 0x4835B4 +int mapSetTransition(MapTransition* transition) +{ + if (transition == NULL) { + return -1; + } + + memcpy(&gMapTransition, transition, sizeof(gMapTransition)); + + if (gMapTransition.map == 0) { + gMapTransition.map = -2; + } + + if (isInCombat()) { + _game_user_wants_to_quit = 1; + } + + return 0; +} + +// 0x4835F8 +int mapHandleTransition() +{ + if (gMapTransition.map == 0) { + return 0; + } + + gameMouseObjectsHide(); + + gameMouseSetCursor(MOUSE_CURSOR_NONE); + + if (gMapTransition.map == -1) { + if (!isInCombat()) { + _anim_stop(); + _wmTownMap(); + memset(&gMapTransition, 0, sizeof(gMapTransition)); + } + } else if (gMapTransition.map == -2) { + if (!isInCombat()) { + _anim_stop(); + _wmWorldMap(); + memset(&gMapTransition, 0, sizeof(gMapTransition)); + } + } else { + if (!isInCombat()) { + if (gMapTransition.map != gMapHeader.field_34 || gElevation == gMapTransition.elevation) { + mapLoadById(gMapTransition.map); + } + + if (gMapTransition.tile != -1 && gMapTransition.tile != 0 + && gMapHeader.field_34 != MAP_MODOC_BEDNBREAKFAST && gMapHeader.field_34 != MAP_THE_SQUAT_A + && elevationIsValid(gMapTransition.elevation)) { + objectSetLocation(gDude, gMapTransition.tile, gMapTransition.elevation, NULL); + mapSetElevation(gMapTransition.elevation); + objectSetRotation(gDude, gMapTransition.rotation, NULL); + } + + if (tileSetCenter(gDude->tile, TILE_SET_CENTER_FLAG_0x01) == -1) { + debugPrint("\nError: map: attempt to center out-of-bounds!"); + } + + memset(&gMapTransition, 0, sizeof(gMapTransition)); + + int city; + _wmMatchAreaContainingMapIdx(gMapHeader.field_34, &city); + if (_wmTeleportToArea(city) == -1) { + debugPrint("\nError: couldn't make jump on worldmap for map jump!"); + } + } + } + + return 0; +} + +// 0x483784 +void _map_fix_critter_combat_data() +{ + for (Object* object = objectFindFirst(); object != NULL; object = objectFindNext()) { + if (object->pid == -1) { + continue; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + continue; + } + + if (object->data.critter.combat.whoHitMeCid == -1) { + object->data.critter.combat.whoHitMe = NULL; + } + } +} + +// map_save +// 0x483850 +int _map_save() +{ + char temp[80]; + temp[0] = '\0'; + + char* masterPatchesPath; + if (configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { + strcat(temp, masterPatchesPath); + mkdir(temp); + + strcat(temp, "\\MAPS"); + mkdir(temp); + } + + int rc = -1; + if (gMapHeader.name[0] != '\0') { + char* mapFileName = mapBuildPath(gMapHeader.name); + File* stream = fileOpen(mapFileName, "wb"); + if (stream != NULL) { + rc = _map_save_file(stream); + fileClose(stream); + } else { + sprintf(temp, "Unable to open %s to write!", gMapHeader.name); + debugPrint(temp); + } + + if (rc == 0) { + sprintf(temp, "%s saved.", gMapHeader.name); + debugPrint(temp); + } + } else { + debugPrint("\nError: map_save: map header corrupt!"); + } + + return rc; +} + +// 0x483980 +int _map_save_file(File* stream) +{ + if (stream == NULL) { + return -1; + } + + scriptsDisable(); + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + int tile; + for (tile = 0; tile < SQUARE_GRID_SIZE; tile++) { + int fid; + + fid = buildFid(4, _square[elevation]->field_0[tile] & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + break; + } + + fid = buildFid(4, (_square[elevation]->field_0[tile] >> 16) & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + break; + } + } + + if (tile == SQUARE_GRID_SIZE) { + Object* object = objectFindFirstAtElevation(elevation); + if (object != NULL) { + // TODO: Implementation is slightly different, check in debugger. + while (object != NULL && (object->flags & OBJECT_TEMPORARY)) { + object = objectFindNextAtElevation(); + } + + if (object != NULL) { + gMapHeader.flags &= ~_map_data_elev_flags[elevation]; + } else { + gMapHeader.flags |= _map_data_elev_flags[elevation]; + } + } else { + gMapHeader.flags |= _map_data_elev_flags[elevation]; + } + } else { + gMapHeader.flags &= ~_map_data_elev_flags[elevation]; + } + } + + gMapHeader.localVariablesCount = gMapLocalVarsLength; + gMapHeader.globalVariablesCount = gMapGlobalVarsLength; + gMapHeader.darkness = 1; + + mapHeaderWrite(&gMapHeader, stream); + + if (gMapHeader.globalVariablesCount != 0) { + fileWriteInt32List(stream, gMapGlobalVars, gMapHeader.globalVariablesCount); + } + + if (gMapHeader.localVariablesCount != 0) { + fileWriteInt32List(stream, gMapLocalVars, gMapHeader.localVariablesCount); + } + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + if ((gMapHeader.flags & _map_data_elev_flags[elevation]) == 0) { + _db_fwriteLongCount(stream, _square[elevation]->field_0, SQUARE_GRID_SIZE); + } + } + + char err[80]; + + if (scriptSaveAll(stream) == -1) { + sprintf(err, "Error saving scripts in %s", gMapHeader.name); + // TODO: Incomplete. + // _win_msg(err, 80, 80, _colorTable[31744]); + } + + if (objectSaveAll(stream) == -1) { + sprintf(err, "Error saving objects in %s", gMapHeader.name); + // TODO: Incomplete. + // _win_msg(err, 80, 80, _colorTable[31744]); + } + + scriptsEnable(); + + return 0; +} + +// 0x483C98 +int _map_save_in_game(bool a1) +{ + if (gMapHeader.name[0] == '\0') { + return 0; + } + + _anim_stop(); + _partyMemberSaveProtos(); + + if (a1) { + _queue_leaving_map(); + _partyMemberPrepLoad(); + _partyMemberPrepItemSaveAll(); + scriptsExecMapExitProc(); + + if (gMapSid != -1) { + Script* script; + scriptGetScript(gMapSid, &script); + } + + gameTimeScheduleUpdateEvent(); + _obj_reset_roof(); + } + + gMapHeader.flags |= 0x01; + gMapHeader.field_38 = gameTimeGetTime(); + + char name[16]; + + if (a1 && !_wmMapIsSaveable()) { + debugPrint("\nNot saving RANDOM encounter map."); + + strcpy(name, gMapHeader.name); + _strmfe(gMapHeader.name, name, "SAV"); + _MapDirEraseFile_("MAPS\\", gMapHeader.name); + strcpy(gMapHeader.name, name); + } else { + debugPrint("\n Saving \".SAV\" map."); + + strcpy(name, gMapHeader.name); + _strmfe(gMapHeader.name, name, "SAV"); + if (_map_save() == -1) { + return -1; + } + + strcpy(gMapHeader.name, name); + + automapSaveCurrent(); + + if (a1) { + gMapHeader.name[0] = '\0'; + _obj_remove_all(); + _proto_remove_all(); + _square_reset(); + gameTimeScheduleUpdateEvent(); + } + } + + return 0; +} + +// 0x483E28 +void mapMakeMapsDirectory() +{ + char path[FILENAME_MAX]; + + char* masterPatchesPath; + if (configGetString(&gGameConfig, 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 +void isoWindowRefreshRect(Rect* rect) +{ + windowRefreshRect(gIsoWindow, rect); +} + +// 0x483EE4 +void isoWindowRefreshRectGame(Rect* rect) +{ + Rect clampedDirtyRect; + if (rectIntersection(rect, &gIsoWindowRect, &clampedDirtyRect) == -1) { + return; + } + + tileRenderFloorsInRect(&clampedDirtyRect, gElevation); + _grid_render(&clampedDirtyRect, gElevation); + _obj_render_pre_roof(&clampedDirtyRect, gElevation); + tileRenderRoofsInRect(&clampedDirtyRect, gElevation); + _obj_render_post_roof(&clampedDirtyRect, gElevation); +} + +// 0x483F44 +void isoWindowRefreshRectMapper(Rect* rect) +{ + Rect clampedDirtyRect; + if (rectIntersection(rect, &gIsoWindowRect, &clampedDirtyRect) == -1) { + return; + } + + bufferFill(gIsoWindowBuffer + clampedDirtyRect.top * (_scr_size.right - _scr_size.left + 1) + clampedDirtyRect.left, + clampedDirtyRect.right - clampedDirtyRect.left + 1, + clampedDirtyRect.bottom - clampedDirtyRect.top + 1, + _scr_size.right - _scr_size.left + 1, + 0); + tileRenderFloorsInRect(&clampedDirtyRect, gElevation); + _grid_render(&clampedDirtyRect, gElevation); + _obj_render_pre_roof(&clampedDirtyRect, gElevation); + tileRenderRoofsInRect(&clampedDirtyRect, gElevation); + _obj_render_post_roof(&clampedDirtyRect, gElevation); +} + +// 0x484038 +void mapGlobalVariablesFree() +{ + if (gMapGlobalVars != NULL) { + internal_free(gMapGlobalVars); + gMapGlobalVars = NULL; + gMapGlobalVarsLength = 0; + } +} + +// 0x4840D4 +void mapLocalVariablesFree() +{ + if (gMapLocalVars != NULL) { + internal_free(gMapLocalVars); + gMapLocalVars = NULL; + gMapLocalVarsLength = 0; + } +} + +// 0x48411C +void _map_place_dude_and_mouse() +{ + _obj_clear_seen(); + + if (gDude != NULL) { + if (((gDude->fid & 0xFF0000) >> 16) != 0) { + objectSetFrame(gDude, 0, 0); + gDude->fid = buildFid(1, gDude->fid & 0xFFF, ANIM_STAND, (gDude->fid & 0xF000) >> 12, gDude->rotation + 1); + } + + if (gDude->tile == -1) { + objectSetLocation(gDude, gCenterTile, gElevation, NULL); + objectSetRotation(gDude, gMapHeader.enteringRotation, 0); + } + + objectSetLight(gDude, 4, 0x10000, 0); + gDude->flags |= OBJECT_TEMPORARY; + + _dude_stand(gDude, gDude->rotation, gDude->fid); + _partyMemberSyncPosition(); + } + + gameMouseResetBouncingCursorFid(); + gameMouseObjectsShow(); +} + +// 0x484210 +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 = ((buildFid(4, 1, 0, 0, 0) & 0xFFF | (((fid >> 16) & 0xF000) >> 12)) << 16) | (fid & 0xFFFF); + + fid = *p; + int v3 = (fid & 0xF000) >> 12; + int v4 = (buildFid(4, 1, 0, 0, 0) & 0xFFF) | v3; + + fid &= ~0xFFFF; + + *p = v4 | ((fid >> 16) << 16); + + p++; + } + } + } +} + +// 0x48431C +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_freadIntCount(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 +int mapHeaderWrite(MapHeader* ptr, File* stream) +{ + if (fileWriteInt32(stream, ptr->version) == -1) return -1; + if (fileWriteFixedLengthString(stream, ptr->name, 16) == -1) return -1; + if (fileWriteInt32(stream, ptr->enteringTile) == -1) return -1; + if (fileWriteInt32(stream, ptr->enteringElevation) == -1) return -1; + if (fileWriteInt32(stream, ptr->enteringRotation) == -1) return -1; + if (fileWriteInt32(stream, ptr->localVariablesCount) == -1) return -1; + if (fileWriteInt32(stream, ptr->scriptIndex) == -1) return -1; + if (fileWriteInt32(stream, ptr->flags) == -1) return -1; + if (fileWriteInt32(stream, ptr->darkness) == -1) return -1; + if (fileWriteInt32(stream, ptr->globalVariablesCount) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_34) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_38) == -1) return -1; + if (fileWriteInt32List(stream, ptr->field_3C, 44) == -1) return -1; + + return 0; +} + +// 0x4844B4 +int mapHeaderRead(MapHeader* ptr, File* stream) +{ + if (fileReadInt32(stream, &(ptr->version)) == -1) return -1; + if (fileReadFixedLengthString(stream, ptr->name, 16) == -1) return -1; + if (fileReadInt32(stream, &(ptr->enteringTile)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->enteringElevation)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->enteringRotation)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->localVariablesCount)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->scriptIndex)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->flags)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->darkness)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->globalVariablesCount)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->field_34)) == -1) return -1; + if (fileReadInt32(stream, &(ptr->field_38)) == -1) return -1; + if (fileReadInt32List(stream, ptr->field_3C, 44) == -1) return -1; + + return 0; +} diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..a544ac1 --- /dev/null +++ b/src/map.h @@ -0,0 +1,144 @@ +#ifndef MAP_H +#define MAP_H + +#include "combat_defs.h" +#include "db.h" +#include "geometry.h" +#include "map_defs.h" +#include "message.h" + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 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; + int field_38; + 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 IsoWindowRefreshProc* _map_scroll_refresh; +extern const int _map_data_elev_flags[ELEVATION_COUNT]; +extern unsigned int gIsoWindowScrollTimestamp; +extern bool gIsoEnabled; +extern int gEnteringElevation; +extern int gEnteringTile; +extern int gEnteringRotation; +extern int gMapSid; +extern int* gMapLocalVars; +extern int* gMapGlobalVars; +extern int gMapLocalVarsLength; +extern int gMapGlobalVarsLength; +extern int gElevation; +extern char* _errMapName; +extern int _wmMapIdx; + +extern TileData _square_data[ELEVATION_COUNT]; +extern MapTransition gMapTransition; +extern Rect gIsoWindowRect; +extern MessageList gMapMessageList; +extern unsigned char* gIsoWindowBuffer; +extern MapHeader gMapHeader; +extern TileData* _square[ELEVATION_COUNT]; +extern int gIsoWindow; +extern char _scratchStr[40]; +extern char _map_path[MAX_PATH]; + +int isoInit(); +void isoReset(); +void isoExit(); +void _map_init(); +void _map_exit(); +void isoEnable(); +bool isoDisable(); +bool isoIsDisabled(); +int mapSetElevation(int elevation); +int mapSetGlobalVar(int var, int value); +int mapGetGlobalVar(int var); +int mapSetLocalVar(int var, int value); +int mapGetLocalVar(int var); +int _map_malloc_local_var(int a1); +void mapSetStart(int a1, int a2, int a3); +char* mapGetName(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* mapGetCityName(int map_num); +char* _map_get_description_idx_(int map_index); +int mapGetCurrentMap(); +int mapScroll(int dx, int dy); +char* mapBuildPath(char* name); +int mapSetEnteringLocation(int a1, int a2, int a3); +void _map_new_map(); +int mapLoadByName(char* fileName); +int mapLoadById(int map_index); +int mapLoad(File* stream); +int mapLoadSaved(char* fileName); +int _map_age_dead_critters(); +int _map_target_load_area(); +int mapSetTransition(MapTransition* transition); +int mapHandleTransition(); +void _map_fix_critter_combat_data(); +int _map_save(); +int _map_save_file(File* stream); +int _map_save_in_game(bool a1); +void mapMakeMapsDirectory(); +void isoWindowRefreshRect(Rect* rect); +void isoWindowRefreshRectGame(Rect* rect); +void isoWindowRefreshRectMapper(Rect* rect); +void mapGlobalVariablesFree(); +void mapLocalVariablesFree(); +void _map_place_dude_and_mouse(); +void _square_reset(); +int _square_load(File* stream, int a2); +int mapHeaderWrite(MapHeader* ptr, File* stream); +int mapHeaderRead(MapHeader* ptr, File* stream); + +#endif /* MAP_H */ diff --git a/src/map_defs.h b/src/map_defs.h new file mode 100644 index 0000000..87a2c95 --- /dev/null +++ b/src/map_defs.h @@ -0,0 +1,31 @@ +#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 */ diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..9673eb3 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,186 @@ +#include "memory.h" + +#include "debug.h" + +#include +#include +#include + +// 0x51DED0 +MallocProc* gMallocProc = memoryBlockMallocImpl; + +// 0x51DED4 +ReallocProc* gReallocProc = memoryBlockReallocImpl; + +// 0x51DED8 +FreeProc* gFreeProc = memoryBlockFreeImpl; + +// 0x51DEDC +int gMemoryBlocksCurrentCount = 0; + +// 0x51DEE0 +int gMemoryBlockMaximumCount = 0; + +// 0x51DEE4 +int gMemoryBlocksCurrentSize = 0; + +// 0x51DEE8 +int gMemoryBlocksMaximumSize = 0; + +// 0x4C5A80 +char* internal_strdup(const char* string) +{ + char* copy = NULL; + if (string != NULL) { + copy = gMallocProc(strlen(string) + 1); + strcpy(copy, string); + } + return copy; +} + +// 0x4C5AD0 +void* internal_malloc(size_t size) +{ + return gMallocProc(size); +} + +// 0x4C5AD8 +void* memoryBlockMallocImpl(size_t size) +{ + void* ptr = NULL; + + if (size != 0) { + size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter); + + unsigned char* block = malloc(size); + if (block != NULL) { + MemoryBlockHeader* header = (MemoryBlockHeader*)block; + header->size = size; + header->guard = MEMORY_BLOCK_HEADER_GUARD; + + MemoryBlockFooter* footer = (MemoryBlockFooter*)(block + size - sizeof(MemoryBlockFooter)); + footer->guard = MEMORY_BLOCK_FOOTER_GUARD; + + gMemoryBlocksCurrentCount++; + if (gMemoryBlocksCurrentCount > gMemoryBlockMaximumCount) { + gMemoryBlockMaximumCount = gMemoryBlocksCurrentCount; + } + + gMemoryBlocksCurrentSize += size; + if (gMemoryBlocksCurrentSize > gMemoryBlocksMaximumSize) { + gMemoryBlocksMaximumSize = gMemoryBlocksCurrentSize; + } + + ptr = block + sizeof(MemoryBlockHeader); + } + } + + return ptr; +} + +// 0x4C5B50 +void* internal_realloc(void* ptr, size_t size) +{ + return gReallocProc(ptr, size); +} + +// 0x4C5B58 +void* memoryBlockReallocImpl(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; + + gMemoryBlocksCurrentSize -= oldSize; + + memoryBlockValidate(block); + + if (size != 0) { + size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter); + } + + unsigned char* newBlock = realloc(block, size); + if (newBlock != NULL) { + MemoryBlockHeader* newHeader = (MemoryBlockHeader*)newBlock; + newHeader->size = size; + newHeader->guard = MEMORY_BLOCK_HEADER_GUARD; + + MemoryBlockFooter* newFooter = (MemoryBlockFooter*)(newBlock + size - sizeof(MemoryBlockFooter)); + newFooter->guard = MEMORY_BLOCK_FOOTER_GUARD; + + gMemoryBlocksCurrentSize += size; + if (gMemoryBlocksCurrentSize > gMemoryBlocksMaximumSize) { + gMemoryBlocksMaximumSize = gMemoryBlocksCurrentSize; + } + + ptr = newBlock + sizeof(MemoryBlockHeader); + } else { + if (size != 0) { + gMemoryBlocksCurrentSize += oldSize; + + debugPrint("%s,%u: ", __FILE__, __LINE__); // "Memory.c", 195 + debugPrint("Realloc failure.\n"); + } else { + gMemoryBlocksCurrentCount--; + } + ptr = NULL; + } + } else { + ptr = gMallocProc(size); + } + + return ptr; +} + +// 0x4C5C24 +void internal_free(void* ptr) +{ + gFreeProc(ptr); +} + +// 0x4C5C2C +void memoryBlockFreeImpl(void* ptr) +{ + if (ptr != NULL) { + void* block = (unsigned char*)ptr - sizeof(MemoryBlockHeader); + MemoryBlockHeader* header = (MemoryBlockHeader*)block; + + memoryBlockValidate(block); + + gMemoryBlocksCurrentSize -= header->size; + gMemoryBlocksCurrentCount--; + + free(block); + } +} + +// NOTE: Not used. +// +// 0x4C5C5C +void memoryBlockPrintStats() +{ + if (gMallocProc == memoryBlockMallocImpl) { + debugPrint("Current memory allocated: %6d blocks, %9u bytes total\n", gMemoryBlocksCurrentCount, gMemoryBlocksCurrentSize); + debugPrint("Max memory allocated: %6d blocks, %9u bytes total\n", gMemoryBlockMaximumCount, gMemoryBlocksMaximumSize); + } +} + +// Validates integrity of the memory block. +// +// [block] is a pointer to the the memory block itself, not it's data. +// +// 0x4C5CE4 +void memoryBlockValidate(void* block) +{ + MemoryBlockHeader* header = (MemoryBlockHeader*)block; + if (header->guard != MEMORY_BLOCK_HEADER_GUARD) { + debugPrint("Memory header stomped.\n"); + } + + MemoryBlockFooter* footer = (MemoryBlockFooter*)((unsigned char*)block + header->size - sizeof(MemoryBlockFooter)); + if (footer->guard != MEMORY_BLOCK_FOOTER_GUARD) { + debugPrint("Memory footer stomped.\n"); + } +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..c6e0de4 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,45 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include "memory_defs.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; + +extern MallocProc* gMallocProc; +extern ReallocProc* gReallocProc; +extern FreeProc* gFreeProc; +extern int gMemoryBlocksCurrentCount; +extern int gMemoryBlockMaximumCount; +extern int gMemoryBlocksCurrentSize; +extern int gMemoryBlocksMaximumSize; + +char* internal_strdup(const char* string); +void* internal_malloc(size_t size); +void* memoryBlockMallocImpl(size_t size); +void* internal_realloc(void* ptr, size_t size); +void* memoryBlockReallocImpl(void* ptr, size_t size); +void internal_free(void* ptr); +void memoryBlockFreeImpl(void* ptr); +void memoryBlockPrintStats(); +void memoryBlockValidate(void* block); + +#endif /* MEMORY_H */ diff --git a/src/memory_defs.h b/src/memory_defs.h new file mode 100644 index 0000000..3ef090f --- /dev/null +++ b/src/memory_defs.h @@ -0,0 +1,10 @@ +#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 */ diff --git a/src/memory_manager.c b/src/memory_manager.c new file mode 100644 index 0000000..710ff21 --- /dev/null +++ b/src/memory_manager.c @@ -0,0 +1,137 @@ +#include "memory_manager.h" + +#include +#include +#include +#include + +// 0x519588 +MemoryManagerPrintErrorProc* gMemoryManagerPrintErrorProc = memoryManagerDefaultPrintErrorImpl; + +// 0x51958C +MallocProc* gMemoryManagerMallocProc = memoryManagerDefaultMallocImpl; + +// 0x519590 +ReallocProc* gMemoryManagerReallocProc = memoryManagerDefaultReallocImpl; + +// 0x519594 +FreeProc* gMemoryManagerFreeProc = memoryManagerDefaultFreeImpl; + +// 0x631F7C +char gMemoryManagerLastError[256]; + +// 0x4845B0 +void memoryManagerDefaultPrintErrorImpl(const char* string) +{ + printf("%s", string); +} + +// 0x4845C8 +int memoryManagerPrintError(const char* format, ...) +{ + int length = 0; + + if (gMemoryManagerPrintErrorProc != NULL) { + va_list args; + va_start(args, format); + length = vsprintf(gMemoryManagerLastError, format, args); + va_end(args); + + gMemoryManagerPrintErrorProc(gMemoryManagerLastError); + } + + return length; +} + +// 0x484610 +__declspec(noreturn) void memoryManagerFatalAllocationError(const char* func, size_t size, const char* file, int line) +{ + memoryManagerPrintError("%s: Error allocating block of size %ld (%x), %s %d\n", func, size, size, file, line); + exit(1); +} + +// 0x48462C +void* memoryManagerDefaultMallocImpl(size_t size) +{ + return malloc(size); +} + +// 0x484634 +void* memoryManagerDefaultReallocImpl(void* ptr, size_t size) +{ + return realloc(ptr, size); +} + +// 0x48463C +void memoryManagerDefaultFreeImpl(void* ptr) +{ + free(ptr); +} + +// 0x484644 +void memoryManagerSetProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) +{ + gMemoryManagerMallocProc = mallocProc; + gMemoryManagerReallocProc = reallocProc; + gMemoryManagerFreeProc = freeProc; +} + +// 0x484660 +void* internal_malloc_safe(size_t size, const char* file, int line) +{ + void* ptr = gMemoryManagerMallocProc(size); + if (ptr == NULL) { + memoryManagerFatalAllocationError("malloc", size, file, line); + } + + return ptr; +} + +// 0x4846B4 +void* internal_realloc_safe(void* ptr, size_t size, const char* file, int line) +{ + ptr = gMemoryManagerReallocProc(ptr, size); + if (ptr == NULL) { + memoryManagerFatalAllocationError("realloc", size, file, line); + } + + return ptr; +} + +// 0x484688 +void internal_free_safe(void* ptr, const char* file, int line) +{ + if (ptr == NULL) { + memoryManagerPrintError("free: free of a null ptr, %s %d\n", file, line); + exit(1); + } + + gMemoryManagerFreeProc(ptr); +} + +// 0x4846D8 +void* internal_calloc_safe(int count, int size, const char* file, int line) +{ + void* ptr = gMemoryManagerMallocProc(count * size); + if (ptr == NULL) { + memoryManagerFatalAllocationError("calloc", size, file, line); + } + + memset(ptr, 0, count * size); + + return ptr; +} + +// 0x484710 +char* strdup_safe(const char* string, const char* file, int line) +{ + size_t size = strlen(string) + 1; + char* copy = gMemoryManagerMallocProc(size); + if (copy == NULL) { + memoryManagerFatalAllocationError("strdup", size, file, line); + } + + strcpy(copy, string); + + return copy; +} diff --git a/src/memory_manager.h b/src/memory_manager.h new file mode 100644 index 0000000..10e3c5b --- /dev/null +++ b/src/memory_manager.h @@ -0,0 +1,29 @@ +#ifndef MEMORY_MANAGER_H +#define MEMORY_MANAGER_H + +#include "memory_defs.h" + +typedef void(MemoryManagerPrintErrorProc)(const char* string); + +extern MemoryManagerPrintErrorProc* gMemoryManagerPrintErrorProc; +extern MallocProc* gMemoryManagerMallocProc; +extern ReallocProc* gMemoryManagerReallocProc; +extern FreeProc* gMemoryManagerFreeProc; +extern char gMemoryManagerLastError[256]; + +extern char gMemoryManagerLastError[256]; + +void memoryManagerDefaultPrintErrorImpl(const char* string); +int memoryManagerPrintError(const char* format, ...); +__declspec(noreturn) void memoryManagerFatalAllocationError(const char* func, size_t size, const char* file, int line); +void* memoryManagerDefaultMallocImpl(size_t size); +void* memoryManagerDefaultReallocImpl(void* ptr, size_t size); +void memoryManagerDefaultFreeImpl(void* ptr); +void memoryManagerSetProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); +void* internal_malloc_safe(size_t size, const char* file, int line); +void* internal_realloc_safe(void* ptr, size_t size, const char* file, int line); +void internal_free_safe(void* ptr, const char* file, int line); +void* internal_calloc_safe(int count, int size, const char* file, int line); +char* strdup_safe(const char* string, const char* file, int line); + +#endif /* MEMORY_MANAGER_H */ diff --git a/src/message.c b/src/message.c new file mode 100644 index 0000000..f842472 --- /dev/null +++ b/src/message.c @@ -0,0 +1,554 @@ +#include "message.h" + +#include "debug.h" +#include "game_config.h" +#include "memory.h" +#include "random.h" + +#include +#include +#include +#include + +// 0x50B79C +char _Error_1[] = "Error"; + +// 0x50B960 +const char* gBadwordsReplacements = "!@#$%&*@#*!&$%#&%#*%!$&%@*$@&"; + +// 0x519598 +char** gBadwords = NULL; + +// 0x51959C +int gBadwordsCount = 0; + +// 0x5195A0 +int* gBadwordsLengths = NULL; + +// Default text for getmsg when no entry is found. +// +// 0x5195A4 +char* _message_error_str = _Error_1; + +// Temporary message list item text used during filtering badwords. +// +// 0x63207C +char _bad_copy[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE]; + +// 0x484770 +int badwordsInit() +{ + File* stream = fileOpen("data\\badwords.txt", "rt"); + if (stream == NULL) { + return -1; + } + + char word[BADWORD_LENGTH_MAX]; + + gBadwordsCount = 0; + while (fileReadString(word, BADWORD_LENGTH_MAX - 1, stream)) { + gBadwordsCount++; + } + + gBadwords = internal_malloc(sizeof(*gBadwords) * gBadwordsCount); + if (gBadwords == NULL) { + fileClose(stream); + return -1; + } + + gBadwordsLengths = internal_malloc(sizeof(*gBadwordsLengths) * gBadwordsCount); + if (gBadwordsLengths == NULL) { + internal_free(gBadwords); + fileClose(stream); + return -1; + } + + fileSeek(stream, 0, SEEK_SET); + + int index = 0; + for (; index < gBadwordsCount; index++) { + if (!fileReadString(word, BADWORD_LENGTH_MAX - 1, stream)) { + break; + } + + int len = strlen(word); + if (word[len - 1] == '\n') { + len--; + word[len] = '\0'; + } + + gBadwords[index] = internal_strdup(word); + if (gBadwords[index] == NULL) { + break; + } + + strupr(gBadwords[index]); + + gBadwordsLengths[index] = len; + } + + fileClose(stream); + + if (index != gBadwordsCount) { + for (; index > 0; index--) { + internal_free(gBadwords[index - 1]); + } + + internal_free(gBadwords); + internal_free(gBadwordsLengths); + + return -1; + } + + return 0; +} + +// 0x4848F0 +void badwordsExit() +{ + for (int index = 0; index < gBadwordsCount; index++) { + internal_free(gBadwords[index]); + } + + if (gBadwordsCount != 0) { + internal_free(gBadwords); + internal_free(gBadwordsLengths); + } + + gBadwordsCount = 0; +} + +// message_init +// 0x48494C +bool messageListInit(MessageList* messageList) +{ + if (messageList != NULL) { + messageList->entries_num = 0; + messageList->entries = NULL; + } + return true; +} + +// 0x484964 +bool messageListFree(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) { + internal_free(entry->audio); + } + + if (entry->text != NULL) { + internal_free(entry->text); + } + } + + messageList->entries_num = 0; + + if (messageList->entries != NULL) { + internal_free(messageList->entries); + messageList->entries = NULL; + } + + return true; +} + +// message_load +// 0x484AA4 +bool messageListLoad(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 (!configGetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { + return false; + } + + sprintf(localized_path, "%s\\%s\\%s", "text", language, path); + + file_ptr = fileOpen(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) { + debugPrint("\nError loading audio field.\n", localized_path); + goto err; + } + + if (_message_load_field(file_ptr, text) != 0) { + debugPrint("\nError loading text field.\n", localized_path); + goto err; + } + + if (!_message_parse_number(&(entry.num), num)) { + debugPrint("\nError parsing number.\n", localized_path); + goto err; + } + + if (!_message_add(messageList, &entry)) { + debugPrint("\nError adding message.\n", localized_path); + goto err; + } + } + + if (rc == 1) { + success = true; + } + +err: + + if (!success) { + debugPrint("Error loading message file %s at offset %x.", localized_path, fileTell(file_ptr)); + } + + fileClose(file_ptr); + + return success; +} + +// 0x484C30 +bool messageListGetItem(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 (!configGetString(&gGameConfig, 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) { + internal_free(existing_entry->audio); + } + + if (existing_entry->text != NULL) { + internal_free(existing_entry->text); + } + } else { + if (msg->entries != NULL) { + entries = internal_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 = internal_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 = internal_strdup(new_entry->audio); + if (existing_entry->audio == NULL) { + return false; + } + + existing_entry->text = internal_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 = fileReadChar(file); + if (ch == -1) { + return 1; + } + + if (ch == '}') { + debugPrint("\nError reading message file - mismatched delimiters.\n"); + return 2; + } + + if (ch == '{') { + break; + } + } + + while (1) { + ch = fileReadChar(file); + + if (ch == -1) { + debugPrint("\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) { + debugPrint("\nError reading message file - text exceeds limit.\n"); + return 4; + } + } + } + + return 0; +} + +// 0x48504C +char* getmsg(MessageList* msg, MessageListItem* entry, int num) +{ + entry->num = num; + + if (!messageListGetItem(msg, entry)) { + entry->text = _message_error_str; + debugPrint("\n ** String not found @ getmsg(), MESSAGE.C **\n"); + } + + return entry->text; +} + +// 0x485078 +bool messageListFilterBadwords(MessageList* messageList) +{ + if (messageList == NULL) { + return false; + } + + if (messageList->entries_num == 0) { + return true; + } + + if (gBadwordsCount == 0) { + return true; + } + + int languageFilter = 0; + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); + if (languageFilter != 1) { + return true; + } + + int replacementsCount = strlen(gBadwordsReplacements); + int replacementsIndex = randomBetween(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 < gBadwordsCount; 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, gBadwords[badwordIndex]); + if (substr == NULL) { + break; + } + + if (substr == _bad_copy || (!isalpha(substr[-1]) && !isalpha(substr[gBadwordsLengths[badwordIndex]]))) { + item->flags |= MESSAGE_LIST_ITEM_TEXT_FILTERED; + char* ptr = item->text + (substr - _bad_copy); + + for (int j = 0; j < gBadwordsLengths[badwordIndex]; j++) { + *ptr++ = gBadwordsReplacements[replacementsIndex++]; + if (replacementsIndex == replacementsCount) { + replacementsIndex = 0; + } + } + } + } + } + } + + return true; +} diff --git a/src/message.h b/src/message.h new file mode 100644 index 0000000..b763508 --- /dev/null +++ b/src/message.h @@ -0,0 +1,50 @@ +#ifndef MESSAGE_H +#define MESSAGE_H + +#include "db.h" + +#include + +#define BADWORD_LENGTH_MAX 80 + +#define MESSAGE_LIST_ITEM_TEXT_FILTERED 0x01 + +#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; + +extern char _Error_1[]; +extern const char* gBadwordsReplacements; + +extern char** gBadwords; +extern int gBadwordsCount; +extern int* gBadwordsLengths; +extern char* _message_error_str; + +extern char _bad_copy[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE]; + +int badwordsInit(); +void badwordsExit(); +bool messageListInit(MessageList* msg); +bool messageListFree(MessageList* msg); +bool messageListLoad(MessageList* msg, const char* path); +bool messageListGetItem(MessageList* msg, MessageListItem* entry); +bool _message_make_path(char* dest, const char* path); +bool _message_find(MessageList* msg, int num, int* out_index); +bool _message_add(MessageList* msg, MessageListItem* new_entry); +bool _message_parse_number(int* out_num, const char* str); +int _message_load_field(File* file, char* str); +char* getmsg(MessageList* msg, MessageListItem* entry, int num); +bool messageListFilterBadwords(MessageList* messageList); + +#endif /* MESSAGE_H */ diff --git a/src/mmx.c b/src/mmx.c new file mode 100644 index 0000000..205bffb --- /dev/null +++ b/src/mmx.c @@ -0,0 +1,68 @@ +#include "mmx.h" + +#include "core.h" + +#include + +// 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 (gMmxEnabled) { + // TODO: Blit with MMX. + gMmxEnabled = false; + mmxBlit(dest, destPitch, src, srcPitch, width, height); + gMmxEnabled = 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 (gMmxEnabled) { + // TODO: Blit with MMX. + gMmxEnabled = false; + mmxBlitTrans(dest, destPitch, src, srcPitch, width, height); + gMmxEnabled = 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; + } + } +} diff --git a/src/mmx.h b/src/mmx.h new file mode 100644 index 0000000..547ef7f --- /dev/null +++ b/src/mmx.h @@ -0,0 +1,10 @@ +#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 */ diff --git a/src/mouse_manager.c b/src/mouse_manager.c new file mode 100644 index 0000000..6ec0c45 --- /dev/null +++ b/src/mouse_manager.c @@ -0,0 +1,57 @@ +#include "mouse_manager.h" + +#include "core.h" + +// 0x5195A8 +char* (*_mouseNameMangler)(char*) = _defaultNameMangler; + +// 0x5195AC +int (*_rateCallback)() = _defaultRateCallback; + +// 0x5195B0 +int (*_currentTimeCallback)() = _defaultTimeCallback; + +// 0x5195B4 +int _curref = 1; + +// 0x485250 +char* _defaultNameMangler(char* a1) +{ + return a1; +} + +// 0x485254 +int _defaultRateCallback() +{ + return 1000; +} + +// 0x48525C +int _defaultTimeCallback() +{ + return _get_time(); +} + +// 0x485288 +void _mousemgrSetNameMangler(char* (*func)(char*)) +{ + _mouseNameMangler = func; +} + +// 0x48568C +void _initMousemgr() +{ + mouseSetSensitivity(1.0); +} + +// 0x4865C4 +void _mouseHide() +{ + mouseHideCursor(); +} + +// 0x4865CC +void _mouseShow() +{ + mouseShowCursor(); +} diff --git a/src/mouse_manager.h b/src/mouse_manager.h new file mode 100644 index 0000000..e27e9de --- /dev/null +++ b/src/mouse_manager.h @@ -0,0 +1,16 @@ +#ifndef MOUSE_MANAGER_H +#define MOUSE_MANAGER_H + +extern char* (*_mouseNameMangler)(char*); +extern int (*_rateCallback)(); +extern int (*_currentTimeCallback)(); + +char* _defaultNameMangler(char* a1); +int _defaultRateCallback(); +int _defaultTimeCallback(); +void _mousemgrSetNameMangler(char* (*func)(char*)); +void _initMousemgr(); +void _mouseHide(); +void _mouseShow(); + +#endif /* MOUSE_MANAGER_H */ diff --git a/src/movie.c b/src/movie.c new file mode 100644 index 0000000..0bc7155 --- /dev/null +++ b/src/movie.c @@ -0,0 +1,840 @@ +#include "movie.h" + +#include "color.h" +#include "core.h" +#include "debug.h" +#include "draw.h" +#include "game_config.h" +#include "memory_manager.h" +#include "movie_effect.h" +#include "movie_lib.h" +#include "sound.h" +#include "text_font.h" +#include "window_manager.h" + +#include + +// 0x5195B8 +int gMovieWindow = -1; + +// 0x5195BC +int gMovieSubtitlesFont = -1; + +// 0x5195E0 +MovieSetPaletteEntriesProc* gMovieSetPaletteEntriesProc = _setSystemPaletteEntries; + +// 0x5195E4 +int gMovieSubtitlesColorR = 31; + +// 0x5195E8 +int gMovieSubtitlesColorG = 31; + +// 0x5195EC +int gMovieSubtitlesColorB = 31; + +// 0x638E10 +Rect gMovieWindowRect; + +// 0x638E20 +Rect _movieRect; + +// 0x638E30 +void (*_movieCallback)(); + +// 0x638E38 +MovieSetPaletteProc* gMoviePaletteProc; + +// NOTE: Some kind of callback which was intended to change movie file path +// in place during opening movie file to find subsitutions. This callback is +// never set. +// +// 0x638E3C +int (*_failedOpenFunc)(char* filePath); + +// 0x638E40 +MovieBuildSubtitleFilePathProc* gMovieBuildSubtitleFilePathProc; + +// 0x638E48 +int _subtitleW; + +// 0x638E4C +int _lastMovieBH; + +// 0x638E50 +int _lastMovieBW; + +// 0x638E54 +int _lastMovieSX; + +// 0x638E58 +int _lastMovieSY; + +// 0x638E5C +int _movieScaleFlag; + +// 0x638E64 +int _lastMovieH; + +// 0x638E68 +int _lastMovieW; + +// 0x638E6C +int _lastMovieX; + +// 0x638E70 +int _lastMovieY; + +// 0x638E74 +MovieSubtitleListNode* gMovieSubtitleHead; + +// 0x638E78 +MovieExtendedFlags gMovieFlags; + +// 0x638E7C +int _movieAlphaFlag; + +// 0x638E80 +bool _movieSubRectFlag; + +// 0x638E84 +int _movieH; + +// 0x638E88 +int _movieOffset; + +// 0x638E8C +void (*_movieCaptureFrameFunc)(void*, int, int, int, int, int); + +// 0x638E90 +unsigned char* _lastMovieBuffer; + +// 0x638E94 +int _movieW; + +// 0x638E98 +void (*_movieFrameGrabFunc)(); + +// 0x638E9C +LPDIRECTDRAWSURFACE gMovieDirectDrawSurface; + +// 0x638EA0 +int _subtitleH; + +// 0x638EA4 +int _running; + +// 0x638EA8 +File* gMovieFileStream; + +// 0x638EAC +unsigned char* _alphaWindowBuf; + +// 0x638EB0 +int _movieX; + +// 0x638EB4 +int _movieY; + +// 0x638EB8 +bool gMovieDirectSoundInitialized; + +// 0x638EBC +File* _alphaHandle; + +// 0x638EC0 +unsigned char* _alphaBuf; + +// 0x4865FC +void* movieMallocImpl(size_t size) +{ + return internal_malloc_safe(size, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 209 +} + +// 0x486614 +void movieFreeImpl(void* ptr) +{ + internal_free_safe(ptr, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 213 +} + +// 0x48662C +bool movieReadImpl(int fileHandle, void* buf, int count) +{ + return fileRead(buf, 1, count, (File*)fileHandle) == count; +} + +// 0x486654 +void movieDirectImpl(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) +{ + int v14; + int v15; + + DDSURFACEDESC ddsd; + memset(&ddsd, 0, sizeof(DDSURFACEDESC)); + ddsd.dwSize = sizeof(DDSURFACEDESC); + + RECT srcRect; + srcRect.left = a4; + srcRect.top = a5; + srcRect.right = a2 + a4; + srcRect.bottom = a3 + a5; + + v14 = gMovieWindowRect.right - gMovieWindowRect.left; + v15 = gMovieWindowRect.right - gMovieWindowRect.left + 1; + + RECT destRect; + + if (_movieScaleFlag) { + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) { + destRect.top = (gMovieWindowRect.bottom - gMovieWindowRect.top + 1 - a7) / 2; + destRect.left = (v15 - 4 * a2 / 3) / 2; + } else { + destRect.top = _movieY + gMovieWindowRect.top; + destRect.left = gMovieWindowRect.left + _movieX; + } + + destRect.right = 4 * a2 / 3 + destRect.left; + destRect.bottom = a7 + destRect.top; + } else { + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) { + destRect.top = (gMovieWindowRect.bottom - gMovieWindowRect.top + 1 - a7) / 2; + destRect.left = (v15 - a6) / 2; + } else { + destRect.top = _movieY + gMovieWindowRect.top; + destRect.left = gMovieWindowRect.left + _movieX; + } + destRect.right = a6 + destRect.left; + destRect.bottom = a7 + destRect.top; + } + + _lastMovieSX = a4; + _lastMovieSY = a5; + _lastMovieX = destRect.left; + _lastMovieY = destRect.top; + _lastMovieBH = a3; + _lastMovieW = destRect.right - destRect.left; + gMovieDirectDrawSurface = a1; + _lastMovieBW = a2; + _lastMovieH = destRect.bottom - destRect.top; + + HRESULT hr; + do { + if (_movieCaptureFrameFunc != NULL) { + if (IDirectDrawSurface_Lock(a1, NULL, &ddsd, 1, NULL) == DD_OK) { + _movieCaptureFrameFunc(ddsd.lpSurface, a2, destRect.left, destRect.top, destRect.right - destRect.left, destRect.bottom - destRect.top); + IDirectDrawSurface_Unlock(a1, ddsd.lpSurface); + } + } + + hr = IDirectDrawSurface_Blt(gDirectDrawSurface1, &destRect, a1, &srcRect, 0, NULL); + } while (hr != DD_OK && hr != DDERR_SURFACELOST && hr == DDERR_WASSTILLDRAWING); +} + +// 0x486900 +void movieBufferedImpl(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) +{ + int v13; + + if (gMovieWindow == -1) { + return; + } + + _lastMovieBW = a2; + gMovieDirectDrawSurface = 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; + } + + if (_movieCaptureFrameFunc != NULL) { + // TODO: Ignore, _movieCaptureFrameFunc is never set. + // _movieCaptureFrameFunc() + } + + if (_movieFrameGrabFunc != NULL) { + // TODO: Ignore, _movieFrameGrabFunc is never set. + // _movieFrameGrabFunc(); + } else { + v13 = 4 * _movieSubRectFlag + 8 * _movieScaleFlag + 16 * _movieAlphaFlag; + // TODO: Incomplete. + } + + IDirectDrawSurface_Unlock(a1, ddsd.lpSurface); +} + +// 0x486C74 +int _movieScaleSubRectAlpha(int a1) +{ + gMovieFlags |= 1; + return 0; +} + +// 0x486C80 +int _blitAlpha(int win, unsigned char* a2, int a3, int a4, int a5) +{ + unsigned char* buf; + int offset; + + offset = windowGetWidth(win) * _movieY + _movieX; + buf = windowGetBuffer(win); + + // TODO: Incomplete. + // _alphaBltBuf(a2, a3, a4, a5, _alphaWindowBuf, _alphaBuf, buf + offset, windowGetWidth(win)); + + return 1; +} + +// 0x486D84 +int _blitNormal(int win, int a2, int a3, int a4, int a5) +{ + unsigned char* buf; + int offset; + + offset = windowGetWidth(win) * _movieY + _movieX; + buf = windowGetBuffer(win); + + // TODO: Incomplete. + // _drawScaled(buf + offset, _movieW, _movieH, windowGetWidth(win), a2, a3, a4, a5); + + return 1; +} + +// 0x486DDC +void movieSetPaletteEntriesImpl(unsigned char* palette, int start, int end) +{ + if (end != 0) { + gMovieSetPaletteEntriesProc(palette + start * 3, start, end + start - 1); + } +} + +// 0x486E08 +int _noop() +{ + return 0; +} + +// initMovie +// 0x486E0C +void movieInit() +{ + movieLibSetMemoryProcs(movieMallocImpl, movieFreeImpl); + movieLibSetDirectSound(gDirectSound); + gMovieDirectSoundInitialized = (gDirectSound != NULL); + movieLibSetDirectDraw(gDirectDraw); + movieLibSetPaletteEntriesProc(movieSetPaletteEntriesImpl); + _MVE_sfSVGA(640, 480, 480, 0, 0, 0, 0, 0, 0); + movieLibSetReadProc(movieReadImpl); +} + +// 0x486E98 +void _cleanupMovie(int a1) +{ + if (!_running) { + return; + } + + // TODO: Probably can be ignored. + // if (_endMovieFunc) { + // _endMovieFunc(_movieW, _movieX, _movieH); + // } + + int frame; + int dropped; + _MVE_rmFrameCounts(&frame, &dropped); + debugPrint("Frames %d, dropped %d\n", frame, dropped); + + if (_lastMovieBuffer != NULL) { + internal_free_safe(_lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 787 + _lastMovieBuffer = NULL; + } + + if (gMovieDirectDrawSurface != NULL) { + DDSURFACEDESC ddsd; + ddsd.dwSize = sizeof(DDSURFACEDESC); + if (IDirectDrawSurface_Lock(gMovieDirectDrawSurface, 0, &ddsd, 1, NULL) == DD_OK) { + _lastMovieBuffer = internal_malloc_safe(_lastMovieBH * _lastMovieBW, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 802 + blitBufferToBuffer((unsigned char*)ddsd.lpSurface + ddsd.lPitch * _lastMovieSX + _lastMovieSY, _lastMovieBW, _lastMovieBH, ddsd.lPitch, _lastMovieBuffer, _lastMovieBW); + IDirectDrawSurface_Unlock(gMovieDirectDrawSurface, ddsd.lpSurface); + } else { + debugPrint("Couldn't lock movie surface\n"); + } + + gMovieDirectDrawSurface = NULL; + } + + if (a1) { + _MVE_rmEndMovie(); + } + + _MVE_ReleaseMem(); + + fileClose(gMovieFileStream); + + if (_alphaWindowBuf != NULL) { + blitBufferToBuffer(_alphaWindowBuf, _movieW, _movieH, _movieW, windowGetBuffer(gMovieWindow) + _movieY * windowGetWidth(gMovieWindow) + _movieX, windowGetWidth(gMovieWindow)); + windowRefreshRect(gMovieWindow, &_movieRect); + } + + if (_alphaHandle != NULL) { + fileClose(_alphaHandle); + _alphaHandle = NULL; + } + + if (_alphaBuf != NULL) { + internal_free_safe(_alphaBuf, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 840 + _alphaBuf = NULL; + } + + if (_alphaWindowBuf != NULL) { + internal_free_safe(_alphaWindowBuf, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 845 + _alphaWindowBuf = NULL; + } + + while (gMovieSubtitleHead != NULL) { + MovieSubtitleListNode* next = gMovieSubtitleHead->next; + internal_free_safe(gMovieSubtitleHead->text, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 851 + internal_free_safe(gMovieSubtitleHead, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 852 + gMovieSubtitleHead = next; + } + + _running = 0; + _movieSubRectFlag = 0; + _movieScaleFlag = 0; + _movieAlphaFlag = 0; + gMovieFlags = 0; + gMovieWindow = -1; +} + +// 0x48711C +void movieExit() +{ + _cleanupMovie(1); + + if (_lastMovieBuffer) { + internal_free_safe(_lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 869 + _lastMovieBuffer = NULL; + } +} + +// 0x487150 +void _movieStop() +{ + if (_running) { + gMovieFlags |= MOVIE_EXTENDED_FLAG_0x02; + } +} + +// 0x487164 +int movieSetFlags(int flags) +{ + if ((flags & MOVIE_FLAG_0x04) != 0) { + gMovieFlags |= MOVIE_EXTENDED_FLAG_0x04 | MOVIE_EXTENDED_FLAG_0x08; + } else { + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x08; + if ((flags & MOVIE_FLAG_0x02) != 0) { + gMovieFlags |= MOVIE_EXTENDED_FLAG_0x04; + } else { + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x04; + } + } + + if ((flags & MOVIE_FLAG_0x01) != 0) { + _movieScaleFlag = 1; + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { + _sub_4F4BB(3); + } + } else { + _movieScaleFlag = 0; + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { + _sub_4F4BB(4); + } else { + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x08; + } + } + + if ((flags & MOVIE_FLAG_0x08) != 0) { + gMovieFlags |= MOVIE_EXTENDED_FLAG_0x10; + } else { + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x10; + } + + return 0; +} + +// 0x48725C +void _movieSetPaletteFunc(MovieSetPaletteEntriesProc* proc) +{ + gMovieSetPaletteEntriesProc = proc != NULL ? proc : _setSystemPaletteEntries; +} + +// 0x487274 +void movieSetPaletteProc(MovieSetPaletteProc* proc) +{ + gMoviePaletteProc = proc; +} + +// 0x4872E8 +void _cleanupLast() +{ + if (_lastMovieBuffer != NULL) { + internal_free_safe(_lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 981 + _lastMovieBuffer = NULL; + } + + gMovieDirectDrawSurface = NULL; +} + +// 0x48731C +File* movieOpen(char* filePath) +{ + gMovieFileStream = fileOpen(filePath, "rb"); + if (gMovieFileStream == NULL) { + if (_failedOpenFunc == NULL) { + debugPrint("Couldn't find movie file %s\n", filePath); + return 0; + } + + while (gMovieFileStream == NULL && _failedOpenFunc(filePath) != 0) { + gMovieFileStream = fileOpen(filePath, "rb"); + } + } + return gMovieFileStream; +} + +// 0x487380 +void movieLoadSubtitles(char* filePath) +{ + _subtitleW = _windowGetXres(); + _subtitleH = fontGetLineHeight() + 4; + + if (gMovieBuildSubtitleFilePathProc != NULL) { + filePath = gMovieBuildSubtitleFilePathProc(filePath); + } + + char path[MAX_PATH]; + strcpy(path, filePath); + + debugPrint("Opening subtitle file %s\n", path); + File* stream = fileOpen(path, "r"); + if (stream == NULL) { + debugPrint("Couldn't open subtitle file %s\n", path); + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x10; + return; + } + + MovieSubtitleListNode* prev = NULL; + int subtitleCount = 0; + while (!fileEof(stream)) { + char string[260]; + string[0] = '\0'; + fileReadString(string, 259, stream); + if (*string == '\0') { + break; + } + + MovieSubtitleListNode* subtitle = internal_malloc_safe(sizeof(*subtitle), __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1050 + subtitle->next = NULL; + + subtitleCount++; + + char* pch; + + pch = string; + while (*pch != '\0' && *pch != '\n') { + pch++; + } + + if (*pch != '\0') { + *pch = '\0'; + } + + pch = string; + while (*pch != '\0' && *pch != '\r') { + pch++; + } + + if (*pch != '\0') { + *pch = '\0'; + } + + pch = string; + while (*pch != '\0' && *pch != ':') { + pch++; + } + + if (*pch != '\0') { + *pch = '\0'; + subtitle->num = atoi(string); + subtitle->text = strdup_safe(pch + 1, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1058 + + if (prev != NULL) { + prev->next = subtitle; + } else { + gMovieSubtitleHead = subtitle; + } + + prev = subtitle; + } else { + debugPrint("subtitle: couldn't parse %s\n", string); + } + } + + fileClose(stream); + + debugPrint("Read %d subtitles\n", subtitleCount); +} + +// 0x48755C +void movieRenderSubtitles() +{ + if (gMovieSubtitleHead == NULL) { + return; + } + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x10) == 0) { + return; + } + + int v1 = fontGetLineHeight(); + 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 (gMovieSubtitleHead != NULL) { + if (frame < gMovieSubtitleHead->num) { + break; + } + + MovieSubtitleListNode* next = gMovieSubtitleHead->next; + + windowFill(gMovieWindow, 0, v2, _subtitleW, _subtitleH, 0); + + int oldFont; + if (gMovieSubtitlesFont != -1) { + oldFont = fontGetCurrent(); + fontSetCurrent(gMovieSubtitlesFont); + } + + int colorIndex = (gMovieSubtitlesColorR << 10) | (gMovieSubtitlesColorG << 5) | gMovieSubtitlesColorB; + _windowWrapLine(gMovieWindow, gMovieSubtitleHead->text, _subtitleW, _subtitleH, 0, v2, _colorTable[colorIndex] | 0x2000000, TEXT_ALIGNMENT_CENTER); + + Rect rect; + rect.right = _subtitleW; + rect.top = v2; + rect.bottom = v2 + _subtitleH; + rect.left = 0; + windowRefreshRect(gMovieWindow, &rect); + + internal_free_safe(gMovieSubtitleHead->text, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1108 + internal_free_safe(gMovieSubtitleHead, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1109 + + gMovieSubtitleHead = next; + + if (gMovieSubtitlesFont != -1) { + fontSetCurrent(oldFont); + } + } +} + +// 0x487710 +int _movieStart(int win, char* filePath, int (*a3)()) +{ + int v15; + int v16; + int v17; + + if (_running) { + return 1; + } + + _cleanupLast(); + + gMovieFileStream = movieOpen(filePath); + if (gMovieFileStream == NULL) { + return 1; + } + + gMovieWindow = win; + _running = 1; + gMovieFlags &= ~MOVIE_EXTENDED_FLAG_0x01; + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x10) != 0) { + movieLoadSubtitles(filePath); + } + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { + debugPrint("Direct "); + windowGetRect(gMovieWindow, &gMovieWindowRect); + debugPrint("Playing at (%d, %d) ", _movieX + gMovieWindowRect.left, _movieY + gMovieWindowRect.top); + _MVE_rmCallbacks(a3); + _MVE_sfCallbacks(movieDirectImpl); + + v17 = 0; + v16 = _movieY + gMovieWindowRect.top; + v15 = _movieX + gMovieWindowRect.left; + } else { + debugPrint("Buffered "); + _MVE_rmCallbacks(a3); + _MVE_sfCallbacks(movieBufferedImpl); + v17 = 0; + v16 = 0; + v15 = 0; + } + + _MVE_rmPrepMovie((int)gMovieFileStream, v15, v16, v17); + + if (_movieScaleFlag) { + debugPrint("scaled\n"); + } else { + debugPrint("not scaled\n"); + } + + // TODO: Probably can be ignored, never set. + // if (_startMovieFunc) { + // _startMovieFunc(); + // } + + if (_alphaHandle != NULL) { + // TODO: Probably can be ignored, never set. + abort(); + } + + _movieRect.left = _movieX; + _movieRect.top = _movieY; + _movieRect.right = _movieW + _movieX; + _movieRect.bottom = _movieH + _movieY; + + return 0; +} + +// 0x487964 +bool _localMovieCallback() +{ + movieRenderSubtitles(); + + 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 = windowGetWidth(win); + _movieH = windowGetHeight(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 * windowGetWidth(win); + _movieW = a5; + _movieH = a6; + _movieSubRectFlag = 1; + + return _movieStart(win, filePath, _noop); +} + +// 0x487B7C +int _stepMovie() +{ + if (_alphaHandle != NULL) { + int size; + fileReadInt32(_alphaHandle, &size); + fileRead(_alphaBuf, 1, size, _alphaHandle); + } + + int v1 = _MVE_rmStepMovie(); + if (v1 != -1) { + movieRenderSubtitles(); + } + + return v1; +} + +// 0x487BC8 +void movieSetBuildSubtitleFilePathProc(MovieBuildSubtitleFilePathProc* proc) +{ + gMovieBuildSubtitleFilePathProc = proc; +} + +// 0x487BD0 +void movieSetVolume(int volume) +{ + if (gMovieDirectSoundInitialized) { + int normalizedVolume = _soundVolumeHMItoDirectSound(volume); + movieLibSetVolume(normalizedVolume); + } +} + +// 0x487BEC +void _movieUpdate() +{ + if (!_running) { + return; + } + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x02) != 0) { + debugPrint("Movie aborted\n"); + _cleanupMovie(1); + return; + } + + if ((gMovieFlags & MOVIE_EXTENDED_FLAG_0x01) != 0) { + debugPrint("Movie error\n"); + _cleanupMovie(1); + return; + } + + if (_stepMovie() == -1) { + _cleanupMovie(1); + return; + } + + if (gMoviePaletteProc != NULL) { + int frame; + int dropped; + _MVE_rmFrameCounts(&frame, &dropped); + gMoviePaletteProc(frame); + } +} + +// 0x487C88 +int _moviePlaying() +{ + return _running; +} diff --git a/src/movie.h b/src/movie.h new file mode 100644 index 0000000..39661ea --- /dev/null +++ b/src/movie.h @@ -0,0 +1,108 @@ +#ifndef MOVIE_H +#define MOVIE_H + +#include "db.h" +#include "geometry.h" +#include "win32.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 struct MovieSubtitleListNode { + int num; + char* text; + struct MovieSubtitleListNode* next; +} MovieSubtitleListNode; + +typedef char* MovieBuildSubtitleFilePathProc(char* movieFilePath); +typedef void MovieSetPaletteEntriesProc(unsigned char* palette, int start, int end); +typedef void MovieSetPaletteProc(int frame); + +extern int gMovieWindow; +extern int gMovieSubtitlesFont; +extern MovieSetPaletteEntriesProc* gMovieSetPaletteEntriesProc; +extern int gMovieSubtitlesColorR; +extern int gMovieSubtitlesColorG; +extern int gMovieSubtitlesColorB; + +extern Rect gMovieWindowRect; +extern Rect _movieRect; +extern void (*_movieCallback)(); +extern MovieSetPaletteProc* gMoviePaletteProc; +extern int (*_failedOpenFunc)(char* filePath); +extern MovieBuildSubtitleFilePathProc* gMovieBuildSubtitleFilePathProc; +extern int _subtitleW; +extern int _lastMovieBH; +extern int _lastMovieBW; +extern int _lastMovieSX; +extern int _lastMovieSY; +extern int _movieScaleFlag; +extern int _lastMovieH; +extern int _lastMovieW; +extern int _lastMovieX; +extern int _lastMovieY; +extern MovieSubtitleListNode* gMovieSubtitleHead; +extern MovieExtendedFlags gMovieFlags; +extern int _movieAlphaFlag; +extern bool _movieSubRectFlag; +extern int _movieH; +extern int _movieOffset; +extern void (*_movieCaptureFrameFunc)(void*, int, int, int, int, int); +extern unsigned char* _lastMovieBuffer; +extern int _movieW; +extern void (*_movieFrameGrabFunc)(); +extern LPDIRECTDRAWSURFACE gMovieDirectDrawSurface; +extern int _subtitleH; +extern int _running; +extern File* gMovieFileStream; +extern unsigned char* _alphaWindowBuf; +extern int _movieX; +extern int _movieY; +extern bool gMovieDirectSoundInitialized; +extern File* _alphaHandle; +extern unsigned char* _alphaBuf; + +void* movieMallocImpl(size_t size); +void movieFreeImpl(void* ptr); +bool movieReadImpl(int fileHandle, void* buf, int count); +void movieDirectImpl(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); +void movieBufferedImpl(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); +int _movieScaleSubRectAlpha(int a1); +int _blitAlpha(int win, unsigned char* a2, int a3, int a4, int a5); +int _blitNormal(int win, int a2, int a3, int a4, int a5); +void movieSetPaletteEntriesImpl(unsigned char* palette, int start, int end); +int _noop(); +void movieInit(); +void _cleanupMovie(int a1); +void movieExit(); +void _movieStop(); +int movieSetFlags(int a1); +void _movieSetPaletteFunc(MovieSetPaletteEntriesProc* proc); +void movieSetPaletteProc(MovieSetPaletteProc* proc); +void _cleanupLast(); +File* movieOpen(char* filePath); +void movieLoadSubtitles(char* filePath); +void movieRenderSubtitles(); +int _movieStart(int win, char* filePath, int (*a3)()); +bool _localMovieCallback(); +int _movieRun(int win, char* filePath); +int _movieRunRect(int win, char* filePath, int a3, int a4, int a5, int a6); +int _stepMovie(); +void movieSetBuildSubtitleFilePathProc(MovieBuildSubtitleFilePathProc* proc); +void movieSetVolume(int volume); +void _movieUpdate(); +int _moviePlaying(); + +#endif /* MOVIE_H */ diff --git a/src/movie_effect.c b/src/movie_effect.c new file mode 100644 index 0000000..236dce7 --- /dev/null +++ b/src/movie_effect.c @@ -0,0 +1,270 @@ +#include "movie_effect.h" + +#include "config.h" +#include "debug.h" +#include "memory.h" +#include "movie.h" +#include "palette.h" + +#include +#include +#include + +// 0x5195F0 +bool gMovieEffectsInitialized = false; + +// 0x5195F4 +MovieEffect* gMovieEffectHead = NULL; + +// 0x638EC4 +unsigned char _source_palette[768]; + +// 0x6391C4 +bool _inside_fade; + +// 0x487CC0 +int movieEffectsInit() +{ + if (gMovieEffectsInitialized) { + return -1; + } + + memset(_source_palette, 0, sizeof(_source_palette)); + + gMovieEffectsInitialized = true; + + return 0; +} + +// 0x487CF4 +void movieEffectsReset() +{ + if (!gMovieEffectsInitialized) { + return; + } + + movieSetPaletteProc(NULL); + _movieSetPaletteFunc(NULL); + movieEffectsClear(); + + _inside_fade = false; + + memset(_source_palette, 0, sizeof(_source_palette)); +} + +// 0x487D30 +void movieEffectsExit() +{ + if (!gMovieEffectsInitialized) { + return; + } + + movieSetPaletteProc(NULL); + _movieSetPaletteFunc(NULL); + movieEffectsClear(); + + _inside_fade = false; + + memset(_source_palette, 0, sizeof(_source_palette)); +} + +// 0x487D7C +int movieEffectsLoad(const char* filePath) +{ + if (!gMovieEffectsInitialized) { + return -1; + } + + movieSetPaletteProc(NULL); + _movieSetPaletteFunc(NULL); + movieEffectsClear(); + _inside_fade = false; + memset(_source_palette, 0, sizeof(_source_palette)); + + if (filePath == NULL) { + return -1; + } + + Config config; + if (!configInit(&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"); + + if (!configRead(&config, path, true)) { + goto out; + } + + int movieEffectsLength; + if (!configGetInt(&config, "info", "total_effects", &movieEffectsLength)) { + goto out; + } + + int* movieEffectFrameList = internal_malloc(sizeof(*movieEffectFrameList) * movieEffectsLength); + if (movieEffectFrameList == NULL) { + goto out; + } + + bool frameListRead; + if (movieEffectsLength >= 2) { + frameListRead = configGetIntList(&config, "info", "effect_frames", movieEffectFrameList, movieEffectsLength); + } else { + frameListRead = configGetInt(&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 (!configGetString(&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 (!configGetIntList(&config, section, "fade_color", fadeColor, 3)) { + continue; + } + + int steps; + if (!configGetInt(&config, section, "fade_steps", &steps)) { + continue; + } + + MovieEffect* movieEffect = internal_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; + } + + movieEffect->next = gMovieEffectHead; + gMovieEffectHead = movieEffect; + + movieEffectsCreated++; + } + + if (movieEffectsCreated != 0) { + movieSetPaletteProc(_moviefx_callback_func); + _movieSetPaletteFunc(_moviefx_palette_func); + rc = 0; + } + } + + internal_free(movieEffectFrameList); + +out: + + configFree(&config); + + return rc; +} + +// 0x4880F0 +void _moviefx_stop() +{ + if (!gMovieEffectsInitialized) { + return; + } + + movieSetPaletteProc(NULL); + _movieSetPaletteFunc(NULL); + + movieEffectsClear(); + + _inside_fade = false; + memset(_source_palette, 0, sizeof(_source_palette)); +} + +// 0x488144 +void _moviefx_callback_func(int frame) +{ + MovieEffect* movieEffect = gMovieEffectHead; + 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); + } + } + + paletteSetEntries(palette); + } + + _inside_fade = movieEffect != NULL; +} + +// 0x4882AC +void _moviefx_palette_func(unsigned char* palette, int start, int end) +{ + memcpy(_source_palette + 3 * start, palette, 3 * (end - start + 1)); + + if (!_inside_fade) { + paletteSetEntriesInRange(palette, start, end); + } +} + +// 0x488310 +void movieEffectsClear() +{ + MovieEffect* movieEffect = gMovieEffectHead; + while (movieEffect != NULL) { + MovieEffect* next = movieEffect->next; + internal_free(movieEffect); + movieEffect = next; + } + + gMovieEffectHead = NULL; +} diff --git a/src/movie_effect.h b/src/movie_effect.h new file mode 100644 index 0000000..ab2b3ec --- /dev/null +++ b/src/movie_effect.h @@ -0,0 +1,41 @@ +#ifndef MOVIE_EFFECT_H +#define MOVIE_EFFECT_H + +#include + +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; + +extern bool gMovieEffectsInitialized; +extern MovieEffect* gMovieEffectHead; + +extern unsigned char _source_palette[768]; +extern bool _inside_fade; + +int movieEffectsInit(); +void movieEffectsReset(); +void movieEffectsExit(); +int movieEffectsLoad(const char* fileName); +void _moviefx_stop(); +void _moviefx_callback_func(int frame); +void _moviefx_palette_func(unsigned char* palette, int start, int end); +void movieEffectsClear(); + +#endif /* MOVIE_EFFECT_H */ diff --git a/src/movie_lib.c b/src/movie_lib.c new file mode 100644 index 0000000..5d7d028 --- /dev/null +++ b/src/movie_lib.c @@ -0,0 +1,2773 @@ +// 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 + +// 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 = _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) +{ + int v5; + int v7; + 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; + int value1; + 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; + } +} diff --git a/src/movie_lib.h b/src/movie_lib.h new file mode 100644 index 0000000..25497ef --- /dev/null +++ b/src/movie_lib.h @@ -0,0 +1,199 @@ +#ifndef MOVIE_LIB_H +#define MOVIE_LIB_H + +#include "memory_defs.h" + +#define DIRECTDRAW_VERSION 0x0300 +#include + +#define DIRECTSOUND_VERSION 0x0300 +#include + +#include + +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 */ diff --git a/src/nevs.c b/src/nevs.c new file mode 100644 index 0000000..99716dc --- /dev/null +++ b/src/nevs.c @@ -0,0 +1,196 @@ +#include "nevs.h" + +#include "debug.h" +#include "memory_manager.h" + +#include +#include + +static_assert(sizeof(Nevs) == 60, "wrong size"); + +// 0x6391C8 +Nevs* _nevs; + +// 0x6391CC +int _anyhits; + +// nevs_alloc +// 0x488340 +Nevs* _nevs_alloc() +{ + if (_nevs == NULL) { + debugPrint("nevs_alloc(): nevs_initonce() not called!"); + exit(99); + } + + for (int i = 0; i < NEVS_COUNT; i++) { + Nevs* nevs = &(_nevs[i]); + if (nevs->field_0 == 0) { + memset(nevs, 0, sizeof(Nevs)); + return nevs; + } + } + + return NULL; +} + +// 0x4883AC +void _nevs_close() +{ + if (_nevs != NULL) { + internal_free_safe(_nevs, __FILE__, __LINE__); // "..\\int\\NEVS.C", 97 + _nevs = NULL; + } +} + +// 0x4883D4 +void _nevs_removeprogramreferences(Program* program) +{ + if (_nevs != NULL) { + for (int i = 0; i < NEVS_COUNT; i++) { + Nevs* nevs = &(_nevs[i]); + if (nevs->field_0 != 0 && nevs->program == program) { + memset(nevs, 0, sizeof(*nevs)); + } + } + } +} + +// nevs_initonce +// 0x488418 +void _nevs_initonce() +{ + // TODO: Incomplete. + // _interpretRegisterProgramDeleteCallback(_nevs_removeprogramreferences); + + if (_nevs == NULL) { + _nevs = internal_calloc_safe(sizeof(Nevs), NEVS_COUNT, __FILE__, __LINE__); // "..\\int\\NEVS.C", 131 + if (_nevs == NULL) { + debugPrint("nevs_initonce(): out of memory"); + exit(99); + } + } +} + +// nevs_find +// 0x48846C +Nevs* _nevs_find(const char* a1) +{ + if (!_nevs) { + debugPrint("nevs_find(): nevs_initonce() not called!"); + exit(99); + } + + for (int index = 0; index < NEVS_COUNT; index++) { + Nevs* nevs = &(_nevs[index]); + if (nevs->field_0 != 0 && stricmp(nevs->field_4, a1) == 0) { + return nevs; + } + } + + return NULL; +} + +// 0x4884C8 +int _nevs_addevent(const char* a1, Program* program, int proc, int a4) +{ + Nevs* nevs; + + nevs = _nevs_find(a1); + if (nevs == NULL) { + nevs = _nevs_alloc(); + } + + if (nevs == NULL) { + return 1; + } + + nevs->field_0 = 1; + strcpy(nevs->field_4, a1); + nevs->program = program; + nevs->proc = proc; + nevs->field_2C = a4; + nevs->field_38 = NULL; + + return 0; +} + +// nevs_clearevent +// 0x48859C +int _nevs_clearevent(const char* a1) +{ + debugPrint("nevs_clearevent( '%s');\n", a1); + + Nevs* nevs = _nevs_find(a1); + if (nevs) { + memset(nevs, 0, sizeof(Nevs)); + return 0; + } + + return 1; +} + +// nevs_signal +// 0x48862C +int _nevs_signal(const char* a1) +{ + debugPrint("nevs_signal( '%s');\n", a1); + + Nevs* nevs = _nevs_find(a1); + if (nevs == NULL) { + return 1; + } + + debugPrint("nep: %p, used = %u, prog = %p, proc = %d", nevs, nevs->field_0, nevs->program, nevs->proc); + + if (nevs->field_0 && (nevs->program && nevs->proc || nevs->field_38) && !nevs->field_34) { + nevs->field_30++; + _anyhits++; + return 0; + } + + return 1; +} + +// nevs_update +// 0x4886AC +void _nevs_update() +{ + int v1; + int v2; + + if (_anyhits == 0) { + return; + } + + debugPrint("nevs_update(): we have anyhits = %u\n", _anyhits); + + _anyhits = 0; + + for (int index = 0; index < NEVS_COUNT; index++) { + Nevs* nevs = &(_nevs[index]); + if (nevs->field_0 && (nevs->program && nevs->proc || nevs->field_38) && !nevs->field_34) { + v1 = nevs->field_30; + if (nevs->field_34 < v1) { + v2 = v1 - 1; + nevs->field_34 = 1; + + nevs->field_30--; + + _anyhits += v2; + + if (nevs->field_38 == NULL) { + _executeProc(nevs->program, nevs->proc); + } else { + nevs->field_38(); + } + + nevs->field_34 = 0; + + if (nevs->field_2C == 0) { + memset(nevs, 0, sizeof(Nevs)); + } + } + } + } +} diff --git a/src/nevs.h b/src/nevs.h new file mode 100644 index 0000000..47a8b71 --- /dev/null +++ b/src/nevs.h @@ -0,0 +1,33 @@ +#ifndef NEVS_H +#define NEVS_H + +#include "interpreter.h" + +#define NEVS_COUNT (40) + +typedef struct Nevs { + int field_0; + char field_4[32]; + Program* program; + int proc; + int field_2C; + int field_30; + int field_34; + void (*field_38)(); +} Nevs; + +extern Nevs* _nevs; + +extern int _anyhits; + +Nevs* _nevs_alloc(); +void _nevs_close(); +void _nevs_removeprogramreferences(Program* program); +void _nevs_initonce(); +Nevs* _nevs_find(const char* a1); +int _nevs_addevent(const char* a1, Program* program, int proc, int a4); +int _nevs_clearevent(const char* a1); +int _nevs_signal(const char* a1); +void _nevs_update(); + +#endif /* NEVS_H */ diff --git a/src/obj_types.h b/src/obj_types.h new file mode 100644 index 0000000..96ff8af --- /dev/null +++ b/src/obj_types.h @@ -0,0 +1,257 @@ +#ifndef OBJ_TYPES_H +#define OBJ_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, +}; + +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_FLAG_0x08 = 0x08, + OBJECT_NO_BLOCK = 0x10, + OBJECT_LIGHTING = 0x20, + OBJECT_FLAG_0x400 = 0x400, + OBJECT_FLAG_0x800 = 0x800, + OBJECT_FLAG_0x1000 = 0x1000, + OBJECT_FLAG_0x2000 = 0x2000, + OBJECT_FLAG_0x4000 = 0x4000, + OBJECT_FLAG_0x8000 = 0x8000, + OBJECT_FLAG_0x10000 = 0x10000, + OBJECT_FLAG_0x20000 = 0x20000, + OBJECT_FLAG_0x40000 = 0x40000, + OBJECT_FLAG_0x80000 = 0x80000, + OBJECT_IN_LEFT_HAND = 0x1000000, + OBJECT_IN_RIGHT_HAND = 0x2000000, + OBJECT_WORN = 0x4000000, + OBJECT_FLAG_0x10000000 = 0x10000000, + OBJECT_FLAG_0x20000000 = 0x20000000, + OBJECT_FLAG_0x40000000 = 0x40000000, + OBJECT_FLAG_0x80000000 = 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_FLAG_0x80000 | OBJECT_FLAG_0x40000 | OBJECT_FLAG_0x20000 | OBJECT_FLAG_0x10000 | OBJECT_FLAG_0x8000 | OBJECT_FLAG_0x4000, + OBJECT_OPEN_DOOR = OBJECT_FLAG_0x80000000 | OBJECT_FLAG_0x20000000 | OBJECT_NO_BLOCK, +} ObjectFlags; + +#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 + +#define CRITTER_MANEUVER_0x01 0x01 +#define CRITTER_MANEUVER_STOP_ATTACKING 0x02 +#define CRITTER_MANUEVER_FLEEING 0x04 + +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 = (DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT | DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT | 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 field_0; // obj_pudg.pudstairs.destMap + int field_4; // obj_pudg.pudstairs.destBuiltTile +} StairsSceneryData; + +typedef struct ElevatorSceneryData { + int field_0; // obj_pudg.pudelevator.elevType + int field_4; // obj_pudg.pudelevator.elevLevel +} ElevatorSceneryData; + +typedef struct LadderSceneryData { + int field_0; + int field_4; +} 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; + +#endif /* OBJ_TYPES_H */ diff --git a/src/object.c b/src/object.c new file mode 100644 index 0000000..1e9cd4e --- /dev/null +++ b/src/object.c @@ -0,0 +1,5166 @@ +#include "object.h" + +#include "animation.h" +#include "art.h" +#include "color.h" +#include "combat.h" +#include "core.h" +#include "critter.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "item.h" +#include "light.h" +#include "map.h" +#include "memory.h" +#include "party_member.h" +#include "proto.h" +#include "proto_instance.h" +#include "scripts.h" +#include "text_object.h" +#include "tile.h" +#include "world_map.h" + +#include +#include + +// 0x5195F8 +bool gObjectsInitialized = false; + +// 0x5195FC +int _updateHexWidth = 0; + +// 0x519600 +int _updateHexHeight = 0; + +// 0x519604 +int _updateHexArea = 0; + +// 0x519608 +int* _orderTable[2] = { + NULL, + NULL, +}; + +// 0x519610 +int* _offsetTable[2] = { + NULL, + NULL, +}; + +// 0x519618 +int* _offsetDivTable = NULL; + +// 0x51961C +int* _offsetModTable = NULL; + +// 0x519620 +ObjectListNode** _renderTable = NULL; + +// Number of objects in _outlinedObjects. +// +// 0x519624 +int _outlineCount = 0; + +// Contains objects that are not bounded to tiles. +// +// 0x519628 +ObjectListNode* gObjectListHead = NULL; + +// 0x51962C +int _centerToUpperLeft = 0; + +// 0x519630 +int gObjectFindElevation = 0; + +// 0x519634 +int gObjectFindTile = 0; + +// 0x519638 +ObjectListNode* gObjectFindLastObjectListNode = NULL; + +// 0x51963C +int* gObjectFids = NULL; + +// 0x519640 +int gObjectFidsLength = 0; + +// 0x51964C +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 +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 +int gViolenceLevel = -1; + +// 0x519770 +int _obj_last_roof_x = -1; + +// 0x519774 +int _obj_last_roof_y = -1; + +// 0x519778 +int _obj_last_elev = -1; + +// 0x51977C +int _obj_last_is_empty = 1; + +// 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 +int _objItemOutlineState = 0; + +// 0x51979C +int _cd_order[9] = { + 1, + 0, + 3, + 5, + 4, + 2, + 0, + 0, + 0, +}; + +// 0x6391D0 +int _light_blocked[6][36]; + +// 0x639530 +int _light_offsets[2][6][36]; + +// 0x639BF0 +Rect gObjectsWindowRect; + +// Likely outlined objects on the screen. +// +// 0x639C00 +Object* _outlinedObjects[100]; + +// 0x639D90 +int _updateAreaPixelBounds; + +// 0x639D94 +int dword_639D94; + +// 0x639D98 +int dword_639D98; + +// 0x639D9C +int dword_639D9C; + +// Contains objects that are bounded to tiles. +// +// 0x639DA0 +ObjectListNode* gObjectListHeadByTile[HEX_GRID_SIZE]; + +// 0x660EA0 +unsigned char _glassGrayTable[256]; + +// 0x660FA0 +unsigned char _commonGrayTable[256]; + +// 0x6610A0 +int gObjectsWindowBufferSize; + +// 0x6610A4 +unsigned char* gObjectsWindowBuffer; + +// 0x6610A8 +int gObjectsWindowHeight; + +// Translucent "egg" effect around player. +// +// 0x6610AC +Object* gEgg; + +// 0x6610B0 +int gObjectsWindowPitch; + +// 0x6610B4 +int gObjectsWindowWidth; + +// obj_dude +// 0x6610B8 +Object* gDude; + +// 0x6610BC +char _obj_seen_check[5001]; + +// 0x662445 +char _obj_seen[5001]; + +// obj_init +// 0x488780 +int objectsInit(unsigned char* buf, int width, int height, int pitch) +{ + memset(_obj_seen, 0, 5001); + dword_639D98 = width + 320; + _updateAreaPixelBounds = -320; + dword_639D9C = height + 240; + dword_639D94 = -240; + + _updateHexWidth = (dword_639D98 + 320 + 1) / 32 + 1; + _updateHexHeight = (dword_639D9C + 240 + 1) / 12 + 1; + _updateHexArea = _updateHexWidth * _updateHexHeight; + + memset(gObjectListHeadByTile, 0, sizeof(gObjectListHeadByTile)); + + 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 (lightInit() == -1) { + goto err_2; + } + + if (textObjectsInit(buf, width, height) == -1) { + goto err_2; + } + + _obj_light_table_init(); + _obj_blend_table_init(); + + _centerToUpperLeft = tileFromScreenXY(_updateAreaPixelBounds, dword_639D94, 0) - gCenterTile; + gObjectsWindowWidth = width; + gObjectsWindowHeight = height; + gObjectsWindowBuffer = buf; + + gObjectsWindowRect.left = 0; + gObjectsWindowRect.top = 0; + gObjectsWindowRect.right = width - 1; + gObjectsWindowRect.bottom = height - 1; + + gObjectsWindowBufferSize = height * width; + gObjectsWindowPitch = pitch; + + int dudeFid = buildFid(1, _art_vault_guy_num, 0, 0, 0); + objectCreateWithFidPid(&gDude, dudeFid, 0x1000000); + + gDude->flags |= OBJECT_FLAG_0x400; + gDude->flags |= OBJECT_TEMPORARY; + gDude->flags |= OBJECT_HIDDEN; + gDude->flags |= OBJECT_FLAG_0x20000000; + objectSetLight(gDude, 4, 0x10000, NULL); + + if (partyMemberAdd(gDude) == -1) { + debugPrint("\n Error: Can't add Player into party!"); + exit(1); + } + + int eggFid = buildFid(6, 2, 0, 0, 0); + objectCreateWithFidPid(&gEgg, eggFid, -1); + gEgg->flags |= OBJECT_FLAG_0x400; + gEgg->flags |= OBJECT_TEMPORARY; + gEgg->flags |= OBJECT_HIDDEN; + gEgg->flags |= OBJECT_FLAG_0x20000000; + + gObjectsInitialized = true; + + return 0; + +err_2: + + // NOTE: Uninline. + _obj_order_table_exit(); + +err: + + _obj_offset_table_exit(); + + return -1; +} + +// 0x488A00 +void objectsReset() +{ + if (gObjectsInitialized) { + textObjectsReset(); + _obj_remove_all(); + memset(_obj_seen, 0, 5001); + lightResetIntensity(); + } +} + +// 0x488A30 +void objectsExit() +{ + if (gObjectsInitialized) { + gDude->flags &= ~OBJECT_FLAG_0x400; + gEgg->flags &= ~OBJECT_FLAG_0x400; + + _obj_remove_all(); + textObjectsFree(); + + // NOTE: Uninline. + _obj_blend_table_exit(); + + lightResetIntensity(); + + // NOTE: Uninline. + _obj_render_table_exit(); + + // NOTE: Uninline. + _obj_order_table_exit(); + + _obj_offset_table_exit(); + } +} + +// 0x488AF4 +int objectRead(Object* obj, File* stream) +{ + int field_74; + + if (fileReadInt32(stream, &(obj->id)) == -1) return -1; + if (fileReadInt32(stream, &(obj->tile)) == -1) return -1; + if (fileReadInt32(stream, &(obj->x)) == -1) return -1; + if (fileReadInt32(stream, &(obj->y)) == -1) return -1; + if (fileReadInt32(stream, &(obj->sx)) == -1) return -1; + if (fileReadInt32(stream, &(obj->sy)) == -1) return -1; + if (fileReadInt32(stream, &(obj->frame)) == -1) return -1; + if (fileReadInt32(stream, &(obj->rotation)) == -1) return -1; + if (fileReadInt32(stream, &(obj->fid)) == -1) return -1; + if (fileReadInt32(stream, &(obj->flags)) == -1) return -1; + if (fileReadInt32(stream, &(obj->elevation)) == -1) return -1; + if (fileReadInt32(stream, &(obj->pid)) == -1) return -1; + if (fileReadInt32(stream, &(obj->cid)) == -1) return -1; + if (fileReadInt32(stream, &(obj->lightDistance)) == -1) return -1; + if (fileReadInt32(stream, &(obj->lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &field_74) == -1) return -1; + if (fileReadInt32(stream, &(obj->sid)) == -1) return -1; + if (fileReadInt32(stream, &(obj->field_80)) == -1) return -1; + + obj->outline = 0; + obj->owner = NULL; + + if (objectDataRead(obj, stream) != 0) { + return -1; + } + + if (obj->pid < 0x5000010 || obj->pid > 0x5000017) { + if ((obj->pid >> 24) == 0 && !(gMapHeader.flags & 0x01)) { + _object_fix_weapon_ammo(obj); + } + } else { + if (obj->data.misc.map <= 0) { + if ((obj->fid & 0xFFF) < 33) { + obj->fid = buildFid(5, (obj->fid & 0xFFF) + 16, (obj->fid & 0xFF0000) >> 16, 0, 0); + } + } + } + + return 0; +} + +// 0x488CE4 +int objectLoadAll(File* stream) +{ + int rc = objectLoadAllInternal(stream); + + gViolenceLevel = -1; + + return rc; +} + +// 0x488CF8 +int objectLoadAllInternal(File* stream) +{ + if (stream == NULL) { + return -1; + } + + bool fixMapInventory; + if (!configGetBool(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, &fixMapInventory)) { + fixMapInventory = false; + } + + if (!configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &gViolenceLevel)) { + gViolenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; + } + + int objectCount; + if (fileReadInt32(stream, &objectCount) == -1) { + return -1; + } + + if (gObjectFids != NULL) { + internal_free(gObjectFids); + } + + if (objectCount != 0) { + gObjectFids = internal_malloc(sizeof(*gObjectFids) * objectCount); + memset(gObjectFids, 0, sizeof(*gObjectFids) * objectCount); + if (gObjectFids == NULL) { + return -1; + } + gObjectFidsLength = 0; + } + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + int objectCountAtElevation; + if (fileReadInt32(stream, &objectCountAtElevation) == -1) { + return -1; + } + + for (int objectIndex = 0; objectIndex < objectCountAtElevation; objectIndex++) { + ObjectListNode* objectListNode; + + // NOTE: Uninline. + if (objectListNodeCreate(&objectListNode) == -1) { + return -1; + } + + if (objectAllocate(&(objectListNode->obj)) == -1) { + // NOTE: Uninline. + objectListNodeDestroy(&objectListNode); + return -1; + } + + if (objectRead(objectListNode->obj, stream) != 0) { + // NOTE: Uninline. + objectDeallocate(&(objectListNode->obj)); + + // NOTE: Uninline. + objectListNodeDestroy(&objectListNode); + + return -1; + } + + objectListNode->obj->outline = 0; + gObjectFids[gObjectFidsLength++] = objectListNode->obj->fid; + + if (objectListNode->obj->sid != -1) { + Script* script; + if (scriptGetScript(objectListNode->obj->sid, &script) == -1) { + objectListNode->obj->sid = -1; + debugPrint("\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) && (objectListNode->obj->flags >> 24) == 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 = internal_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 (fileReadInt32(stream, &(inventoryItem->quantity)) != 0) { + debugPrint("Error loading inventory\n"); + return -1; + } + + if (fixMapInventory) { + inventoryItem->item = internal_malloc(sizeof(Object)); + if (inventoryItem->item == NULL) { + debugPrint("Error loading inventory\n"); + return -1; + } + + if (objectRead(inventoryItem->item, stream) != 0) { + debugPrint("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 +void _obj_fix_combat_cid_for_dude() +{ + Object** critterList; + int critterListLength = objectListCreate(-1, gElevation, OBJ_TYPE_CRITTER, &critterList); + + if (gDude->data.critter.combat.whoHitMeCid == -1) { + gDude->data.critter.combat.whoHitMe = NULL; + } else { + int index = _find_cid(0, gDude->data.critter.combat.whoHitMeCid, critterList, critterListLength); + if (index != critterListLength) { + gDude->data.critter.combat.whoHitMe = critterList[index]; + } else { + gDude->data.critter.combat.whoHitMe = NULL; + } + } + + if (critterListLength != 0) { + // NOTE: Uninline. + objectListFree(critterList); + } +} + +// Fixes ammo pid and number of charges. +// +// 0x48911C +void _object_fix_weapon_ammo(Object* obj) +{ + if ((obj->pid >> 24) != OBJ_TYPE_ITEM) { + return; + } + + Proto* proto; + if (protoGetProto(obj->pid, &proto) == -1) { + debugPrint("\nError: obj_load: proto_ptr failed on pid"); + exit(1); + } + + int charges; + if (itemGetType(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 ((obj->pid >> 24) == 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) { + debugPrint("\nError: Misc Item Prototype %s: charges incorrect!", protoGetName(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 +int objectWrite(Object* obj, File* stream) +{ + if (fileWriteInt32(stream, obj->id) == -1) return -1; + if (fileWriteInt32(stream, obj->tile) == -1) return -1; + if (fileWriteInt32(stream, obj->x) == -1) return -1; + if (fileWriteInt32(stream, obj->y) == -1) return -1; + if (fileWriteInt32(stream, obj->sx) == -1) return -1; + if (fileWriteInt32(stream, obj->sy) == -1) return -1; + if (fileWriteInt32(stream, obj->frame) == -1) return -1; + if (fileWriteInt32(stream, obj->rotation) == -1) return -1; + if (fileWriteInt32(stream, obj->fid) == -1) return -1; + if (fileWriteInt32(stream, obj->flags) == -1) return -1; + if (fileWriteInt32(stream, obj->elevation) == -1) return -1; + if (fileWriteInt32(stream, obj->pid) == -1) return -1; + if (fileWriteInt32(stream, obj->cid) == -1) return -1; + if (fileWriteInt32(stream, obj->lightDistance) == -1) return -1; + if (fileWriteInt32(stream, obj->lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, obj->outline) == -1) return -1; + if (fileWriteInt32(stream, obj->sid) == -1) return -1; + if (fileWriteInt32(stream, obj->field_80) == -1) return -1; + if (objectDataWrite(obj, stream) == -1) return -1; + + return 0; +} + +// 0x48935C +int objectSaveAll(File* stream) +{ + if (stream == NULL) { + return -1; + } + + _obj_process_seen(); + + int objectCount = 0; + + long objectCountPos = fileTell(stream); + if (fileWriteInt32(stream, objectCount) == -1) { + return -1; + } + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + int objectCountAtElevation = 0; + + long objectCountAtElevationPos = fileTell(stream); + if (fileWriteInt32(stream, objectCountAtElevation) == -1) { + return -1; + } + + for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { + for (ObjectListNode* objectListNode = gObjectListHeadByTile[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 ((object->pid >> 24) == 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 (objectWrite(object, stream) == -1) { + return -1; + } + + if ((object->pid >> 24) == 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 (fileWriteInt32(stream, inventoryItem->quantity) == -1) { + return -1; + } + + if (_obj_save_obj(stream, inventoryItem->item) == -1) { + return -1; + } + } + + objectCountAtElevation++; + } + } + + long pos = fileTell(stream); + fileSeek(stream, objectCountAtElevationPos, SEEK_SET); + fileWriteInt32(stream, objectCountAtElevation); + fileSeek(stream, pos, SEEK_SET); + + objectCount += objectCountAtElevation; + } + + long pos = fileTell(stream); + fileSeek(stream, objectCountPos, SEEK_SET); + fileWriteInt32(stream, objectCount); + fileSeek(stream, pos, SEEK_SET); + + return 0; +} + +// 0x489550 +void _obj_render_pre_roof(Rect* rect, int elevation) +{ + if (!gObjectsInitialized) { + return; + } + + Rect updatedRect; + if (rectIntersection(rect, &gObjectsWindowRect, &updatedRect) != 0) { + return; + } + + int lightLevel = lightGetLightLevel(); + int v5 = updatedRect.left - 320; + int v4 = updatedRect.top - 240; + int v17 = updatedRect.right + 320; + int v18 = updatedRect.bottom + 240; + int v19 = tileFromScreenXY(v5, v4, elevation); + int v20 = (v17 - v5 + 1) / 32; + int v23 = (v18 - v4 + 1) / 12; + + int odd = gCenterTile & 1; + int* v7 = _orderTable[odd]; + int* v8 = _offsetTable[odd]; + + _outlineCount = 0; + + int v34 = 0; + for (int i = 0; i < _updateHexArea; i++) { + int v9 = *v7++; + if (v23 > _offsetDivTable[v9] && v20 > _offsetModTable[v9]) { + int v2; + + ObjectListNode* objectListNode = gObjectListHeadByTile[v19 + v8[v9]]; + if (objectListNode != NULL) { + // NOTE: calls _light_get_tile two times, probably result of min/max macro + int q = _light_get_tile(elevation, objectListNode->obj->tile); + if (q >= lightLevel) { + v2 = q; + } else { + v2 = lightLevel; + } + } + + while (objectListNode != NULL) { + if (elevation < objectListNode->obj->elevation) { + break; + } + + if (elevation == objectListNode->obj->elevation) { + if ((objectListNode->obj->flags & OBJECT_FLAG_0x08) == 0) { + break; + } + + if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) { + _obj_render_object(objectListNode->obj, &updatedRect, v2); + + 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[v34++] = objectListNode; + } + } + } + + for (int i = 0; i < v34; i++) { + int v2; + + ObjectListNode* objectListNode = _renderTable[i]; + if (objectListNode != NULL) { + v2 = lightLevel; + int w = _light_get_tile(elevation, objectListNode->obj->tile); + if (w > v2) { + v2 = w; + } + } + + 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, v2); + + 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 (!gObjectsInitialized) { + return; + } + + Rect updatedRect; + if (rectIntersection(rect, &gObjectsWindowRect, &updatedRect) != 0) { + return; + } + + for (int index = 0; index < _outlineCount; index++) { + objectDrawOutline(_outlinedObjects[index], &updatedRect); + } + + textObjectsRenderInRect(&updatedRect); + + ObjectListNode* objectListNode = gObjectListHead; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if ((object->flags & OBJECT_HIDDEN) == 0) { + _obj_render_object(object, &updatedRect, 0x10000); + } + objectListNode = objectListNode->next; + } +} + +// 0x489A84 +int objectCreateWithFidPid(Object** objectPtr, int fid, int pid) +{ + ObjectListNode* objectListNode; + + // NOTE: Uninline; + if (objectListNodeCreate(&objectListNode) == -1) { + return -1; + } + + if (objectAllocate(&(objectListNode->obj)) == -1) { + // Uninline. + objectListNodeDestroy(&objectListNode); + return -1; + } + + objectListNode->obj->fid = fid; + _obj_insert(objectListNode); + + if (objectPtr) { + *objectPtr = objectListNode->obj; + } + + objectListNode->obj->pid = pid; + objectListNode->obj->id = scriptsNewObjectId(); + + if (pid == -1 || (pid >> 24) == 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 (protoGetProto(pid, &proto) == -1) { + return 0; + } + + objectSetLight(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_FLAG_0x800; + } + + if ((proto->flags & 0x8000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x8000; + } else { + if ((proto->flags & 0x10000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x10000; + } else if ((proto->flags & 0x20000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x20000; + } else if ((proto->flags & 0x40000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x40000; + } else if ((proto->flags & 0x80000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x80000; + } else if ((proto->flags & 0x4000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x4000; + } + } + + if ((proto->flags & 0x20000000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x20000000; + } + + if ((proto->flags & 0x80000000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x80000000; + } + + if ((proto->flags & 0x10000000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x10000000; + } + + if ((proto->flags & 0x1000) != 0) { + objectListNode->obj->flags |= OBJECT_FLAG_0x1000; + } + + _obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid)); + + return 0; +} + +// 0x489C9C +int objectCreateWithPid(Object** objectPtr, int pid) +{ + Proto* proto; + + *objectPtr = NULL; + + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + return objectCreateWithFidPid(objectPtr, proto->fid, pid); +} + +// 0x489CCC +int _obj_copy(Object** a1, Object* a2) +{ + if (a2 == NULL) { + return -1; + } + + ObjectListNode* objectListNode; + + // NOTE: Uninline. + if (objectListNodeCreate(&objectListNode) == -1) { + return -1; + } + + if (objectAllocate(&(objectListNode->obj)) == -1) { + // NOTE: Uninline. + objectListNodeDestroy(&objectListNode); + return -1; + } + + objectDataReset(objectListNode->obj); + + memcpy(objectListNode->obj, a2, sizeof(Object)); + + if (a1 != NULL) { + *a1 = objectListNode->obj; + } + + _obj_insert(objectListNode); + + objectListNode->obj->id = scriptsNewObjectId(); + + if (objectListNode->obj->sid != -1) { + objectListNode->obj->sid = -1; + _obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid)); + } + + if (objectSetRotation(objectListNode->obj, a2->rotation, NULL) == -1) { + // TODO: Probably leaking object allocated with objectAllocate. + // NOTE: Uninline. + objectListNodeDestroy(&objectListNode); + return -1; + } + + objectListNode->obj->flags &= ~OBJECT_FLAG_0x2000; + + 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 objectAllocate. + // NOTE: Uninline. + objectListNodeDestroy(&objectListNode); + return -1; + } + + if (itemAdd(objectListNode->obj, newItem, oldInventoryItem->quantity) == 1) { + // TODO: Probably leaking object allocated with objectAllocate. + // NOTE: Uninline. + objectListNodeDestroy(&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 (objectListNodeCreate(&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 (objectGetListNode(obj, &node, &prev_node) != 0) { + return -1; + } + + if (_obj_adjust_light(obj, 1, rect) == -1) { + if (rect != NULL) { + objectGetRect(obj, rect); + } + } + + if (prev_node != NULL) { + prev_node->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + + if (node != NULL) { + internal_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 (objectGetListNode(obj, &node, &previousNode) == -1) { + return -1; + } + + if (obj == gDude) { + if (rect != NULL) { + Rect eggRect; + objectGetRect(gEgg, &eggRect); + rectCopy(rect, &eggRect); + + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + + obj->x += x; + obj->sx += x; + + obj->y += y; + obj->sy += y; + + _obj_insert(node); + + rectOffset(&eggRect, x, y); + + _obj_offset(gEgg, x, y, NULL); + rectUnion(rect, &eggRect, rect); + } else { + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + + obj->x += x; + obj->sx += x; + + obj->y += y; + obj->sy += y; + + _obj_insert(node); + + _obj_offset(gEgg, x, y, NULL); + } + } else { + if (rect != NULL) { + objectGetRect(obj, rect); + + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[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); + + rectUnion(rect, &objectRect, rect); + } else { + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[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 (objectGetListNode(a1, &node, &previousNode) == -1) { + return -1; + } + + if (_obj_adjust_light(a1, 1, a5) == -1) { + if (a5 != NULL) { + objectGetRect(a1, a5); + } + } + + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + + a1->tile = -1; + a1->elevation = elevation; + v22 = 1; + } else { + if (elevation == a1->elevation) { + if (a5 != NULL) { + objectGetRect(a1, a5); + } + } else { + if (objectGetListNode(a1, &node, &previousNode) == -1) { + return -1; + } + + if (a5 != NULL) { + objectGetRect(a1, a5); + } + + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile != -1) { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } else { + gObjectListHead = gObjectListHead->next; + } + } + + a1->elevation = elevation; + v22 = 1; + } + } + + CacheEntry* cacheHandle; + int width; + int height; + Art* art = artLock(a1->fid, &cacheHandle); + if (art != NULL) { + artGetSize(art, a1->frame, a1->rotation, &width, &height); + a1->sx = a2 - width / 2; + a1->sy = a3 - (height - 1); + artUnlock(cacheHandle); + } + + if (v22) { + _obj_insert(node); + } + + if (a5 != NULL) { + Rect rect; + objectGetRect(a1, &rect); + rectUnion(a5, &rect, a5); + } + + if (a1 == gDude) { + if (a1 != NULL) { + Rect rect; + _obj_move(gEgg, a2, a3, elevation, &rect); + rectUnion(a5, &rect, a5); + } else { + _obj_move(gEgg, a2, a3, elevation, NULL); + } + } + + return 0; +} + +// 0x48A568 +int objectSetLocation(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 (objectGetListNode(obj, &node, &prevNode) == -1) { + return -1; + } + + Rect v23; + int v5 = _obj_adjust_light(obj, 1, rect); + if (rect != NULL) { + if (v5 == -1) { + objectGetRect(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) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tileIndex] = gObjectListHeadByTile[tileIndex]->next; + } + } + + if (_obj_connect_to_tile(node, tile, elevation, rect) == -1) { + return -1; + } + + if (isInCombat()) { + if ((obj->fid & 0xF000000) >> 24 == OBJ_TYPE_CRITTER) { + bool v8 = obj->outline != 0 && (obj->outline & OUTLINE_DISABLED) == 0; + _combat_update_critter_outline_for_los(obj, v8); + } + } + + if (rect != NULL) { + rectUnion(rect, &v23, rect); + } + + if (obj == gDude) { + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + Object* obj = objectListNode->obj; + int elev = obj->elevation; + if (elevation < elev) { + break; + } + + if (elevation == elev) { + if ((obj->fid & 0xF000000) >> 24 == 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; + mapSetTransition(&transition); + + _wmMapMarkMapEntranceState(transition.map, transition.elevation, 1); + } + } + } + + objectListNode = objectListNode->next; + } + + _obj_seen[tile >> 3] |= 1 << (tile & 7); + + int v14 = tile % 200 / 2; + int v15 = tile / 200 / 2; + if (v14 != _obj_last_roof_x || v15 != _obj_last_roof_y || elevation != _obj_last_elev) { + int v16 = _square[elevation]->field_0[v14 + 100 * v15]; + int v31 = buildFid(4, (v16 >> 16) & 0xFFF, 0, 0, 0); + int v32 = _square[elevation]->field_0[_obj_last_roof_x + 100 * _obj_last_roof_y]; + int v34 = buildFid(4, 1, 0, 0, 0) == v31; + + if (v34 != _obj_last_is_empty || (((v16 >> 16) & 0xF000) >> 12) != (((v32 >> 16) & 0xF000) >> 12)) { + if (_obj_last_is_empty == 0) { + _tile_fill_roof(_obj_last_roof_x, _obj_last_roof_y, elevation, 1); + } + + if (v34 == 0) { + _tile_fill_roof(v14, v15, elevation, 0); + } + + if (rect != NULL) { + rectUnion(rect, &_scr_size, rect); + } + } + + _obj_last_roof_x = v14; + _obj_last_roof_y = v15; + _obj_last_elev = elevation; + _obj_last_is_empty = v34; + } + + if (rect != NULL) { + Rect r; + objectSetLocation(gEgg, tile, elevation, &r); + rectUnion(rect, &r, rect); + } else { + objectSetLocation(gEgg, tile, elevation, 0); + } + + if (elevation != oldElevation) { + mapSetElevation(elevation); + tileSetCenter(tile, TILE_SET_CENTER_FLAG_0x01 | TILE_SET_CENTER_FLAG_0x02); + if (isInCombat()) { + _game_user_wants_to_quit = 1; + } + } + } else { + if (elevation != _obj_last_elev && (obj->pid >> 24) == OBJ_TYPE_CRITTER) { + _combat_delete_critter(obj); + } + } + + return 0; +} + +// 0x48A9A0 +int _obj_reset_roof() +{ + int fid = buildFid(4, (_square[gDude->elevation]->field_0[_obj_last_roof_x + 100 * _obj_last_roof_y] >> 16) & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + _tile_fill_roof(_obj_last_roof_x, _obj_last_roof_y, gDude->elevation, 1); + } + return 0; +} + +// Sets object fid. +// +// 0x48AA3C +int objectSetFid(Object* obj, int fid, Rect* dirtyRect) +{ + Rect new_rect; + + if (obj == NULL) { + return -1; + } + + if (dirtyRect != NULL) { + objectGetRect(obj, dirtyRect); + + obj->fid = fid; + + objectGetRect(obj, &new_rect); + rectUnion(dirtyRect, &new_rect, dirtyRect); + } else { + obj->fid = fid; + } + + return 0; +} + +// Sets object frame. +// +// 0x48AA84 +int objectSetFrame(Object* obj, int frame, Rect* rect) +{ + Rect new_rect; + Art* art; + CacheEntry* cache_entry; + int framesPerDirection; + + if (obj == NULL) { + return -1; + } + + art = artLock(obj->fid, &cache_entry); + if (art == NULL) { + return -1; + } + + framesPerDirection = art->frameCount; + + artUnlock(cache_entry); + + if (frame >= framesPerDirection) { + return -1; + } + + if (rect != NULL) { + objectGetRect(obj, rect); + obj->frame = frame; + objectGetRect(obj, &new_rect); + rectUnion(rect, &new_rect, rect); + } else { + obj->frame = frame; + } + + return 0; +} + +// 0x48AAF0 +int objectSetNextFrame(Object* obj, Rect* dirtyRect) +{ + Art* art; + CacheEntry* cache_entry; + int framesPerDirection; + int nextFrame; + + if (obj == NULL) { + return -1; + } + + art = artLock(obj->fid, &cache_entry); + if (art == NULL) { + return -1; + } + + framesPerDirection = art->frameCount; + + artUnlock(cache_entry); + + nextFrame = obj->frame + 1; + if (nextFrame >= framesPerDirection) { + nextFrame = 0; + } + + if (dirtyRect != NULL) { + + objectGetRect(obj, dirtyRect); + + obj->frame = nextFrame; + + Rect updatedRect; + objectGetRect(obj, &updatedRect); + rectUnion(dirtyRect, &updatedRect, dirtyRect); + } else { + obj->frame = nextFrame; + } + + return 0; +} + +// 0x48AB60 +// +int objectSetPrevFrame(Object* obj, Rect* dirtyRect) +{ + Art* art; + CacheEntry* cache_entry; + int framesPerDirection; + int prevFrame; + Rect newRect; + + if (obj == NULL) { + return -1; + } + + art = artLock(obj->fid, &cache_entry); + if (art == NULL) { + return -1; + } + + framesPerDirection = art->frameCount; + + artUnlock(cache_entry); + + prevFrame = obj->frame - 1; + if (prevFrame < 0) { + prevFrame = framesPerDirection - 1; + } + + if (dirtyRect != NULL) { + objectGetRect(obj, dirtyRect); + obj->frame = prevFrame; + objectGetRect(obj, &newRect); + rectUnion(dirtyRect, &newRect, dirtyRect); + } else { + obj->frame = prevFrame; + } + + return 0; +} + +// 0x48ABD4 +int objectSetRotation(Object* obj, int direction, Rect* dirtyRect) +{ + if (obj == NULL) { + return -1; + } + + if (direction >= ROTATION_COUNT) { + return -1; + } + + if (dirtyRect != NULL) { + objectGetRect(obj, dirtyRect); + obj->rotation = direction; + + Rect newRect; + objectGetRect(obj, &newRect); + rectUnion(dirtyRect, &newRect, dirtyRect); + } else { + obj->rotation = direction; + } + + return 0; +} + +// 0x48AC20 +int objectRotateClockwise(Object* obj, Rect* dirtyRect) +{ + int rotation = obj->rotation + 1; + if (rotation >= ROTATION_COUNT) { + rotation = ROTATION_NE; + } + + return objectSetRotation(obj, rotation, dirtyRect); +} + +// 0x48AC38 +int objectRotateCounterClockwise(Object* obj, Rect* dirtyRect) +{ + int rotation = obj->rotation - 1; + if (rotation < 0) { + rotation = ROTATION_NW; + } + + return objectSetRotation(obj, rotation, dirtyRect); +} + +// 0x48AC54 +void _obj_rebuild_all_light() +{ + lightResetIntensity(); + + for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + _obj_adjust_light(objectListNode->obj, 0, NULL); + objectListNode = objectListNode->next; + } + } +} + +// 0x48AC90 +int objectSetLight(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); + rectUnion(rect, &new_rect, rect); + } else { + v7 = _obj_turn_on_light(obj, NULL); + } + } else { + obj->lightIntensity = 0; + obj->lightDistance = 0; + } + + return v7; +} + +// 0x48AD04 +int objectGetLightIntensity(Object* obj) +{ + int lightLevel = lightGetLightLevel(); + int lightIntensity = lightGetIntensity(obj->elevation, obj->tile); + + if (obj == gDude) { + lightIntensity -= gDude->lightIntensity; + } + + if (lightIntensity >= lightLevel) { + if (lightIntensity > 0x10000) { + lightIntensity = 0x10000; + } + } 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) { + objectGetRect(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) { + objectGetRect(obj, rect); + } + } + + obj->flags &= ~OBJECT_LIGHTING; + } + + return 0; +} + +// 0x48ADF0 +int objectShow(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) { + objectGetRect(obj, rect); + } + } + + if (obj == gDude) { + if (rect != NULL) { + Rect eggRect; + objectGetRect(gEgg, &eggRect); + rectUnion(rect, &eggRect, rect); + } + } + + return 0; +} + +// 0x48AE68 +int objectHide(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) { + objectGetRect(object, rect); + } + } + + object->flags |= OBJECT_HIDDEN; + + if ((object->outline & OUTLINE_TYPE_MASK) != 0) { + object->outline |= OUTLINE_DISABLED; + } + + if (object == gDude) { + if (rect != NULL) { + Rect eggRect; + objectGetRect(gEgg, &eggRect); + rectUnion(rect, &eggRect, rect); + } + } + + return 0; +} + +// 0x48AEE4 +int objectEnableOutline(Object* object, Rect* rect) +{ + if (object == NULL) { + return -1; + } + + object->outline &= ~OUTLINE_DISABLED; + + if (rect != NULL) { + objectGetRect(object, rect); + } + + return 0; +} + +// 0x48AF00 +int objectDisableOutline(Object* object, Rect* rect) +{ + if (object == NULL) { + return -1; + } + + if ((object->outline & OUTLINE_TYPE_MASK) != 0) { + object->outline |= OUTLINE_DISABLED; + } + + if (rect != NULL) { + objectGetRect(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 (objectGetListNode(object, &node, &previousNode) == -1) { + return -1; + } + + if (rect != NULL) { + objectGetRect(object, rect); + + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile_index = node->obj->tile; + if (tile_index == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile_index] = gObjectListHeadByTile[tile_index]->next; + } + } + + object->flags ^= OBJECT_FLAG_0x08; + + _obj_insert(node); + objectGetRect(object, &v1); + rectUnion(rect, &v1, rect); + } else { + if (previousNode != NULL) { + previousNode->next = node->next; + } else { + int tile = node->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + + object->flags ^= OBJECT_FLAG_0x08; + + _obj_insert(node); + } + + return 0; +} + +// 0x48B0FC +int objectDestroy(Object* object, Rect* rect) +{ + if (object == NULL) { + return -1; + } + + _gmouse_remove_item_outline(object); + + ObjectListNode* node; + ObjectListNode* previousNode; + if (objectGetListNode(object, &node, &previousNode) == 0) { + if (_obj_adjust_light(object, 1, rect) == -1) { + if (rect != NULL) { + objectGetRect(object, rect); + } + } + + if (_obj_remove(node, previousNode) != 0) { + return -1; + } + + return 0; + } + + // NOTE: Uninline. + if (objectListNodeCreate(&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. + objectListNodeCreate(&node); + + node->obj = inventoryItem->item; + node->obj->flags &= ~OBJECT_FLAG_0x400; + _obj_remove(node, node); + + inventoryItem->item = NULL; + } + + if (inventory->items != NULL) { + internal_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) && ((obj->pid >> 24) == OBJ_TYPE_CRITTER) && critterIsActive(obj); +} + +// 0x48B2A8 +bool _obj_portal_is_walk_thru(Object* obj) +{ + if ((obj->pid >> 24) != OBJ_TYPE_SCENERY) { + return false; + } + + Proto* proto; + if (protoGetProto(obj->pid, &proto) == -1) { + return false; + } + + return (proto->scenery.data.generic.field_0 & 0x04) != 0; +} + +// 0x48B2E8 +Object* objectFindById(int a1) +{ + Object* obj = objectFindFirst(); + while (obj != NULL) { + if (obj->id == a1) { + return obj; + } + obj = objectFindNext(); + } + + return NULL; +} + +// Returns root owner of given object. +// +// 0x48B304 +Object* objectGetOwner(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 = gObjectListHeadByTile[tile]; + prev = NULL; + + while (node != NULL) { + next = node->next; + if (_obj_remove(node, prev) == -1) { + prev = node; + } + node = next; + } + } + + node = gObjectListHead; + 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 = 1; + _obj_last_roof_x = -1; +} + +// 0x48B3A8 +Object* objectFindFirst() +{ + gObjectFindElevation = 0; + + ObjectListNode* objectListNode; + for (gObjectFindTile = 0; gObjectFindTile < HEX_GRID_SIZE; gObjectFindTile++) { + objectListNode = gObjectListHeadByTile[gObjectFindTile]; + if (objectListNode) { + break; + } + } + + if (gObjectFindTile == HEX_GRID_SIZE) { + gObjectFindLastObjectListNode = NULL; + return NULL; + } + + while (objectListNode != NULL) { + if (artIsObjectTypeHidden((objectListNode->obj->fid & 0xF000000) >> 24) == 0) { + gObjectFindLastObjectListNode = objectListNode; + return objectListNode->obj; + } + objectListNode = objectListNode->next; + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x48B41C +Object* objectFindNext() +{ + if (gObjectFindLastObjectListNode == NULL) { + return NULL; + } + + ObjectListNode* objectListNode = gObjectFindLastObjectListNode->next; + + while (gObjectFindTile < HEX_GRID_SIZE) { + if (objectListNode == NULL) { + objectListNode = gObjectListHeadByTile[gObjectFindTile++]; + } + + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (!artIsObjectTypeHidden((object->fid & 0xF000000) >> 24)) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + objectListNode = objectListNode->next; + } + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x48B48C +Object* objectFindFirstAtElevation(int elevation) +{ + gObjectFindElevation = elevation; + gObjectFindTile = 0; + + for (gObjectFindTile = 0; gObjectFindTile < HEX_GRID_SIZE; gObjectFindTile++) { + ObjectListNode* objectListNode = gObjectListHeadByTile[gObjectFindTile]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation == elevation) { + if (!artIsObjectTypeHidden((object->fid & 0xF000000) >> 24)) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + } + objectListNode = objectListNode->next; + } + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x48B510 +Object* objectFindNextAtElevation() +{ + if (gObjectFindLastObjectListNode == NULL) { + return NULL; + } + + ObjectListNode* objectListNode = gObjectFindLastObjectListNode->next; + + while (gObjectFindTile < HEX_GRID_SIZE) { + if (objectListNode == NULL) { + objectListNode = gObjectListHeadByTile[gObjectFindTile++]; + } + + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation == gObjectFindElevation) { + if (!artIsObjectTypeHidden((object->fid & 0xF000000) >> 24)) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + } + objectListNode = objectListNode->next; + } + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x48B5A8 +Object* objectFindFirstAtLocation(int elevation, int tile) +{ + gObjectFindElevation = elevation; + gObjectFindTile = tile; + + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation == elevation) { + if (!artIsObjectTypeHidden((object->fid & 0xF000000) >> 24)) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + } + objectListNode = objectListNode->next; + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x48B608 +Object* objectFindNextAtLocation() +{ + if (gObjectFindLastObjectListNode == NULL) { + return NULL; + } + + ObjectListNode* objectListNode = gObjectFindLastObjectListNode->next; + + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation == gObjectFindElevation) { + if (!artIsObjectTypeHidden((object->fid & 0xF000000) >> 24)) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + } + objectListNode = objectListNode->next; + } + + gObjectFindLastObjectListNode = NULL; + return NULL; +} + +// 0x0x48B66C +void objectGetRect(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 = artLock(obj->fid, &artHandle); + if (art == NULL) { + rect->left = 0; + rect->top = 0; + rect->right = 0; + rect->bottom = 0; + return; + } + + int width; + int height; + artGetSize(art, obj->frame, obj->rotation, &width, &height); + + if (obj->tile == -1) { + rect->left = obj->sx; + rect->top = obj->sy; + rect->right = obj->sx + width - 1; + rect->bottom = obj->sy + height - 1; + } else { + int tileScreenY; + int tileScreenX; + if (tileToScreenXY(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->left = tileScreenX - width / 2; + rect->top = tileScreenY - height + 1; + rect->right = width + rect->left - 1; + rect->bottom = tileScreenY; + } else { + rect->left = 0; + rect->top = 0; + rect->right = 0; + rect->bottom = 0; + isOutlined = false; + } + } + + artUnlock(artHandle); + + if (isOutlined) { + rect->left--; + rect->top--; + rect->right++; + rect->bottom++; + } +} + +// 0x48B7F8 +bool _obj_occupied(int tile, int elevation) +{ + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + if (objectListNode->obj->elevation == elevation + && objectListNode->obj != gGameMouseBouncingCursor + && objectListNode->obj != gGameMouseHexCursor) { + 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 = gObjectListHeadByTile[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 = (v7->fid & 0xF000000) >> 24; + 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 = tileGetTileInDirection(tile, rotation, 1); + if (hexGridTileIsValid(neighboor)) { + objectListNode = gObjectListHeadByTile[neighboor]; + while (objectListNode != NULL) { + v7 = objectListNode->obj; + if ((v7->flags & OBJECT_FLAG_0x800) != 0) { + if (v7->elevation == elev) { + if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) { + type = (v7->fid & 0xF000000) >> 24; + 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 = gObjectListHeadByTile[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_FLAG_0x80000000) == 0) && candidate != obj) { + int type = (candidate->fid & 0xF000000) >> 24; + 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 = tileGetTileInDirection(tile, rotation, 1); + if (!hexGridTileIsValid(adjacentTile)) { + continue; + } + + ObjectListNode* objectListItem = gObjectListHeadByTile[adjacentTile]; + while (objectListItem != NULL) { + Object* candidate = objectListItem->obj; + unsigned int flags = candidate->flags; + if ((flags & OBJECT_FLAG_0x800) != 0) { + if (candidate->elevation == elev) { + if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != obj) { + int type = (candidate->fid & 0xF000000) >> 24; + 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 = gObjectListHeadByTile[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 = (object->fid & 0xF000000) >> 24; + 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 = tileGetTileInDirection(tile, rotation, 1); + if (!hexGridTileIsValid(candidate)) { + continue; + } + + objectListNode = gObjectListHeadByTile[candidate]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if ((object->flags & OBJECT_FLAG_0x800) != 0) { + if (object->elevation == elevation) { + if ((object->flags & OBJECT_HIDDEN) == 0 + && (object->flags & OBJECT_NO_BLOCK) == 0 + && object != a1) { + int objectType = (object->fid & 0xF000000) >> 24; + 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 = gObjectListHeadByTile[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 = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation == elevation + && (object->flags & OBJECT_HIDDEN) == 0 + && (object->flags & OBJECT_FLAG_0x20000000) == 0 + && object != a1) { + int objectType = (object->fid & 0xF000000) >> 24; + if (objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) { + return object; + } + } + objectListNode = objectListNode->next; + } + + return NULL; +} + +// 0x48BBD4 +int objectGetDistanceBetween(Object* object1, Object* object2) +{ + if (object1 == NULL || object2 == NULL) { + return 0; + } + + int distance = tileDistanceBetween(object1->tile, object2->tile); + + if ((object1->flags & OBJECT_FLAG_0x800) != 0) { + distance -= 1; + } + + if ((object2->flags & OBJECT_FLAG_0x800) != 0) { + distance -= 1; + } + + if (distance < 0) { + distance = 0; + } + + return distance; +} + +// 0x48BC08 +int objectGetDistanceBetweenTiles(Object* object1, int tile1, Object* object2, int tile2) +{ + if (object1 == NULL || object2 == NULL) { + return 0; + } + + int distance = tileDistanceBetween(tile1, tile2); + + if ((object1->flags & OBJECT_FLAG_0x800) != 0) { + distance -= 1; + } + + if ((object2->flags & OBJECT_FLAG_0x800) != 0) { + distance -= 1; + } + + if (distance < 0) { + distance = 0; + } + + return distance; +} + +// 0x48BC38 +int objectListCreate(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 = gObjectListHeadByTile[index]; + while (objectListNode != NULL) { + Object* obj = objectListNode->obj; + if ((obj->flags & OBJECT_HIDDEN) == 0 + && obj->elevation == elevation + && ((obj->fid & 0xF000000) >> 24) == objectType) { + count++; + } + objectListNode = objectListNode->next; + } + } + } else { + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + Object* obj = objectListNode->obj; + if ((obj->flags & OBJECT_HIDDEN) == 0 + && obj->elevation == elevation + && ((objectListNode->obj->fid & 0xF000000) >> 24) == objectType) { + count++; + } + objectListNode = objectListNode->next; + } + } + + if (count == 0) { + return 0; + } + + Object** objects = *objectListPtr = internal_malloc(sizeof(*objects) * count); + if (objects == NULL) { + return -1; + } + + if (tile == -1) { + for (int index = 0; index < HEX_GRID_SIZE; index++) { + ObjectListNode* objectListNode = gObjectListHeadByTile[index]; + while (objectListNode) { + Object* obj = objectListNode->obj; + if ((obj->flags & OBJECT_HIDDEN) == 0 + && obj->elevation == elevation + && ((obj->fid & 0xF000000) >> 24) == objectType) { + *objects++ = obj; + } + objectListNode = objectListNode->next; + } + } + } else { + ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; + while (objectListNode != NULL) { + Object* obj = objectListNode->obj; + if ((obj->flags & OBJECT_HIDDEN) == 0 + && obj->elevation == elevation + && ((obj->fid & 0xF000000) >> 24) == objectType) { + *objects++ = obj; + } + objectListNode = objectListNode->next; + } + } + + return count; +} + +// 0x48BDCC +void objectListFree(Object** objectList) +{ + if (objectList != NULL) { + internal_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) { + int t = (b << 8) + lightModifier; + b = _intensityColorTable[t]; + } + + *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]; + index <<= 8; + index += lightModifier; + *dest = _intensityColorTable[index]; + } + + 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) { + int off = (b << 8) + light; + b = _intensityColorTable[off]; + unsigned char m = *mask; + if (m != 0) { + unsigned char d = *dest; + int off = (d << 8) + 128 - m; + int q = _intensityColorTable[off]; + + off = (b << 8) + m; + m = _intensityColorTable[off]; + + off = (m << 8) + q; + b = _colorMixAddTable[off]; + } + *dest = b; + } + + src++; + dest++; + mask++; + } + + src += srcStep; + dest += destStep; + mask += maskStep; + } +} + +// 0x48C2B4 +int objectSetOutline(Object* obj, int outlineType, Rect* rect) +{ + if (obj == NULL) { + return -1; + } + + if ((obj->outline & OUTLINE_TYPE_MASK) != 0) { + return -1; + } + + if ((obj->flags & OBJECT_FLAG_0x1000) != 0) { + return -1; + } + + obj->outline = outlineType; + + if ((obj->flags & OBJECT_HIDDEN) != 0) { + obj->outline |= OUTLINE_DISABLED; + } + + if (rect != NULL) { + objectGetRect(obj, rect); + } + + return 0; +} + +// 0x48C2F0 +int objectClearOutline(Object* object, Rect* rect) +{ + if (object == NULL) { + return -1; + } + + if (rect != NULL) { + objectGetRect(object, rect); + } + + object->outline = 0; + + return 0; +} + +// 0x48C340 +int _obj_intersects_with(Object* object, int x, int y) +{ + int flags = 0; + + if (object == gEgg || (object->flags & OBJECT_HIDDEN) == 0) { + CacheEntry* handle; + Art* art = artLock(object->fid, &handle); + if (art != NULL) { + int width; + int height; + artGetSize(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; + tileToScreenXY(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 = artGetFrameData(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_FLAG_0x8000) == 0) { + flags &= ~0x03; + flags |= 0x02; + } + } else { + int type = (object->fid & 0xF000000) >> 24; + if (type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { + Proto* proto; + protoGetProto(object->pid, &proto); + + bool v20; + int extendedFlags = proto->scenery.extendedFlags; + if ((extendedFlags & 0x8000000) != 0 || (extendedFlags & 0x80000000) != 0) { + v20 = _tile_in_front_of(object->tile, gDude->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, gDude->tile) || _tile_to_right_of(gDude->tile, object->tile); + } else if ((extendedFlags & 0x20000000) != 0) { + v20 = _tile_in_front_of(object->tile, gDude->tile) && _tile_to_right_of(gDude->tile, object->tile); + } else { + v20 = _tile_to_right_of(gDude->tile, object->tile); + } + + if (v20) { + if (_obj_intersects_with(gEgg, x, y) != 0) { + flags |= 0x04; + } + } + } + } + } + } + } + + artUnlock(handle); + } + } + + return flags; +} + +// 0x48C5C4 +int _obj_create_intersect_list(int x, int y, int elevation, int objectType, ObjectWithFlags** entriesPtr) +{ + int v5 = tileFromScreenXY(x - 320, y - 240, elevation); + *entriesPtr = NULL; + + if (_updateHexArea <= 0) { + return 0; + } + + int count = 0; + + int parity = gCenterTile & 1; + for (int index = 0; index < _updateHexArea; index++) { + int v7 = _orderTable[parity][index]; + if (_offsetDivTable[v7] < 30 && _offsetModTable[v7] < 20) { + ObjectListNode* objectListNode = gObjectListHeadByTile[_offsetTable[parity][v7] + v5]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (object->elevation > elevation) { + break; + } + + if (object->elevation == elevation + && (objectType == -1 || (object->fid & 0xF000000) >> 24 == objectType) + && object != gEgg) { + int flags = _obj_intersects_with(object, x, y); + if (flags != 0) { + ObjectWithFlags* entries = internal_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) { + internal_free(*entriesPtr); + *entriesPtr = NULL; + } +} + +// 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 = gObjectListHeadByTile[v5]; obj_entry != NULL; obj_entry = obj_entry->next) { + if (obj_entry->obj->elevation == gDude->elevation) { + obj_entry->obj->flags |= OBJECT_FLAG_0x40000000; + } + } + } + } + v8 *= 2; + } + } + v7 += 8; + } + + memset(_obj_seen, 0, 5001); +} + +// 0x48C8E4 +char* objectGetName(Object* obj) +{ + int objectType = (obj->fid & 0xF000000) >> 24; + switch (objectType) { + case OBJ_TYPE_ITEM: + return itemGetName(obj); + case OBJ_TYPE_CRITTER: + return critterGetName(obj); + default: + return protoGetName(obj->pid); + } +} + +// 0x48C914 +char* objectGetDescription(Object* obj) +{ + if (((obj->fid & 0xF000000) >> 24) == OBJ_TYPE_ITEM) { + return itemGetDescription(obj); + } + + return protoGetDescription(obj->pid); +} + +// Warm objects cache? +// +// 0x48C938 +void _obj_preload_art_cache(int flags) +{ + if (gObjectFids == 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(gObjectFids, gObjectFidsLength, sizeof(*gObjectFids), _obj_preload_sort); + + int v11 = gObjectFidsLength; + int v12 = gObjectFidsLength; + + if ((gObjectFids[v12 - 1] & 0xF000000) >> 24 == 3) { + int v13 = 0; + do { + v11--; + v13 = (gObjectFids[v12 - 1] & 0xF000000) >> 24; + v12--; + } while (v13 == 3); + v11++; + } + + CacheEntry* cache_handle; + if (artLock(*gObjectFids, &cache_handle) != NULL) { + artUnlock(cache_handle); + } + + for (int i = 1; i < v11; i++) { + if (gObjectFids[i - 1] != gObjectFids[i]) { + if (artLock(gObjectFids[i], &cache_handle) != NULL) { + artUnlock(cache_handle); + } + } + } + + for (int i = 0; i < 4096; i++) { + if (arr[i] != 0) { + int fid = buildFid(4, i, 0, 0, 0); + if (artLock(fid, &cache_handle) != NULL) { + artUnlock(cache_handle); + } + } + } + + for (int i = v11; i < gObjectFidsLength; i++) { + if (gObjectFids[i - 1] != gObjectFids[i]) { + if (artLock(gObjectFids[i], &cache_handle) != NULL) { + artUnlock(cache_handle); + } + } + } + + internal_free(gObjectFids); + gObjectFids = NULL; + + gObjectFidsLength = 0; +} + +// 0x48CB88 +int _obj_offset_table_init() +{ + int i; + + if (_offsetTable[0] != NULL) { + return -1; + } + + if (_offsetTable[1] != NULL) { + return -1; + } + + _offsetTable[0] = internal_malloc(sizeof(int) * _updateHexArea); + if (_offsetTable[0] == NULL) { + goto err; + } + + _offsetTable[1] = internal_malloc(sizeof(int) * _updateHexArea); + if (_offsetTable[1] == NULL) { + goto err; + } + + for (int i = 0; i < 2; i++) { + int tile = tileFromScreenXY(_updateAreaPixelBounds, dword_639D94, 0); + if (tile != -1) { + int* v5 = _offsetTable[gCenterTile & 1]; + int v20; + int v19; + tileToScreenXY(tile, &v20, &v19, 0); + + int v23 = 16; + v20 += 16; + v19 += 8; + if (v20 > _updateAreaPixelBounds) { + v23 = -v23; + } + + int v6 = v20; + for (int j = 0; j < _updateHexHeight; j++) { + for (int m = 0; m < _updateHexWidth; m++) { + int t = tileFromScreenXY(v6, v19, 0); + if (t == -1) { + goto err; + } + + v6 += 32; + *v5++ = t - tile; + } + + v6 = v23 + v20; + v19 += 12; + v23 = -v23; + } + } + + if (tileSetCenter(gCenterTile + 1, 2) == -1) { + goto err; + } + } + + _offsetDivTable = internal_malloc(sizeof(int) * _updateHexArea); + if (_offsetDivTable == NULL) { + goto err; + } + + for (i = 0; i < _updateHexArea; i++) { + _offsetDivTable[i] = i / _updateHexWidth; + } + + _offsetModTable = internal_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 +void _obj_offset_table_exit() +{ + if (_offsetModTable != NULL) { + internal_free(_offsetModTable); + _offsetModTable = NULL; + } + + if (_offsetDivTable != NULL) { + internal_free(_offsetDivTable); + _offsetDivTable = NULL; + } + + if (_offsetTable[1] != NULL) { + internal_free(_offsetTable[1]); + _offsetTable[1] = NULL; + } + + if (_offsetTable[0] != NULL) { + internal_free(_offsetTable[0]); + _offsetTable[0] = NULL; + } +} + +// 0x48CE10 +int _obj_order_table_init() +{ + if (_orderTable[0] != NULL || _orderTable[1] != NULL) { + return -1; + } + + _orderTable[0] = internal_malloc(sizeof(int) * _updateHexArea); + if (_orderTable[0] == NULL) { + goto err; + } + + _orderTable[1] = internal_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 +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 +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 +void _obj_order_table_exit() +{ + if (_orderTable[1] != NULL) { + internal_free(_orderTable[1]); + _orderTable[1] = NULL; + } + + if (_orderTable[0] != NULL) { + internal_free(_orderTable[0]); + _orderTable[0] = NULL; + } +} + +// 0x48CF8C +int _obj_render_table_init() +{ + if (_renderTable != NULL) { + return -1; + } + + _renderTable = internal_malloc(sizeof(*_renderTable) * _updateHexArea); + if (_renderTable == NULL) { + return -1; + } + + for (int index = 0; index < _updateHexArea; index++) { + _renderTable[index] = NULL; + } + + return 0; +} + +// NOTE: Inlined. +// +// 0x48D000 +void _obj_render_table_exit() +{ + if (_renderTable != NULL) { + internal_free(_renderTable); + _renderTable = NULL; + } +} + +// 0x48D020 +void _obj_light_table_init() +{ + for (int s = 0; s < 2; s++) { + int v4 = gCenterTile + s; + for (int i = 0; i < 6; i++) { + int v15 = 8; + int* p = _light_offsets[v4 & 1][i]; + for (int j = 0; j < 8; j++) { + int tile = tileGetTileInDirection(v4, (i + 1) % 6, j); + + for (int m = 0; m < v15; m++) { + *p++ = tileGetTileInDirection(tile, i, m + 1) - v4; + } + + v15--; + } + } + } +} + +// 0x48D1E4 +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 +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 ((object->pid >> 24) == 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 (objectWrite(object, stream) == -1) { + return -1; + } + + if ((object->pid >> 24) == 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 (fileWriteInt32(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 (objectAllocate(&obj) == -1) { + *objectPtr = NULL; + return -1; + } + + if (objectRead(obj, stream) != 0) { + *objectPtr = NULL; + return -1; + } + + if (obj->sid != -1) { + Script* script; + if (scriptGetScript(obj->sid, &script) == -1) { + obj->sid = -1; + } else { + script->owner = obj; + } + } + + _obj_fix_violence_settings(&(obj->fid)); + + if (!_art_fid_valid(obj->fid)) { + debugPrint("\nError: invalid object art fid: %u\n", obj->fid); + // NOTE: Uninline. + objectDeallocate(&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 = internal_malloc(sizeof(*inventoryItems) * inventory->capacity); + if (inventoryItems == NULL) { + return -1; + } + + for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) { + InventoryItem* inventoryItem = &(inventoryItems[inventoryItemIndex]); + if (fileReadInt32(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 = gDude->sid; + + gDude->flags &= ~OBJECT_TEMPORARY; + gDude->sid = -1; + + int rc = _obj_save_obj(stream, gDude); + + gDude->sid = field_78; + gDude->flags |= OBJECT_TEMPORARY; + + if (fileWriteInt32(stream, gCenterTile) == -1) { + fileClose(stream); + return -1; + } + + return rc; +} + +// obj_load_dude +// 0x48D600 +int _obj_load_dude(File* stream) +{ + int savedTile = gDude->tile; + int savedElevation = gDude->elevation; + int savedRotation = gDude->rotation; + int savedOid = gDude->id; + + scriptsClearDudeScript(); + + Object* temp; + int rc = _obj_load_obj(stream, &temp, -1, NULL); + + memcpy(gDude, temp, sizeof(*gDude)); + + gDude->flags |= OBJECT_TEMPORARY; + + scriptsClearDudeScript(); + + gDude->id = savedOid; + + scriptsSetDudeScript(); + + int newTile = gDude->tile; + gDude->tile = savedTile; + + int newElevation = gDude->elevation; + gDude->elevation = savedElevation; + + int newRotation = gDude->rotation; + gDude->rotation = newRotation; + + scriptsSetDudeScript(); + + if (rc != -1) { + objectSetLocation(gDude, newTile, newElevation, NULL); + objectSetRotation(gDude, newRotation, NULL); + } + + // Set ownership of inventory items from temporary instance to dude. + Inventory* inventory = &(gDude->data.inventory); + for (int index = 0; index < inventory->length; index++) { + InventoryItem* inventoryItem = &(inventory->items[index]); + inventoryItem->item->owner = gDude; + } + + _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 (objectDestroy(temp, NULL) == -1) { + debugPrint("\nError: obj_load_dude: Can't destroy temp object!\n"); + } + + _inven_reset_dude(); + + int tile; + if (fileReadInt32(stream, &tile) == -1) { + fileClose(stream); + return -1; + } + + tileSetCenter(tile, TILE_SET_CENTER_FLAG_0x01 | TILE_SET_CENTER_FLAG_0x02); + + return rc; +} + +// 0x48D778 +int objectAllocate(Object** objectPtr) +{ + if (objectPtr == NULL) { + return -1; + } + + Object* object = *objectPtr = internal_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 +void objectDeallocate(Object** objectPtr) +{ + if (objectPtr == NULL) { + return; + } + + if (*objectPtr == NULL) { + return; + } + + internal_free(*objectPtr); + + *objectPtr = NULL; +} + +// NOTE: Inlined. +// +// 0x48D818 +int objectListNodeCreate(ObjectListNode** nodePtr) +{ + if (nodePtr == NULL) { + return -1; + } + + ObjectListNode* node = *nodePtr = internal_malloc(sizeof(*node)); + if (node == NULL) { + return -1; + } + + node->obj = NULL; + node->next = NULL; + + return 0; +} + +// NOTE: Inlined. +// +// 0x48D84C +void objectListNodeDestroy(ObjectListNode** nodePtr) +{ + if (nodePtr == NULL) { + return; + } + + if (*nodePtr == NULL) { + return; + } + + internal_free(*nodePtr); + + *nodePtr = NULL; +} + +// 0x48D86C +int objectGetListNode(Object* object, ObjectListNode** nodePtr, ObjectListNode** previousNodePtr) +{ + if (object == NULL) { + return -1; + } + + if (nodePtr == NULL) { + return -1; + } + + int tile = object->tile; + if (tile != -1) { + *nodePtr = gObjectListHeadByTile[tile]; + } else { + *nodePtr = gObjectListHead; + } + + 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 +void _obj_insert(ObjectListNode* objectListNode) +{ + ObjectListNode** objectListNodePtr; + + if (objectListNode == NULL) { + return; + } + + if (objectListNode->obj->tile == -1) { + objectListNodePtr = &gObjectListHead; + } else { + Art* art = NULL; + CacheEntry* cacheHandle = NULL; + + objectListNodePtr = &(gObjectListHeadByTile[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_FLAG_0x08) == 0 && (objectListNode->obj->flags & OBJECT_FLAG_0x08) != 0) { + break; + } + + if ((obj->flags & OBJECT_FLAG_0x08) == (objectListNode->obj->flags & OBJECT_FLAG_0x08)) { + bool v11 = false; + CacheEntry* a2; + Art* v12 = artLock(obj->fid, &a2); + if (v12 != NULL) { + + if (art == NULL) { + art = artLock(objectListNode->obj->fid, &cacheHandle); + } + + // TODO: Incomplete. + + artUnlock(a2); + + if (v11) { + break; + } + } + } + } + + objectListNodePtr = &((*objectListNodePtr)->next); + } + + if (art != NULL) { + artUnlock(cacheHandle); + } + } + + objectListNode->next = *objectListNodePtr; + *objectListNodePtr = objectListNode; +} + +// 0x48DA58 +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) { + scriptExecProc(a1->obj->sid, SCRIPT_PROC_DESTROY); + scriptRemove(a1->obj->sid); + } + + if (a1 != a2) { + if (a2 != NULL) { + a2->next = a1->next; + } else { + int tile = a1->obj->tile; + if (tile == -1) { + gObjectListHead = gObjectListHead->next; + } else { + gObjectListHeadByTile[tile] = gObjectListHeadByTile[tile]->next; + } + } + } + + // NOTE: Uninline. + objectDeallocate(&(a1->obj)); + + // NOTE: Uninline. + objectListNodeDestroy(&a1); + + return 0; +} + +// 0x48DB28 +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) { + objectGetRect(node->obj, rect); + } + } + + return 0; +} + +// 0x48DC28 +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 ? lightDecreaseIntensity : lightIncreaseIntensity; + adjustLightIntensity(obj->elevation, obj->tile, obj->lightIntensity); + + Rect objectRect; + objectGetRect(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 = gObjectListHeadByTile[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; + objectGetRect(objectListNode->obj, &v29); + rectUnion(&objectRect, &v29, &objectRect); + + v14 = (objectListNode->obj->flags & OBJECT_FLAG_0x20000000) == 0; + + if ((objectListNode->obj->fid & 0xF000000) >> 24 == OBJ_TYPE_WALL) { + if ((objectListNode->obj->flags & OBJECT_FLAG_0x08) == 0) { + Proto* proto; + protoGetProto(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; + tileToScreenXY(obj->tile, &x, &y, obj->elevation); + x += 16; + y += 8; + + x -= rect->right / 2; + y -= rect->bottom / 2; + + rectOffset(rect, x, y); + rectUnion(rect, &objectRect, rect); + } + + return 0; +} + +// 0x48EABC +void objectDrawOutline(Object* object, Rect* rect) +{ + CacheEntry* cacheEntry; + Art* art = artLock(object->fid, &cacheEntry); + if (art == NULL) { + return; + } + + int frameWidth = 0; + int frameHeight = 0; + artGetSize(art, object->frame, object->rotation, &frameWidth, &frameHeight); + + Rect v49; + v49.left = 0; + v49.top = 0; + v49.right = frameWidth - 1; + + // FIXME: I'm not sure why it ignores frameHeight and makes separate call + // to obtain height. + int v8 = artGetHeight(art, object->frame, object->rotation); + v49.bottom = v8 - 1; + + Rect objectRect; + if (object->tile == -1) { + objectRect.left = object->sx; + objectRect.top = object->sy; + objectRect.right = object->sx + frameWidth - 1; + objectRect.bottom = object->sy + frameHeight - 1; + } else { + int x; + int y; + tileToScreenXY(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.left = x - frameWidth / 2; + objectRect.top = y - (frameHeight - 1); + objectRect.right = objectRect.left + frameWidth - 1; + objectRect.bottom = y; + + object->sx = objectRect.left; + object->sy = objectRect.top; + } + + Rect v32; + rectCopy(&v32, rect); + + v32.left--; + v32.top--; + v32.right++; + v32.bottom++; + + rectIntersection(&v32, &gObjectsWindowRect, &v32); + + if (rectIntersection(&objectRect, &v32, &objectRect) == 0) { + v49.left += objectRect.left - object->sx; + v49.top += objectRect.top - object->sy; + v49.right = v49.left + (objectRect.right - objectRect.left); + v49.bottom = v49.top + (objectRect.bottom - objectRect.top); + + unsigned char* src = artGetFrameData(art, object->frame, object->rotation); + + unsigned char* dest = gObjectsWindowBuffer + gObjectsWindowPitch * object->sy + object->sx; + int destStep = gObjectsWindowPitch - 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 - gObjectsWindowBuffer; + for (int x = 0; x < frameWidth; x++) { + v22 = dest14 - gObjectsWindowBuffer; + if (*src15 != 0 && cycle) { + if (x >= v49.left && x <= v49.right && y >= v49.top && y <= v49.bottom && v22 > 0 && v22 % gObjectsWindowPitch != 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.left && x <= v49.right && y >= v49.top && y <= v49.bottom) { + 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 < gObjectsWindowBufferSize) { + int v23 = frameWidth - 1; + if (v23 >= v49.left && v23 <= v49.right && y >= v49.top && y <= v49.bottom) { + 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.left && x <= v49.right && y >= v49.top && y <= v49.bottom) { + unsigned char* v29 = dest27 - gObjectsWindowPitch; + if (v29 >= gObjectsWindowBuffer) { + if (v53) { + *v29 = v48[(v47[v28] << 8) + *v29]; + } else { + *v29 = v28; + } + } + } + cycle = false; + } else if (*src27 == 0 && !cycle) { + if (x >= v49.left && x <= v49.right && y >= v49.top && y <= v49.bottom) { + if (v53) { + *dest27 = v48[(v47[v28] << 8) + *dest27]; + } else { + *dest27 = v28; + } + } + cycle = true; + } + + dest27 += gObjectsWindowPitch; + src27 += frameWidth; + } + + if (src27[-frameWidth] != 0) { + if (dest27 - gObjectsWindowBuffer < gObjectsWindowBufferSize) { + int y = frameHeight - 1; + if (x >= v49.left && x <= v49.right && y >= v49.top && y <= v49.bottom) { + if (v53) { + *dest27 = v48[(v47[v28] << 8) + *dest27]; + } else { + *dest27 = v28; + } + } + } + } + } + } + + artUnlock(cacheEntry); +} + +// 0x48F1B0 +void _obj_render_object(Object* object, Rect* rect, int light) +{ + int type = (object->fid & 0xF000000) >> 24; + if (artIsObjectTypeHidden(type)) { + return; + } + + CacheEntry* cacheEntry; + Art* art = artLock(object->fid, &cacheEntry); + if (art == NULL) { + return; + } + + int frameWidth = artGetWidth(art, object->frame, object->rotation); + int frameHeight = artGetHeight(art, object->frame, object->rotation); + + Rect objectRect; + if (object->tile == -1) { + objectRect.left = object->sx; + objectRect.top = object->sy; + objectRect.right = object->sx + frameWidth - 1; + objectRect.bottom = object->sy + frameHeight - 1; + } else { + int objectScreenX; + int objectScreenY; + tileToScreenXY(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.left = objectScreenX - frameWidth / 2; + objectRect.top = objectScreenY - (frameHeight - 1); + objectRect.right = objectRect.left + frameWidth - 1; + objectRect.bottom = objectScreenY; + + object->sx = objectRect.left; + object->sy = objectRect.top; + } + + if (rectIntersection(&objectRect, rect, &objectRect) != 0) { + artUnlock(cacheEntry); + return; + } + + unsigned char* src = artGetFrameData(art, object->frame, object->rotation); + unsigned char* src2 = src; + int v50 = objectRect.left - object->sx; + int v49 = objectRect.top - object->sy; + src += frameWidth * v49 + v50; + int objectWidth = objectRect.right - objectRect.left + 1; + int objectHeight = objectRect.bottom - objectRect.top + 1; + + if (type == 6) { + blitBufferToBufferTrans(src, + objectWidth, + objectHeight, + frameWidth, + gObjectsWindowBuffer + gObjectsWindowPitch * objectRect.top + objectRect.left, + gObjectsWindowPitch); + artUnlock(cacheEntry); + return; + } + + if (type == 2 || type == 3) { + if ((gDude->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_FLAG_0xFC000) == 0) { + Proto* proto; + protoGetProto(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, gDude->tile); + if (!v17 + || !_tile_to_right_of(object->tile, gDude->tile) + || (object->flags & OBJECT_FLAG_0x10000000) == 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, gDude->tile) + || _tile_to_right_of(gDude->tile, object->tile); + } else if ((extendedFlags & 0x20000000) != 0) { + v17 = _tile_in_front_of(object->tile, gDude->tile) + && _tile_to_right_of(gDude->tile, object->tile); + } else { + v17 = _tile_to_right_of(gDude->tile, object->tile); + if (v17 + && _tile_in_front_of(gDude->tile, object->tile) + && (object->flags & OBJECT_FLAG_0x10000000) != 0) { + v17 = 0; + } + } + + if (v17) { + CacheEntry* eggHandle; + Art* egg = artLock(gEgg->fid, &eggHandle); + if (egg == NULL) { + return; + } + + int eggWidth; + int eggHeight; + artGetSize(egg, 0, 0, &eggWidth, &eggHeight); + + int eggScreenX; + int eggScreenY; + tileToScreenXY(gEgg->tile, &eggScreenX, &eggScreenY, gEgg->elevation); + eggScreenX += 16; + eggScreenY += 8; + + eggScreenX += egg->xOffsets[0]; + eggScreenY += egg->yOffsets[0]; + + eggScreenX += gEgg->x; + eggScreenY += gEgg->y; + + Rect eggRect; + eggRect.left = eggScreenX - eggWidth / 2; + eggRect.top = eggScreenY - (eggHeight - 1); + eggRect.right = eggRect.left + eggWidth - 1; + eggRect.bottom = eggScreenY; + + gEgg->sx = eggRect.left; + gEgg->sy = eggRect.top; + + Rect updatedEggRect; + if (rectIntersection(&eggRect, &objectRect, &updatedEggRect) == 0) { + Rect rects[4]; + + rects[0].left = objectRect.left; + rects[0].top = objectRect.top; + rects[0].right = objectRect.right; + rects[0].bottom = updatedEggRect.top - 1; + + rects[1].left = objectRect.left; + rects[1].top = updatedEggRect.top; + rects[1].right = updatedEggRect.left - 1; + rects[1].bottom = updatedEggRect.bottom; + + rects[2].left = updatedEggRect.right + 1; + rects[2].top = updatedEggRect.top; + rects[2].right = objectRect.right; + rects[2].bottom = updatedEggRect.bottom; + + rects[3].left = objectRect.left; + rects[3].top = updatedEggRect.bottom + 1; + rects[3].right = objectRect.right; + rects[3].bottom = objectRect.bottom; + + for (int i = 0; i < 4; i++) { + Rect* v21 = &(rects[i]); + if (v21->left <= v21->right && v21->top <= v21->bottom) { + unsigned char* sp = src + frameWidth * (v21->top - objectRect.top) + (v21->left - objectRect.left); + _dark_trans_buf_to_buf(sp, v21->right - v21->left + 1, v21->bottom - v21->top + 1, frameWidth, gObjectsWindowBuffer, v21->left, v21->top, gObjectsWindowPitch, light); + } + } + + unsigned char* mask = artGetFrameData(egg, 0, 0); + _intensity_mask_buf_to_buf( + src + frameWidth * (updatedEggRect.top - objectRect.top) + (updatedEggRect.left - objectRect.left), + updatedEggRect.right - updatedEggRect.left + 1, + updatedEggRect.bottom - updatedEggRect.top + 1, + frameWidth, + gObjectsWindowBuffer + gObjectsWindowPitch * updatedEggRect.top + updatedEggRect.left, + gObjectsWindowPitch, + mask + eggWidth * (updatedEggRect.top - eggRect.top) + (updatedEggRect.left - eggRect.left), + eggWidth, + light); + artUnlock(eggHandle); + artUnlock(cacheEntry); + return; + } + + artUnlock(eggHandle); + } + } + } + + switch (object->flags & OBJECT_FLAG_0xFC000) { + case OBJECT_FLAG_0x4000: + _dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, light, _redBlendTable, _commonGrayTable); + break; + case OBJECT_FLAG_0x10000: + _dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, 0x10000, _wallBlendTable, _commonGrayTable); + break; + case OBJECT_FLAG_0x20000: + _dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, light, _glassBlendTable, _glassGrayTable); + break; + case OBJECT_FLAG_0x40000: + _dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, light, _steamBlendTable, _commonGrayTable); + break; + case OBJECT_FLAG_0x80000: + _dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, light, _energyBlendTable, _commonGrayTable); + break; + default: + _dark_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, gObjectsWindowBuffer, objectRect.left, objectRect.top, gObjectsWindowPitch, light); + break; + } + + artUnlock(cacheEntry); +} + +// Updates fid according to current violence level. +// +// 0x48FA14 +void _obj_fix_violence_settings(int* fid) +{ + if ((*fid >> 24) != OBJ_TYPE_CRITTER) { + return; + } + + bool shouldResetViolenceLevel = false; + if (gViolenceLevel == -1) { + if (!configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &gViolenceLevel)) { + gViolenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; + } + shouldResetViolenceLevel = true; + } + + int start; + int end; + + switch (gViolenceLevel) { + 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 & 0xFF0000) >> 16; + if (anim >= start && anim <= end) { + anim = (anim == ANIM_FALL_BACK_BLOOD_SF) + ? ANIM_FALL_BACK_SF + : ANIM_FALL_FRONT_SF; + *fid = buildFid(1, *fid & 0xFFF, anim, (*fid & 0xF000) >> 12, (*fid & 0x70000000) >> 28); + } + + if (shouldResetViolenceLevel) { + gViolenceLevel = -1; + } +} + +// 0x48FB08 +int _obj_preload_sort(const void* a1, const void* a2) +{ + int v1 = *(int*)a1; + int v2 = *(int*)a2; + + int v3 = _cd_order[(v1 & 0xF000000) >> 24]; + int v4 = _cd_order[(v2 & 0xF000000) >> 24]; + + 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; +} diff --git a/src/object.h b/src/object.h new file mode 100644 index 0000000..16f53c9 --- /dev/null +++ b/src/object.h @@ -0,0 +1,175 @@ +#ifndef OBJECT_H +#define OBJECT_H + +#include "db.h" +#include "geometry.h" +#include "inventory.h" +#include "map_defs.h" +#include "obj_types.h" + +typedef struct ObjectWithFlags { + int flags; + Object* object; +} ObjectWithFlags; + +extern bool gObjectsInitialized; +extern int _updateHexWidth; +extern int _updateHexHeight; +extern int _updateHexArea; +extern int* _orderTable[2]; +extern int* _offsetTable[2]; +extern int* _offsetModTable; +extern int* _offsetDivTable; +extern ObjectListNode** _renderTable; +extern int _outlineCount; +extern ObjectListNode* gObjectListHead; +extern int _centerToUpperLeft; +extern int gObjectFindElevation; +extern int gObjectFindTile; +extern ObjectListNode* gObjectFindLastObjectListNode; +extern int* gObjectFids; +extern int gObjectFidsLength; +extern Rect _light_rect[9]; +extern int _light_distance[36]; +extern int gViolenceLevel; +extern int _obj_last_roof_x; +extern int _obj_last_roof_y; +extern int _obj_last_elev; +extern int _obj_last_is_empty; +extern unsigned char* _wallBlendTable; +extern unsigned char* _glassBlendTable; +extern unsigned char* _steamBlendTable; +extern unsigned char* _energyBlendTable; +extern unsigned char* _redBlendTable; +extern Object* _moveBlockObj; +extern int _objItemOutlineState; +extern int _cd_order[9]; + +extern int _light_blocked[6][36]; +extern int _light_offsets[2][6][36]; +extern Rect gObjectsWindowRect; +extern Object* _outlinedObjects[100]; +extern int _updateAreaPixelBounds; +extern int dword_639D94; +extern int dword_639D98; +extern int dword_639D9C; +extern ObjectListNode* gObjectListHeadByTile[HEX_GRID_SIZE]; +extern unsigned char _glassGrayTable[256]; +extern unsigned char _commonGrayTable[256]; +extern int gObjectsWindowBufferSize; +extern unsigned char* gObjectsWindowBuffer; +extern int gObjectsWindowHeight; +extern Object* gEgg; +extern int gObjectsWindowPitch; +extern int gObjectsWindowWidth; +extern Object* gDude; +extern char _obj_seen_check[5001]; +extern char _obj_seen[5001]; + +int objectsInit(unsigned char* buf, int width, int height, int pitch); +void objectsReset(); +void objectsExit(); +int objectRead(Object* obj, File* stream); +int objectLoadAll(File* stream); +int objectLoadAllInternal(File* stream); +void _obj_fix_combat_cid_for_dude(); +void _object_fix_weapon_ammo(Object* obj); +int objectWrite(Object* obj, File* stream); +int objectSaveAll(File* stream); +void _obj_render_pre_roof(Rect* rect, int elevation); +void _obj_render_post_roof(Rect* rect, int elevation); +int objectCreateWithFidPid(Object** objectPtr, int fid, int pid); +int objectCreateWithPid(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 objectSetLocation(Object* obj, int tile, int elevation, Rect* rect); +int _obj_reset_roof(); +int objectSetFid(Object* obj, int fid, Rect* rect); +int objectSetFrame(Object* obj, int frame, Rect* rect); +int objectSetNextFrame(Object* obj, Rect* rect); +int objectSetPrevFrame(Object* obj, Rect* rect); +int objectSetRotation(Object* obj, int direction, Rect* rect); +int objectRotateClockwise(Object* obj, Rect* rect); +int objectRotateCounterClockwise(Object* obj, Rect* rect); +void _obj_rebuild_all_light(); +int objectSetLight(Object* obj, int lightDistance, int lightIntensity, Rect* rect); +int objectGetLightIntensity(Object* obj); +int _obj_turn_on_light(Object* obj, Rect* rect); +int _obj_turn_off_light(Object* obj, Rect* rect); +int objectShow(Object* obj, Rect* rect); +int objectHide(Object* obj, Rect* rect); +int objectEnableOutline(Object* obj, Rect* rect); +int objectDisableOutline(Object* obj, Rect* rect); +int _obj_toggle_flat(Object* obj, Rect* rect); +int objectDestroy(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* objectFindById(int a1); +Object* objectGetOwner(Object* obj); +void _obj_remove_all(); +Object* objectFindFirst(); +Object* objectFindNext(); +Object* objectFindFirstAtElevation(int elevation); +Object* objectFindNextAtElevation(); +Object* objectFindFirstAtLocation(int elevation, int tile); +Object* objectFindNextAtLocation(); +void objectGetRect(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 objectGetDistanceBetween(Object* object1, Object* object2); +int objectGetDistanceBetweenTiles(Object* object1, int tile1, Object* object2, int tile2); +int objectListCreate(int tile, int elevation, int objectType, Object*** objectsPtr); +void objectListFree(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 objectSetOutline(Object* obj, int a2, Rect* rect); +int objectClearOutline(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_clear_seen(); +void _obj_process_seen(); +char* objectGetName(Object* obj); +char* objectGetDescription(Object* obj); +void _obj_preload_art_cache(int flags); +int _obj_offset_table_init(); +void _obj_offset_table_exit(); +int _obj_order_table_init(); +int _obj_order_comp_func_even(const void* a1, const void* a2); +int _obj_order_comp_func_odd(const void* a1, const void* a2); +void _obj_order_table_exit(); +int _obj_render_table_init(); +void _obj_render_table_exit(); +void _obj_light_table_init(); +void _obj_blend_table_init(); +void _obj_blend_table_exit(); +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); +int objectAllocate(Object** objectPtr); +void objectDeallocate(Object** objectPtr); +int objectListNodeCreate(ObjectListNode** nodePtr); +void objectListNodeDestroy(ObjectListNode** nodePtr); +int objectGetListNode(Object* obj, ObjectListNode** out_node, ObjectListNode** out_prev_node); +void _obj_insert(ObjectListNode* ptr); +int _obj_remove(ObjectListNode* a1, ObjectListNode* a2); +int _obj_connect_to_tile(ObjectListNode* node, int tile_index, int elev, Rect* rect); +int _obj_adjust_light(Object* obj, int a2, Rect* rect); +void objectDrawOutline(Object* object, Rect* rect); +void _obj_render_object(Object* object, Rect* rect, int light); +void _obj_fix_violence_settings(int* fid); +int _obj_preload_sort(const void* a1, const void* a2); + +#endif /* OBJECT_H */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..f81e5fb --- /dev/null +++ b/src/options.c @@ -0,0 +1,1942 @@ +#include "options.h" + +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "grayscale.h" +#include "loadsave.h" +#include "memory.h" +#include "scripts.h" +#include "text_font.h" +#include "text_object.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include +#include + +// 0x48FBD0 +const int _row1Ytab[PRIMARY_PREF_COUNT] = { + 48, + 125, + 203, + 286, + 363, +}; + +// 0x48FBDA +const int _row2Ytab[SECONDARY_PREF_COUNT] = { + 49, + 116, + 181, + 247, + 313, + 380, +}; + +// 0x48FBE6 +const int _row3Ytab[RANGE_PREF_COUNT] = { + 19, + 94, + 165, + 216, + 268, + 319, + 369, + 420, +}; + +// x offsets for primary preferences from the knob position +// 0x48FBF6 +const short word_48FBF6[PRIMARY_OPTION_VALUE_COUNT] = { + 2, + 25, + 46, + 46, +}; + +// y offsets for primary preference option values from the knob position +// 0x48FBFE +const short word_48FBFE[PRIMARY_OPTION_VALUE_COUNT] = { + 10, + -4, + 10, + 31, +}; + +// x offsets for secondary prefrence option values from the knob position +// 0x48FC06 +const short word_48FC06[SECONDARY_OPTION_VALUE_COUNT] = { + 4, + 21, +}; + +// 0x48FC0C +const int gPauseWindowFrmIds[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 +}; + +// y offsets for secondary preferences +// 0x48FC30 +const int dword_48FC30[SECONDARY_PREF_COUNT] = { + 66, // combat messages + 133, // combat taunts + 200, // language filter + 264, // running + 331, // subtitles + 397, // item highlight +}; + +// y offsets for primary preferences +// 0x48FC1C +const int dword_48FC1C[PRIMARY_PREF_COUNT] = { + 66, // game difficulty + 143, // combat difficulty + 222, // violence level + 304, // target highlight + 382, // combat looks +}; + +// 0x50C168 +const double dbl_50C168 = 1.17999267578125; + +// 0x50C170 +const double dbl_50C170 = 0.01124954223632812; + +// 0x50C178 +const double dbl_50C178 = -0.01124954223632812; + +// 0x50C180 +const double dbl_50C180 = 1.17999267578125; + +// 0x50C2D0 +const double dbl_50C2D0 = -1.0; + +// 0x50C2D8 +const double dbl_50C2D8 = 0.2; + +// 0x50C2E0 +const double dbl_50C2E0 = 2.0; + +// 0x5197C0 +const int gOptionsWindowFrmIds[OPTIONS_WINDOW_FRM_COUNT] = { + 220, // opbase.frm - character editor + 222, // opbtnon.frm - character editor + 221, // opbtnoff.frm - character editor +}; + +// 0x5197CC +const int gPreferencesWindowFrmIds[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 +}; + +// 0x5197F8 +PreferenceDescription gPreferenceDescriptions[PREF_COUNT] = { + { 3, 0, 76, 71, 0, 0, { 203, 204, 205, 0 }, 0, GAME_CONFIG_GAME_DIFFICULTY_KEY, 0, 0, &gPreferencesGameDifficulty1 }, + { 3, 0, 76, 149, 0, 0, { 206, 204, 208, 0 }, 0, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 0, 0, &gPreferencesCombatDifficulty1 }, + { 4, 0, 76, 226, 0, 0, { 214, 215, 204, 216 }, 0, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 0, 0, &gPreferencesViolenceLevel1 }, + { 3, 0, 76, 309, 0, 0, { 202, 201, 213, 0 }, 0, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 0, 0, &gPreferencesTargetHighlight1 }, + { 2, 0, 76, 387, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_LOOKS_KEY, 0, 0, &gPreferencesCombatLooks1 }, + { 2, 0, 299, 74, 0, 0, { 211, 212, 0, 0 }, 0, GAME_CONFIG_COMBAT_MESSAGES_KEY, 0, 0, &gPreferencesCombatMessages1 }, + { 2, 0, 299, 141, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_TAUNTS_KEY, 0, 0, &gPreferencesCombatTaunts1 }, + { 2, 0, 299, 207, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0, 0, &gPreferencesLanguageFilter1 }, + { 2, 0, 299, 271, 0, 0, { 209, 219, 0, 0 }, 0, GAME_CONFIG_RUNNING_KEY, 0, 0, &gPreferencesRunning1 }, + { 2, 0, 299, 338, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_SUBTITLES_KEY, 0, 0, &gPreferencesSubtitles1 }, + { 2, 0, 299, 404, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 0, 0, &gPreferencesItemHighlight1 }, + { 2, 0, 374, 50, 0, 0, { 207, 210, 0, 0 }, 0, GAME_CONFIG_COMBAT_SPEED_KEY, 0.0, 50.0, &gPreferencesCombatSpeed1 }, + { 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, &gPreferencesMasterVolume1 }, + { 4, 0, 374, 247, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_MUSIC_VOLUME_KEY, 0, 32767.0, &gPreferencesMusicVolume1 }, + { 4, 0, 374, 298, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SNDFX_VOLUME_KEY, 0, 32767.0, &gPreferencesSoundEffectsVolume1 }, + { 4, 0, 374, 349, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SPEECH_VOLUME_KEY, 0, 32767.0, &gPreferencesSpeechVolume1 }, + { 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 }, +}; + +// 0x6637D0 +Size gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_COUNT]; + +// 0x6637E8 +MessageList gOptionsMessageList; + +// 0x6637F0 +Size gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_COUNT]; + +// 0x663840 +MessageListItem gOptionsMessageListItem; + +// 0x663850 +unsigned char* gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_COUNT]; + +// 0x663878 +unsigned char* _opbtns[OPTIONS_WINDOW_BUTTONS_COUNT]; + +// 0x6638A0 +CacheEntry* gPreferencesWindowFrmHandles[PREFERENCES_WINDOW_FRM_COUNT]; + +// 0x6638C8 +double gPreferencesTextBaseDelay2; + +// 0x6638D0 +double gPreferencesBrightness1; + +// 0x6638D8 +double gPreferencesBrightness2; + +// 0x6638E0 +double gPreferencesTextBaseDelay1; + +// 0x6638E8 +double gPreferencesMouseSensitivity1; + +// 0x6638F0 +double gPreferencesMouseSensitivity2; + +// 0x6638F8 +unsigned char* gPreferencesWindowBuffer; + +// 0x6638FC +bool gOptionsWindowGameMouseObjectsWasVisible; + +// 0x663900 +int gOptionsWindow; + +// 0x663904 +int gPreferencesWindow; + +// 0x663908 +unsigned char* gOptionsWindowBuffer; + +// 0x66390C +CacheEntry* gOptionsWindowFrmHandles[OPTIONS_WINDOW_FRM_COUNT]; + +// 0x663918 +unsigned char* gOptionsWindowFrmData[OPTIONS_WINDOW_FRM_COUNT]; + +// 0x663924 +int gPreferencesGameDifficulty2; + +// 0x663928 +int gPreferencesCombatDifficulty2; + +// 0x66392C +int gPreferencesViolenceLevel2; + +// 0x663930 +int gPreferencesTargetHighlight2; + +// 0x663934 +int gPreferencesCombatLooks2; + +// 0x663938 +int gPreferencesCombatMessages2; + +// 0x66393C +int gPreferencesCombatTaunts2; + +// 0x663940 +int gPreferencesLanguageFilter2; + +// 0x663944 +int gPreferencesRunning2; + +// 0x663948 +int gPreferencesSubtitles2; + +// 0x66394C +int gPreferencesItemHighlight2; + +// 0x663950 +int gPreferencesCombatSpeed2; + +// 0x663954 +int gPreferencesPlayerSpeedup2; + +// 0x663958 +int gPreferencesMasterVolume2; + +// 0x66395C +int gPreferencesMusicVolume2; + +// 0x663960 +int gPreferencesSoundEffectsVolume2; + +// 0x663964 +int gPreferencesSpeechVolume2; + +// 0x663970 +int gPreferencesSoundEffectsVolume1; + +// 0x663974 +int gPreferencesSubtitles1; + +// 0x663978 +int gPreferencesLanguageFilter1; + +// 0x66397C +int gPreferencesSpeechVolume1; + +// 0x663980 +int gPreferencesMasterVolume1; + +// 0x663984 +int gPreferencesPlayerSpeedup1; + +// 0x663988 +int gPreferencesCombatTaunts1; + +// 0x66398C +int gOptionsWindowOldFont; + +// 0x663990 +int gPreferencesMusicVolume1; + +// 0x663994 +bool gOptionsWindowIsoWasEnabled; + +// 0x663998 +int gPreferencesRunning1; + +// 0x66399C +int gPreferencesCombatSpeed1; + +// +int _plyrspdbid; + +// 0x6639A4 +int gPreferencesItemHighlight1; + +// 0x6639A8 +bool _changed; + +// 0x6639AC +int gPreferencesCombatMessages1; + +// 0x6639B0 +int gPreferencesTargetHighlight1; + +// 0x6639B4 +int gPreferencesCombatDifficulty1; + +// 0x6639B8 +int gPreferencesViolenceLevel1; + +// 0x6639BC +int gPreferencesGameDifficulty1; + +// 0x6639C0 +int gPreferencesCombatLooks1; + +// 0x48FC48 +int showOptions() +{ + return showOptionsWithInitialKeyCode(-1); +} + +// 0x48FC50 +int showOptionsWithInitialKeyCode(int initialKeyCode) +{ + if (optionsWindowInit() == -1) { + debugPrint("\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: + soundPlayFile("ib1p1xx1"); + rc = 0; + break; + case KEY_UPPERCASE_S: + case KEY_LOWERCASE_S: + case 500: + if (lsgSaveGame(1) != 1) { + rc = 1; + } + break; + case KEY_UPPERCASE_L: + case KEY_LOWERCASE_L: + case 501: + if (lsgLoadGame(LOAD_SAVE_MODE_NORMAL) == 1) { + rc = 1; + } + break; + case KEY_UPPERCASE_P: + case KEY_LOWERCASE_P: + soundPlayFile("ib1p1xx1"); + // FALLTHROUGH + case 502: + // PREFERENCES + showPreferences = true; + break; + case KEY_PLUS: + case KEY_EQUAL: + brightnessIncrease(); + break; + case KEY_UNDERSCORE: + case KEY_MINUS: + brightnessDecrease(); + break; + } + } + + if (showPreferences) { + _do_prefscreen(); + } else { + switch (keyCode) { + case KEY_F12: + takeScreenshot(); + break; + case KEY_UPPERCASE_E: + case KEY_LOWERCASE_E: + case KEY_CTRL_Q: + case KEY_CTRL_X: + case KEY_F10: + case 503: + showQuitConfirmationDialog(); + break; + } + } + } + + optionsWindowFree(); + + return rc; +} + +// 0x48FE14 +int optionsWindowInit() +{ + gOptionsWindowOldFont = fontGetCurrent(); + + if (!messageListInit(&gOptionsMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "options.msg"); + if (!messageListLoad(&gOptionsMessageList, path)) { + return -1; + } + + for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { + int fid = buildFid(6, gOptionsWindowFrmIds[index], 0, 0, 0); + gOptionsWindowFrmData[index] = artLockFrameDataReturningSize(fid, &(gOptionsWindowFrmHandles[index]), &(gOptionsWindowFrmSizes[index].width), &(gOptionsWindowFrmSizes[index].height)); + + if (gOptionsWindowFrmData[index] == NULL) { + while (--index >= 0) { + artUnlock(gOptionsWindowFrmHandles[index]); + } + + messageListFree(&gOptionsMessageList); + + return -1; + } + } + + int cycle = 0; + for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { + _opbtns[index] = internal_malloc(gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width * gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 1024); + if (_opbtns[index] == NULL) { + while (--index >= 0) { + internal_free(_opbtns[index]); + } + + for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { + artUnlock(gOptionsWindowFrmHandles[index]); + } + + messageListFree(&gOptionsMessageList); + + return -1; + } + + cycle = cycle ^ 1; + + memcpy(_opbtns[index], gOptionsWindowFrmData[cycle + 1], gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width * gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].height); + } + + gOptionsWindow = windowCreate( + (640 - gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BACKGROUND].width) / 2, + (480 - gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BACKGROUND].height) / 2 - 60, + gOptionsWindowFrmSizes[0].width, + gOptionsWindowFrmSizes[0].height, + 256, + WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + + if (gOptionsWindow == -1) { + for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { + internal_free(_opbtns[index]); + } + + for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { + artUnlock(gOptionsWindowFrmHandles[index]); + } + + messageListFree(&gOptionsMessageList); + + return -1; + } + + gOptionsWindowIsoWasEnabled = isoDisable(); + + gOptionsWindowGameMouseObjectsWasVisible = gameMouseObjectsIsVisible(); + if (gOptionsWindowGameMouseObjectsWasVisible) { + gameMouseObjectsHide(); + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + gOptionsWindowBuffer = windowGetBuffer(gOptionsWindow); + memcpy(gOptionsWindowBuffer, gOptionsWindowFrmData[OPTIONS_WINDOW_FRM_BACKGROUND], gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BACKGROUND].width * gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BACKGROUND].height); + + fontSetCurrent(103); + + int textY = (gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].height - fontGetLineHeight()) / 2 + 1; + int buttonY = 17; + + for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index += 2) { + char text[128]; + + const char* msg = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, index / 2); + strcpy(text, msg); + + int textX = (gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width - fontGetStringWidth(text)) / 2; + if (textX < 0) { + textX = 0; + } + + fontDrawText(_opbtns[index] + gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width, _colorTable[18979]); + fontDrawText(_opbtns[index + 1] + gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width, _colorTable[14723]); + + int btn = buttonCreate(gOptionsWindow, 13, buttonY, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].width, gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].height, -1, -1, -1, index / 2 + 500, _opbtns[index], _opbtns[index + 1], NULL, 32); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_lrg_butt_press, _gsound_lrg_butt_release); + } + + buttonY += gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 3; + } + + fontSetCurrent(101); + + windowRefresh(gOptionsWindow); + + return 0; +} + +// 0x490244 +int optionsWindowFree() +{ + windowDestroy(gOptionsWindow); + fontSetCurrent(gOptionsWindowOldFont); + messageListFree(&gOptionsMessageList); + + for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { + internal_free(_opbtns[index]); + } + + for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { + artUnlock(gOptionsWindowFrmHandles[index]); + } + + if (gOptionsWindowGameMouseObjectsWasVisible) { + gameMouseObjectsShow(); + } + + if (gOptionsWindowIsoWasEnabled) { + isoEnable(); + } + + return 0; +} + +// 0x4902B0 +int showPause(bool a1) +{ + int graphicIds[PAUSE_WINDOW_FRM_COUNT]; + unsigned char* frmData[PAUSE_WINDOW_FRM_COUNT]; + CacheEntry* frmHandles[PAUSE_WINDOW_FRM_COUNT]; + Size frmSizes[PAUSE_WINDOW_FRM_COUNT]; + + static_assert(sizeof(graphicIds) == sizeof(gPauseWindowFrmIds), "wrong size"); + memcpy(graphicIds, gPauseWindowFrmIds, sizeof(gPauseWindowFrmIds)); + + bool gameMouseWasVisible; + if (!a1) { + gOptionsWindowIsoWasEnabled = isoDisable(); + colorCycleDisable(); + + gameMouseWasVisible = gameMouseObjectsIsVisible(); + if (gameMouseWasVisible) { + gameMouseObjectsHide(); + } + } + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + _ShadeScreen(a1); + + for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) { + int fid = buildFid(6, graphicIds[index], 0, 0, 0); + frmData[index] = artLockFrameDataReturningSize(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height)); + if (frmData[index] == NULL) { + while (--index >= 0) { + artUnlock(frmHandles[index]); + } + + debugPrint("\n** Error loading pause window graphics! **\n"); + return -1; + } + } + + if (!messageListInit(&gOptionsMessageList)) { + // FIXME: Leaking graphics. + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "options.msg"); + if (!messageListLoad(&gOptionsMessageList, path)) { + // FIXME: Leaking graphics. + return -1; + } + + int x = (640 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width) / 2; + int y = (480 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height) / 2; + + if (a1) { + x -= 65; + y -= 24; + } else { + y -= 54; + } + + int window = windowCreate(x, y, 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++) { + artUnlock(frmHandles[index]); + } + + messageListFree(&gOptionsMessageList); + + debugPrint("\n** Error opening pause window! **\n"); + return -1; + } + + unsigned char* windowBuffer = windowGetBuffer(window); + memcpy(windowBuffer, + frmData[PAUSE_WINDOW_FRM_BACKGROUND], + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height); + + blitBufferToBufferTrans(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); + + gOptionsWindowOldFont = fontGetCurrent(); + fontSetCurrent(103); + + char* messageItemText; + + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 300); + fontDrawText(windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 45 + 52, + messageItemText, + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, + _colorTable[18979]); + + fontSetCurrent(104); + + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 301); + strcpy(path, messageItemText); + + int length = fontGetStringWidth(path); + fontDrawText(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 = buttonCreate(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) { + buttonSetCallbacks(doneBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + windowRefresh(window); + + bool done = false; + while (!done) { + int keyCode = _get_input(); + switch (keyCode) { + case KEY_PLUS: + case KEY_EQUAL: + brightnessIncrease(); + break; + case KEY_MINUS: + case KEY_UNDERSCORE: + brightnessDecrease(); + break; + default: + if (keyCode != -1 && keyCode != -2) { + done = true; + } + + if (_game_user_wants_to_quit != 0) { + done = true; + } + } + } + + if (!a1) { + tileWindowRefresh(); + } + + windowDestroy(window); + + for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) { + artUnlock(frmHandles[index]); + } + + messageListFree(&gOptionsMessageList); + + if (!a1) { + if (gameMouseWasVisible) { + gameMouseObjectsShow(); + } + + if (gOptionsWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + } + + fontSetCurrent(gOptionsWindowOldFont); + + return 0; +} + +// 0x490748 +void _ShadeScreen(bool a1) +{ + if (a1) { + mouseHideCursor(); + } else { + mouseHideCursor(); + tileWindowRefresh(); + + int windowHeight = windowGetHeight(gIsoWindow); + unsigned char* windowBuffer = windowGetBuffer(gIsoWindow); + grayscalePaletteApply(windowBuffer, 640, windowHeight, 640); + + windowRefresh(gIsoWindow); + } + + mouseShowCursor(); +} + +// 0x492AA8 +void _SetSystemPrefs() +{ + preferencesSetDefaults(false); + + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gPreferencesGameDifficulty1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &gPreferencesCombatDifficulty1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &gPreferencesViolenceLevel1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &gPreferencesTargetHighlight1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, &gPreferencesCombatMessages1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &gPreferencesCombatLooks1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &gPreferencesCombatTaunts1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &gPreferencesLanguageFilter1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &gPreferencesRunning1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &gPreferencesSubtitles1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &gPreferencesItemHighlight1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &gPreferencesCombatSpeed1); + configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &gPreferencesTextBaseDelay1); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &gPreferencesPlayerSpeedup1); + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &gPreferencesMasterVolume1); + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &gPreferencesMusicVolume1); + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &gPreferencesSoundEffectsVolume1); + configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &gPreferencesSpeechVolume1); + configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gPreferencesBrightness1); + configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, &gPreferencesMouseSensitivity1); + + _JustUpdate_(); +} + +// Copy options (1) to (2). +// +// 0x493054 +void _SaveSettings() +{ + gPreferencesGameDifficulty2 = gPreferencesGameDifficulty1; + gPreferencesCombatDifficulty2 = gPreferencesCombatDifficulty1; + gPreferencesViolenceLevel2 = gPreferencesViolenceLevel1; + gPreferencesTargetHighlight2 = gPreferencesTargetHighlight1; + gPreferencesCombatLooks2 = gPreferencesCombatLooks1; + gPreferencesCombatMessages2 = gPreferencesCombatMessages1; + gPreferencesCombatTaunts2 = gPreferencesCombatTaunts1; + gPreferencesLanguageFilter2 = gPreferencesLanguageFilter1; + gPreferencesRunning2 = gPreferencesRunning1; + gPreferencesSubtitles2 = gPreferencesSubtitles1; + gPreferencesItemHighlight2 = gPreferencesItemHighlight1; + gPreferencesCombatSpeed2 = gPreferencesCombatSpeed1; + gPreferencesPlayerSpeedup2 = gPreferencesPlayerSpeedup1; + gPreferencesMasterVolume2 = gPreferencesMasterVolume1; + gPreferencesTextBaseDelay2 = gPreferencesTextBaseDelay1; + gPreferencesMusicVolume2 = gPreferencesMusicVolume1; + gPreferencesBrightness2 = gPreferencesBrightness1; + gPreferencesSoundEffectsVolume2 = gPreferencesSoundEffectsVolume1; + gPreferencesMouseSensitivity2 = gPreferencesMouseSensitivity1; + gPreferencesSpeechVolume2 = gPreferencesSpeechVolume1; +} + +// Copy options (2) to (1). +// +// 0x493128 +void _RestoreSettings() +{ + gPreferencesGameDifficulty1 = gPreferencesGameDifficulty2; + gPreferencesCombatDifficulty1 = gPreferencesCombatDifficulty2; + gPreferencesViolenceLevel1 = gPreferencesViolenceLevel2; + gPreferencesTargetHighlight1 = gPreferencesTargetHighlight2; + gPreferencesCombatLooks1 = gPreferencesCombatLooks2; + gPreferencesCombatMessages1 = gPreferencesCombatMessages2; + gPreferencesCombatTaunts1 = gPreferencesCombatTaunts2; + gPreferencesLanguageFilter1 = gPreferencesLanguageFilter2; + gPreferencesRunning1 = gPreferencesRunning2; + gPreferencesSubtitles1 = gPreferencesSubtitles2; + gPreferencesItemHighlight1 = gPreferencesItemHighlight2; + gPreferencesCombatSpeed1 = gPreferencesCombatSpeed2; + gPreferencesPlayerSpeedup1 = gPreferencesPlayerSpeedup2; + gPreferencesMasterVolume1 = gPreferencesMasterVolume2; + gPreferencesTextBaseDelay1 = gPreferencesTextBaseDelay2; + gPreferencesMusicVolume1 = gPreferencesMusicVolume2; + gPreferencesBrightness1 = gPreferencesBrightness2; + gPreferencesSoundEffectsVolume1 = gPreferencesSoundEffectsVolume2; + gPreferencesMouseSensitivity1 = gPreferencesMouseSensitivity2; + gPreferencesSpeechVolume1 = gPreferencesSpeechVolume2; + + _JustUpdate_(); +} + +// 0x492F60 +void preferencesSetDefaults(bool a1) +{ + gPreferencesCombatDifficulty1 = COMBAT_DIFFICULTY_NORMAL; + gPreferencesViolenceLevel1 = VIOLENCE_LEVEL_MAXIMUM_BLOOD; + gPreferencesTargetHighlight1 = TARGET_HIGHLIGHT_TARGETING_ONLY; + gPreferencesCombatMessages1 = 1; + gPreferencesCombatLooks1 = 0; + gPreferencesCombatTaunts1 = 1; + gPreferencesRunning1 = 0; + gPreferencesSubtitles1 = 0; + gPreferencesItemHighlight1 = 1; + gPreferencesCombatSpeed1 = 0; + gPreferencesPlayerSpeedup1 = 0; + gPreferencesTextBaseDelay1 = 3.5; + gPreferencesBrightness1 = 1.0; + gPreferencesMouseSensitivity1 = 1.0; + gPreferencesGameDifficulty1 = 1; + gPreferencesLanguageFilter1 = 0; + gPreferencesMasterVolume1 = 22281; + gPreferencesMusicVolume1 = 22281; + gPreferencesSoundEffectsVolume1 = 22281; + gPreferencesSpeechVolume1 = 22281; + + if (a1) { + for (int index = 0; index < PREF_COUNT; index++) { + _UpdateThing(index); + } + _win_set_button_rest_state(_plyrspdbid, gPreferencesPlayerSpeedup1, 0); + windowRefresh(gPreferencesWindow); + _changed = true; + } +} + +// 0x4931F8 +void _JustUpdate_() +{ + gPreferencesGameDifficulty1 = min(max(gPreferencesGameDifficulty1, 0), 2); + gPreferencesCombatDifficulty1 = min(max(gPreferencesCombatDifficulty1, 0), 2); + gPreferencesViolenceLevel1 = min(max(gPreferencesViolenceLevel1, 0), 3); + gPreferencesTargetHighlight1 = min(max(gPreferencesTargetHighlight1, 0), 2); + gPreferencesCombatMessages1 = min(max(gPreferencesCombatMessages1, 0), 1); + gPreferencesCombatLooks1 = min(max(gPreferencesCombatLooks1, 0), 1); + gPreferencesCombatTaunts1 = min(max(gPreferencesCombatTaunts1, 0), 1); + gPreferencesLanguageFilter1 = min(max(gPreferencesLanguageFilter1, 0), 1); + gPreferencesRunning1 = min(max(gPreferencesRunning1, 0), 1); + gPreferencesSubtitles1 = min(max(gPreferencesSubtitles1, 0), 1); + gPreferencesItemHighlight1 = min(max(gPreferencesItemHighlight1, 0), 1); + gPreferencesCombatSpeed1 = min(max(gPreferencesCombatSpeed1, 0), 50); + gPreferencesPlayerSpeedup1 = min(max(gPreferencesPlayerSpeedup1, 0), 1); + gPreferencesTextBaseDelay1 = min(max(gPreferencesTextBaseDelay1, 1.0), 6.0); + gPreferencesMasterVolume1 = min(max(gPreferencesMasterVolume1, 0), VOLUME_MAX); + gPreferencesMusicVolume1 = min(max(gPreferencesMusicVolume1, 0), VOLUME_MAX); + gPreferencesSoundEffectsVolume1 = min(max(gPreferencesSoundEffectsVolume1, 0), VOLUME_MAX); + gPreferencesSpeechVolume1 = min(max(gPreferencesSpeechVolume1, 0), VOLUME_MAX); + gPreferencesBrightness1 = min(max(gPreferencesBrightness1, 1.0), 1.17999267578125); + gPreferencesMouseSensitivity1 = min(max(gPreferencesMouseSensitivity1, 1.0), 2.5); + + textObjectsSetBaseDelay(gPreferencesTextBaseDelay1); + gameMouseLoadItemHighlight(); + + double textLineDelay = (gPreferencesTextBaseDelay1 + (-1.0)) * 0.2 * 2.0; + textLineDelay = min(max(textLineDelay, 0.0), 2.0); + + textObjectsSetLineDelay(textLineDelay); + aiMessageListReloadIfNeeded(); + _scr_message_free(); + gameSoundSetMasterVolume(gPreferencesMasterVolume1); + backgroundSoundSetVolume(gPreferencesMusicVolume1); + soundEffectsSetVolume(gPreferencesSoundEffectsVolume1); + speechSetVolume(gPreferencesSpeechVolume1); + mouseSetSensitivity(gPreferencesMouseSensitivity1); + colorSetBrightness(gPreferencesBrightness1); +} + +// init_options_menu +// 0x4928B8 +int _init_options_menu() +{ + for (int index = 0; index < 11; index++) { + gPreferenceDescriptions[index].direction = 0; + } + + _SetSystemPrefs(); + + grayscalePaletteUpdate(0, 255); + + return 0; +} + +// 0x491A68 +void _UpdateThing(int index) +{ + fontSetCurrent(101); + + PreferenceDescription* meta = &(gPreferenceDescriptions[index]); + + if (index >= FIRST_PRIMARY_PREF && index <= LAST_PRIMARY_PREF) { + int primaryOptionIndex = index - FIRST_PRIMARY_PREF; + + int offsets[PRIMARY_PREF_COUNT]; + memcpy(offsets, dword_48FC1C, sizeof(dword_48FC1C)); + + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[primaryOptionIndex] + 23, 160, 54, 640, gPreferencesWindowBuffer + 640 * offsets[primaryOptionIndex] + 23, 640); + + for (int valueIndex = 0; valueIndex < meta->valuesCount; valueIndex++) { + const char* text = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, meta->labelIds[valueIndex]); + + char copy[100]; // TODO: Size is probably wrong. + strcpy(copy, text); + + int x = meta->knobX + word_48FBF6[valueIndex]; + int len = fontGetStringWidth(copy); + switch (valueIndex) { + case 0: + x -= fontGetStringWidth(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 + word_48FBFE[valueIndex]; + const char* s; + if (*p != '\0') { + *p = '\0'; + fontDrawText(gPreferencesWindowBuffer + 640 * y + x, copy, 640, 640, _colorTable[18979]); + s = p + 1; + y += fontGetLineHeight(); + } else { + s = copy; + } + + fontDrawText(gPreferencesWindowBuffer + 640 * y + x, s, 640, 640, _colorTable[18979]); + } + + int value = *(meta->valuePtr); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_PRIMARY_SWITCH] + (46 * 47) * value, 46, 47, 46, gPreferencesWindowBuffer + 640 * meta->knobY + meta->knobX, 640); + } else if (index >= FIRST_SECONDARY_PREF && index <= LAST_SECONDARY_PREF) { + int secondaryOptionIndex = index - FIRST_SECONDARY_PREF; + + int offsets[SECONDARY_PREF_COUNT]; + memcpy(offsets, dword_48FC30, sizeof(dword_48FC30)); + + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[secondaryOptionIndex] + 251, 113, 34, 640, gPreferencesWindowBuffer + 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(&gOptionsMessageList, &gOptionsMessageListItem, meta->labelIds[value]); + + int x; + if (value) { + x = meta->knobX + word_48FC06[value]; + meta->maxX = x + fontGetStringWidth(text); + } else { + x = meta->knobX + word_48FC06[value] - fontGetStringWidth(text); + meta->minX = x; + } + fontDrawText(gPreferencesWindowBuffer + 640 * (meta->knobY - 5) + x, text, 640, 640, _colorTable[18979]); + } + + int value = *(meta->valuePtr); + if (index == PREF_COMBAT_MESSAGES) { + value ^= 1; + } + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_SECONDARY_SWITCH] + (22 * 25) * value, 22, 25, 22, gPreferencesWindowBuffer + 640 * meta->knobY + meta->knobX, 640); + } else if (index >= FIRST_RANGE_PREF && index <= LAST_RANGE_PREF) { + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * (meta->knobY - 12) + 384, 240, 24, 640, gPreferencesWindowBuffer + 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); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + x, 640); + } + break; + case PREF_TEXT_BASE_DELAY: + if (1) { + gPreferencesTextBaseDelay1 = min(max(gPreferencesTextBaseDelay1, 1.0), 6.0); + + int x = (int)((6.0 - gPreferencesTextBaseDelay1) * 43.8 + 384.0); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + x, 640); + + double value = (gPreferencesTextBaseDelay1 - 1.0) * 0.2 * 2.0; + value = min(max(value, 0.0), 2.0); + + textObjectsSetBaseDelay(gPreferencesTextBaseDelay1); + textObjectsSetLineDelay(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); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + x, 640); + + switch (index) { + case PREF_MASTER_VOLUME: + gameSoundSetMasterVolume(gPreferencesMasterVolume1); + break; + case PREF_MUSIC_VOLUME: + backgroundSoundSetVolume(gPreferencesMusicVolume1); + break; + case PREF_SFX_VOLUME: + soundEffectsSetVolume(gPreferencesSoundEffectsVolume1); + break; + case PREF_SPEECH_VOLUME: + speechSetVolume(gPreferencesSpeechVolume1); + break; + } + } + break; + case PREF_BRIGHTNESS: + if (1) { + gPreferencesBrightness1 = min(max(gPreferencesBrightness1, 1.0), 1.17999267578125); + + int x = (int)((gPreferencesBrightness1 - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + x, 640); + + colorSetBrightness(gPreferencesBrightness1); + } + break; + case PREF_MOUSE_SENSITIVIY: + if (1) { + gPreferencesMouseSensitivity1 = min(max(gPreferencesMouseSensitivity1, 1.0), 2.5); + + int x = (int)((gPreferencesMouseSensitivity1 - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + x, 640); + + mouseSetSensitivity(gPreferencesMouseSensitivity1); + } + break; + } + + for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) { + const char* str = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 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 - fontGetStringWidth(str); + break; + case 3: + // This code path does not use floating-point arithmetic + x = 504 - fontGetStringWidth(str) / 2 - 2; + break; + case 4: + // Uses floating-point arithmetic + x = 444 + fontGetStringWidth(str) / 2 - 8; + break; + } + break; + case 2: + // 0x492766 + switch (meta->valuesCount) { + case 3: + x = 624 - fontGetStringWidth(str); + break; + case 4: + // Uses floating-point arithmetic + x = 564 - fontGetStringWidth(str) - 4; + break; + } + break; + case 3: + // 0x49279E + x = 624 - fontGetStringWidth(str); + break; + } + fontDrawText(gPreferencesWindowBuffer + 640 * (meta->knobY - 12) + x, str, 640, 640, _colorTable[18979]); + } + } else { + // return false; + } + + // TODO: Incomplete. + + // return true; +} + +// 0x492CB0 +int _SavePrefs(bool save) +{ + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, gPreferencesGameDifficulty1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, gPreferencesCombatDifficulty1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, gPreferencesViolenceLevel1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, gPreferencesTargetHighlight1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, gPreferencesCombatMessages1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, gPreferencesCombatLooks1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, gPreferencesCombatTaunts1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, gPreferencesLanguageFilter1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, gPreferencesRunning1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, gPreferencesSubtitles1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, gPreferencesItemHighlight1); + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, gPreferencesCombatSpeed1); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, gPreferencesTextBaseDelay1); + + double textLineDelay = (gPreferencesTextBaseDelay1 + dbl_50C2D0) * dbl_50C2D8 * dbl_50C2E0; + if (textLineDelay >= 0.0) { + if (textLineDelay > dbl_50C2E0) { + textLineDelay = 2.0; + } + + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, textLineDelay); + } else { + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 0.0); + } + + configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, gPreferencesPlayerSpeedup1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, gPreferencesMasterVolume1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, gPreferencesMusicVolume1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, gPreferencesSoundEffectsVolume1); + configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, gPreferencesSpeechVolume1); + + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gPreferencesBrightness1); + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, gPreferencesMouseSensitivity1); + + if (save) { + gameConfigSave(); + } + + return 0; +} + +// 0x493224 +int preferencesSave(File* stream) +{ + float textBaseDelay = (float)gPreferencesTextBaseDelay1; + float brightness = (float)gPreferencesBrightness1; + float mouseSensitivity = (float)gPreferencesMouseSensitivity1; + + if (fileWriteInt32(stream, gPreferencesGameDifficulty1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesCombatDifficulty1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesViolenceLevel1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesTargetHighlight1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesCombatLooks1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesCombatMessages1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesCombatTaunts1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesLanguageFilter1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesRunning1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesSubtitles1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesItemHighlight1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesCombatSpeed1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesPlayerSpeedup1) == -1) goto err; + if (fileWriteFloat(stream, textBaseDelay) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesMasterVolume1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesMusicVolume1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesSoundEffectsVolume1) == -1) goto err; + if (fileWriteInt32(stream, gPreferencesSpeechVolume1) == -1) goto err; + if (fileWriteFloat(stream, brightness) == -1) goto err; + if (fileWriteFloat(stream, mouseSensitivity) == -1) goto err; + + return 0; + +err: + + debugPrint("\nOPTION MENU: Error save option data!\n"); + + return -1; +} + +// 0x49340C +int preferencesLoad(File* stream) +{ + float textBaseDelay; + float brightness; + float mouseSensitivity; + + preferencesSetDefaults(false); + + if (fileReadInt32(stream, &gPreferencesGameDifficulty1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesCombatDifficulty1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesViolenceLevel1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesTargetHighlight1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesCombatLooks1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesCombatMessages1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesCombatTaunts1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesLanguageFilter1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesRunning1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesSubtitles1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesItemHighlight1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesCombatSpeed1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesPlayerSpeedup1) == -1) goto err; + if (fileReadFloat(stream, &textBaseDelay) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesMasterVolume1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesMusicVolume1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesSoundEffectsVolume1) == -1) goto err; + if (fileReadInt32(stream, &gPreferencesSpeechVolume1) == -1) goto err; + if (fileReadFloat(stream, &brightness) == -1) goto err; + if (fileReadFloat(stream, &mouseSensitivity) == -1) goto err; + + gPreferencesBrightness1 = brightness; + gPreferencesMouseSensitivity1 = mouseSensitivity; + gPreferencesTextBaseDelay1 = textBaseDelay; + + _JustUpdate_(); + _SavePrefs(0); + + return 0; + +err: + + debugPrint("\nOPTION MENU: Error loading option data!, using defaults.\n"); + + preferencesSetDefaults(false); + _JustUpdate_(); + _SavePrefs(0); + + return -1; +} + +// 0x4928E4 +void brightnessIncrease() +{ + gPreferencesBrightness1 = 1.0; + configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gPreferencesBrightness1); + + if (gPreferencesBrightness1 < dbl_50C168) { + gPreferencesBrightness1 += dbl_50C170; + + if (gPreferencesBrightness1 >= 1.0) { + if (gPreferencesBrightness1 > dbl_50C168) { + gPreferencesBrightness1 = dbl_50C168; + } + } else { + gPreferencesBrightness1 = 1.0; + } + + colorSetBrightness(gPreferencesBrightness1); + + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gPreferencesBrightness1); + + gameConfigSave(); + } +} + +// 0x4929C8 +void brightnessDecrease() +{ + gPreferencesBrightness1 = 1.0; + configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gPreferencesBrightness1); + + if (gPreferencesBrightness1 > 1.0) { + gPreferencesBrightness1 += dbl_50C178; + + if (gPreferencesBrightness1 >= 1.0) { + if (gPreferencesBrightness1 > dbl_50C180) { + gPreferencesBrightness1 = dbl_50C180; + } + } else { + gPreferencesBrightness1 = 1.0; + } + + colorSetBrightness(gPreferencesBrightness1); + + configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gPreferencesBrightness1); + + gameConfigSave(); + } +} + +// 0x4908A0 +int preferencesWindowInit() +{ + 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 = buildFid(6, gPreferencesWindowFrmIds[i], 0, 0, 0); + gPreferencesWindowFrmData[i] = artLockFrameDataReturningSize(fid, &(gPreferencesWindowFrmHandles[i]), &(gPreferencesWindowFrmSizes[i].width), &(gPreferencesWindowFrmSizes[i].height)); + if (gPreferencesWindowFrmData[i] == NULL) { + for (; i != 0; i--) { + artUnlock(gPreferencesWindowFrmHandles[i - 1]); + } + return -1; + } + } + + _changed = false; + + gPreferencesWindow = windowCreate(0, 0, 640, 480, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (gPreferencesWindow == -1) { + for (i = 0; i < PREFERENCES_WINDOW_FRM_COUNT; i++) { + artUnlock(gPreferencesWindowFrmHandles[i]); + } + return -1; + } + + gPreferencesWindowBuffer = windowGetBuffer(gPreferencesWindow); + memcpy(gPreferencesWindowBuffer, + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND], + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_BACKGROUND].width * gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_BACKGROUND].height); + + fontSetCurrent(104); + + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 100); + fontDrawText(gPreferencesWindowBuffer + 640 * 10 + 74, messageItemText, 640, 640, _colorTable[18979]); + + fontSetCurrent(103); + + messageItemId = 101; + for (i = 0; i < PRIMARY_PREF_COUNT; i++) { + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, messageItemId++); + x = 99 - fontGetStringWidth(messageItemText) / 2; + fontDrawText(gPreferencesWindowBuffer + 640 * _row1Ytab[i] + x, messageItemText, 640, 640, _colorTable[18979]); + } + + for (i = 0; i < SECONDARY_PREF_COUNT; i++) { + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, messageItemId++); + fontDrawText(gPreferencesWindowBuffer + 640 * _row2Ytab[i] + 206, messageItemText, 640, 640, _colorTable[18979]); + } + + for (i = 0; i < RANGE_PREF_COUNT; i++) { + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, messageItemId++); + fontDrawText(gPreferencesWindowBuffer + 640 * _row3Ytab[i] + 384, messageItemText, 640, 640, _colorTable[18979]); + } + + // DEFAULT + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 120); + fontDrawText(gPreferencesWindowBuffer + 640 * 449 + 43, messageItemText, 640, 640, _colorTable[18979]); + + // DONE + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 4); + fontDrawText(gPreferencesWindowBuffer + 640 * 449 + 169, messageItemText, 640, 640, _colorTable[18979]); + + // CANCEL + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 121); + fontDrawText(gPreferencesWindowBuffer + 640 * 449 + 283, messageItemText, 640, 640, _colorTable[18979]); + + // Affect player speed + messageItemText = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 122); + fontDrawText(gPreferencesWindowBuffer + 640 * 72 + 405, messageItemText, 640, 640, _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 = gPreferenceDescriptions[i].knobY - 12; + width = 240; + height = 23; + mouseEnterEventCode = 526; + mouseExitEventCode = 526; + mouseDownEventCode = 505 + i; + mouseUpEventCode = 526; + + } else if (i >= FIRST_SECONDARY_PREF) { + x = gPreferenceDescriptions[i].minX; + y = gPreferenceDescriptions[i].knobY - 5; + width = gPreferenceDescriptions[i].maxX - x; + height = 28; + mouseEnterEventCode = -1; + mouseExitEventCode = -1; + mouseDownEventCode = -1; + mouseUpEventCode = 505 + i; + } else { + x = gPreferenceDescriptions[i].minX; + y = gPreferenceDescriptions[i].knobY - 4; + width = gPreferenceDescriptions[i].maxX - x; + height = 48; + mouseEnterEventCode = -1; + mouseExitEventCode = -1; + mouseDownEventCode = -1; + mouseUpEventCode = 505 + i; + } + + gPreferenceDescriptions[i].btn = buttonCreate(gPreferencesWindow, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, NULL, NULL, NULL, 32); + } + + _plyrspdbid = buttonCreate(gPreferencesWindow, + 383, + 68, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF].width, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_CHECKBOX_ON].height, + -1, + -1, + 524, + 524, + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF], + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_CHECKBOX_ON], + NULL, + BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01 | BUTTON_FLAG_0x02); + if (_plyrspdbid != -1) { + _win_set_button_rest_state(_plyrspdbid, gPreferencesPlayerSpeedup1, 0); + } + + buttonSetCallbacks(_plyrspdbid, _gsound_med_butt_press, _gsound_med_butt_press); + + // DEFAULT + btn = buttonCreate(gPreferencesWindow, + 23, + 450, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, + -1, + -1, + -1, + 527, + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // DONE + btn = buttonCreate(gPreferencesWindow, + 148, + 450, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, + -1, + -1, + -1, + 504, + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + // CANCEL + btn = buttonCreate(gPreferencesWindow, + 263, + 450, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, + gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, + -1, + -1, + -1, + 528, + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], + gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + fontSetCurrent(101); + + windowRefresh(gPreferencesWindow); + + return 0; +} + +// 0x492870 +int preferencesWindowFree() +{ + if (_changed) { + _SavePrefs(1); + _JustUpdate_(); + _combat_highlight_change(); + } + + windowDestroy(gPreferencesWindow); + + for (int index = 0; index < PREFERENCES_WINDOW_FRM_COUNT; index++) { + artUnlock(gPreferencesWindowFrmHandles[index]); + } + + return 0; +} + +// 0x490798 +int _do_prefscreen() +{ + if (preferencesWindowInit() == -1) { + debugPrint("\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: + soundPlayFile("ib1p1xx1"); + // FALLTHROUGH + case 504: + rc = 1; + break; + case KEY_CTRL_Q: + case KEY_CTRL_X: + case KEY_F10: + showQuitConfirmationDialog(); + break; + case KEY_EQUAL: + case KEY_PLUS: + brightnessIncrease(); + break; + case KEY_MINUS: + case KEY_UNDERSCORE: + brightnessDecrease(); + break; + case KEY_F12: + takeScreenshot(); + break; + case 527: + preferencesSetDefaults(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; + } + } + + preferencesWindowFree(); + + return rc; +} + +// 0x490E8C +void _DoThing(int eventCode) +{ + int x; + int y; + mouseGetPosition(&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 = &(gPreferenceDescriptions[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 + word_48FBFE[0]; + if (y >= v14 && y <= v14 + fontGetLineHeight()) { + if (x >= meta->minX && x <= meta->knobX) { + *valuePtr = 0; + meta->direction = 0; + valueChanged = true; + } else { + if (meta->valuesCount >= 3 && x >= meta->knobX + word_48FBF6[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 + word_48FBFE[3]; + if (y >= v19 && y <= v19 + 2 * fontGetLineHeight() && x >= meta->knobX + word_48FBF6[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) { + soundPlayFile("ib3p1xx1"); + coreDelay(70); + soundPlayFile("ib3lu1x1"); + _UpdateThing(preferenceIndex); + windowRefresh(gPreferencesWindow); + _changed = true; + return; + } + } else if (preferenceIndex >= FIRST_SECONDARY_PREF && preferenceIndex <= LAST_SECONDARY_PREF) { + PreferenceDescription* meta = &(gPreferenceDescriptions[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 + fontGetLineHeight() + 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) { + soundPlayFile("ib2p1xx1"); + coreDelay(70); + soundPlayFile("ib2lu1x1"); + _UpdateThing(preferenceIndex); + windowRefresh(gPreferencesWindow); + _changed = true; + return; + } + } else if (preferenceIndex >= FIRST_RANGE_PREF && preferenceIndex <= LAST_RANGE_PREF) { + PreferenceDescription* meta = &(gPreferenceDescriptions[preferenceIndex]); + int* valuePtr = meta->valuePtr; + + soundPlayFile("ib1p1xx1"); + + double value; + switch (preferenceIndex) { + case PREF_TEXT_BASE_DELAY: + value = 6.0 - gPreferencesTextBaseDelay1 + 1.0; + break; + case PREF_BRIGHTNESS: + value = gPreferencesBrightness1; + break; + case PREF_MOUSE_SENSITIVIY: + value = gPreferencesMouseSensitivity1; + 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); + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * meta->knobY + 384, 240, 12, 640, gPreferencesWindowBuffer + 640 * meta->knobY + 384, 640); + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + v31, 640); + + windowRefresh(gPreferencesWindow); + + int sfxVolumeExample = 0; + int speechVolumeExample = 0; + while (true) { + _get_input(); + + int tick = _get_time(); + + mouseGetPosition(&x, &y); + + if (mouseGetEvent() & 0x10) { + soundPlayFile("ib1lu1x1"); + _UpdateThing(preferenceIndex); + windowRefresh(gPreferencesWindow); + _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: + gPreferencesTextBaseDelay1 = 6.0 - newValue + 1.0; + break; + case PREF_MASTER_VOLUME: + *meta->valuePtr = (int)newValue; + gameSoundSetMasterVolume(gPreferencesMasterVolume1); + v52 = 1; + break; + case PREF_MUSIC_VOLUME: + *meta->valuePtr = (int)newValue; + backgroundSoundSetVolume(gPreferencesMusicVolume1); + v52 = 1; + break; + case PREF_SFX_VOLUME: + *meta->valuePtr = (int)newValue; + soundEffectsSetVolume(gPreferencesSoundEffectsVolume1); + v52 = 1; + if (sfxVolumeExample == 0) { + soundPlayFile("butin1"); + sfxVolumeExample = 7; + } else { + sfxVolumeExample--; + } + break; + case PREF_SPEECH_VOLUME: + *meta->valuePtr = (int)newValue; + speechSetVolume(gPreferencesSpeechVolume1); + v52 = 1; + if (speechVolumeExample == 0) { + speechLoad("narrator\\options", 12, 13, 15); + speechVolumeExample = 40; + } else { + speechVolumeExample--; + } + break; + case PREF_BRIGHTNESS: + gPreferencesBrightness1 = newValue; + colorSetBrightness(newValue); + break; + case PREF_MOUSE_SENSITIVIY: + gPreferencesMouseSensitivity1 = newValue; + break; + } + + if (v52) { + int off = 640 * (meta->knobY - 12) + 384; + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 24, 640, gPreferencesWindowBuffer + off, 640); + + for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) { + const char* str = getmsg(&gOptionsMessageList, &gOptionsMessageListItem, 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 - fontGetStringWidth(str); + break; + case 3: + // This code path does not use floating-point arithmetic + x = 504 - fontGetStringWidth(str) / 2 - 2; + break; + case 4: + // Uses floating-point arithmetic + x = 444 + fontGetStringWidth(str) / 2 - 8; + break; + } + break; + case 2: + // 0x492766 + switch (meta->valuesCount) { + case 3: + x = 624 - fontGetStringWidth(str); + break; + case 4: + // Uses floating-point arithmetic + x = 564 - fontGetStringWidth(str) - 4; + break; + } + break; + case 3: + // 0x49279E + x = 624 - fontGetStringWidth(str); + break; + } + fontDrawText(gPreferencesWindowBuffer + 640 * (meta->knobY - 12) + x, str, 640, 640, _colorTable[18979]); + } + } else { + int off = 640 * meta->knobY + 384; + blitBufferToBuffer(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 12, 640, gPreferencesWindowBuffer + off, 640); + } + + blitBufferToBufferTrans(gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, gPreferencesWindowBuffer + 640 * meta->knobY + v31, 640); + windowRefresh(gPreferencesWindow); + + while (getTicksSince(tick) < 35) + ; + } + } else if (preferenceIndex == 19) { + gPreferencesPlayerSpeedup1 ^= 1; + } + + _changed = true; +} + +// 0x48FC48 +int _do_options() +{ + return showOptionsWithInitialKeyCode(-1); +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..f0b72c9 --- /dev/null +++ b/src/options.h @@ -0,0 +1,213 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "art.h" +#include "db.h" +#include "message.h" +#include "geometry.h" + +#include + +#define OPTIONS_WINDOW_BUTTONS_COUNT (10) +#define PRIMARY_OPTION_VALUE_COUNT (4) +#define SECONDARY_OPTION_VALUE_COUNT (2) + +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"); + +extern const int _row1Ytab[5]; +extern const int _row2Ytab[6]; +extern const int _row3Ytab[8]; +extern const short word_48FBF6[PRIMARY_OPTION_VALUE_COUNT]; +extern const short word_48FBFE[PRIMARY_OPTION_VALUE_COUNT]; +extern const short word_48FC06[SECONDARY_OPTION_VALUE_COUNT]; +extern const int gPauseWindowFrmIds[PAUSE_WINDOW_FRM_COUNT]; +extern const int dword_48FC30[SECONDARY_PREF_COUNT]; +extern const int dword_48FC1C[PRIMARY_PREF_COUNT]; + +extern const double dbl_50C168; +extern const double dbl_50C170; +extern const double dbl_50C178; +extern const double dbl_50C180; +extern const double dbl_50C2D0; +extern const double dbl_50C2D8; +extern const double dbl_50C2E0; + +extern const int gOptionsWindowFrmIds[OPTIONS_WINDOW_FRM_COUNT]; +extern const int gPreferencesWindowFrmIds[PREFERENCES_WINDOW_FRM_COUNT]; +extern PreferenceDescription gPreferenceDescriptions[PREF_COUNT]; + +extern Size gOptionsWindowFrmSizes[OPTIONS_WINDOW_FRM_COUNT]; +extern MessageList gOptionsMessageList; +extern Size gPreferencesWindowFrmSizes[PREFERENCES_WINDOW_FRM_COUNT]; +extern MessageListItem gOptionsMessageListItem; +extern unsigned char* gPreferencesWindowFrmData[PREFERENCES_WINDOW_FRM_COUNT]; +extern unsigned char* _opbtns[OPTIONS_WINDOW_BUTTONS_COUNT]; +extern CacheEntry* gPreferencesWindowFrmHandles[PREFERENCES_WINDOW_FRM_COUNT]; +extern double gPreferencesTextBaseDelay2; +extern double gPreferencesBrightness1; +extern double gPreferencesBrightness2; +extern double gPreferencesTextBaseDelay1; +extern double gPreferencesMouseSensitivity1; +extern double gPreferencesMouseSensitivity2; +extern unsigned char* gPreferencesWindowBuffer; +extern bool gOptionsWindowGameMouseObjectsWasVisible; +extern int gOptionsWindow; +extern int gPreferencesWindow; +extern unsigned char* gOptionsWindowBuffer; +extern CacheEntry* gOptionsWindowFrmHandles[OPTIONS_WINDOW_FRM_COUNT]; +extern unsigned char* gOptionsWindowFrmData[OPTIONS_WINDOW_FRM_COUNT]; +extern int gPreferencesGameDifficulty2; +extern int gPreferencesCombatDifficulty2; +extern int gPreferencesViolenceLevel2; +extern int gPreferencesTargetHighlight2; +extern int gPreferencesCombatLooks2; +extern int gPreferencesCombatMessages2; +extern int gPreferencesCombatTaunts2; +extern int gPreferencesLanguageFilter2; +extern int gPreferencesRunning2; +extern int gPreferencesSubtitles2; +extern int gPreferencesItemHighlight2; +extern int gPreferencesCombatSpeed2; +extern int gPreferencesPlayerSpeedup2; +extern int gPreferencesMasterVolume2; +extern int gPreferencesMusicVolume2; +extern int gPreferencesSoundEffectsVolume2; +extern int gPreferencesSpeechVolume2; +extern int gPreferencesSoundEffectsVolume1; +extern int gPreferencesSubtitles1; +extern int gPreferencesLanguageFilter1; +extern int gPreferencesSpeechVolume1; +extern int gPreferencesMasterVolume1; +extern int gPreferencesPlayerSpeedup1; +extern int gPreferencesCombatTaunts1; +extern int gOptionsWindowOldFont; +extern int gPreferencesMusicVolume1; +extern bool gOptionsWindowIsoWasEnabled; +extern int gPreferencesRunning1; +extern int gPreferencesCombatSpeed1; +extern int _plyrspdbid; +extern int gPreferencesItemHighlight1; +extern bool _changed; +extern int gPreferencesCombatMessages1; +extern int gPreferencesTargetHighlight1; +extern int gPreferencesCombatDifficulty1; +extern int gPreferencesViolenceLevel1; +extern int gPreferencesGameDifficulty1; +extern int gPreferencesCombatLooks1; + +int showOptions(); +int showOptionsWithInitialKeyCode(int initialKeyCode); +int optionsWindowInit(); +int optionsWindowFree(); +int showPause(bool a1); +void _ShadeScreen(bool a1); +void _SetSystemPrefs(); +void _SaveSettings(); +void _RestoreSettings(); +void preferencesSetDefaults(bool a1); +void _JustUpdate_(); +void _UpdateThing(int index); +int _init_options_menu(); +int _SavePrefs(bool save); +int preferencesSave(File* stream); +int preferencesLoad(File* stream); +void brightnessIncrease(); +void brightnessDecrease(); +int preferencesWindowInit(); +int preferencesWindowFree(); +int _do_prefscreen(); +void _DoThing(int eventCode); +void brightnessIncrease(); +void brightnessDecrease(); +int _do_options(); + +#endif /* OPTIONS_H */ diff --git a/src/palette.c b/src/palette.c new file mode 100644 index 0000000..57c6f7d --- /dev/null +++ b/src/palette.c @@ -0,0 +1,106 @@ +#include "palette.h" + +#include "color.h" +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "game_sound.h" + +#include + +// 0x6639D0 +unsigned char gPalette[256 * 3]; + +// 0x663CD0 +unsigned char gPaletteWhite[256 * 3]; + +// 0x663FD0 +unsigned char gPaletteBlack[256 * 3]; + +// 0x6642D0 +int gPaletteFadeSteps; + +// 0x493A00 +void paletteInit() +{ + memset(gPaletteBlack, 0, 256 * 3); + memset(gPaletteWhite, 63, 256 * 3); + memcpy(gPalette, _cmap, 256 * 3); + + unsigned int tick = _get_time(); + if (backgroundSoundIsEnabled() || speechIsEnabled()) { + colorPaletteSetTransitionCallback(soundContinueAll); + } + + colorPaletteFadeBetween(gPalette, gPalette, 60); + + colorPaletteSetTransitionCallback(NULL); + + unsigned int diff = getTicksSince(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; + } + + gPaletteFadeSteps = (int)(60.0 / (diff * (1.0 / 700.0))); + + debugPrint("\nFade time is %u\nFade steps are %d\n", diff, gPaletteFadeSteps); +} + +// NOTE: Collapsed. +// +// 0x493AD0 +void _palette_reset_() +{ +} + +// NOTE: Uncollapsed 0x493AD0. +void paletteReset() +{ + _palette_reset_(); +} + +// NOTE: Uncollapsed 0x493AD0. +void paletteExit() +{ + _palette_reset_(); +} + +// 0x493AD4 +void paletteFadeTo(unsigned char* palette) +{ + bool colorCycleWasEnabled = colorCycleEnabled(); + colorCycleDisable(); + + if (backgroundSoundIsEnabled() || speechIsEnabled()) { + colorPaletteSetTransitionCallback(soundContinueAll); + } + + colorPaletteFadeBetween(gPalette, palette, gPaletteFadeSteps); + colorPaletteSetTransitionCallback(NULL); + + memcpy(gPalette, palette, 768); + + if (colorCycleWasEnabled) { + colorCycleEnable(); + } +} + +// 0x493B48 +void paletteSetEntries(unsigned char* palette) +{ + memcpy(gPalette, palette, sizeof(gPalette)); + _setSystemPalette(palette); +} + +// 0x493B78 +void paletteSetEntriesInRange(unsigned char* palette, int start, int end) +{ + memcpy(gPalette + 3 * start, palette, 3 * (end - start + 1)); + _setSystemPaletteEntries(palette, start, end); +} diff --git a/src/palette.h b/src/palette.h new file mode 100644 index 0000000..fb4c31f --- /dev/null +++ b/src/palette.h @@ -0,0 +1,17 @@ +#ifndef PALETTE_H +#define PALETTE_H + +extern unsigned char gPalette[256 * 3]; +extern unsigned char gPaletteWhite[256 * 3]; +extern unsigned char gPaletteBlack[256 * 3]; +extern int gPaletteFadeSteps; + +void paletteInit(); +void _palette_reset_(); +void paletteReset(); +void paletteExit(); +void paletteFadeTo(unsigned char* palette); +void paletteSetEntries(unsigned char* palette); +void paletteSetEntriesInRange(unsigned char* palette, int start, int end); + +#endif /* PALETTE_H */ diff --git a/src/party_member.c b/src/party_member.c new file mode 100644 index 0000000..b30a9d2 --- /dev/null +++ b/src/party_member.c @@ -0,0 +1,1625 @@ +#include "party_member.h" + +#include "combat_ai.h" +#include "animation.h" +#include "color.h" +#include "config.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_dialog.h" +#include "item.h" +#include "loadsave.h" +#include "map.h" +#include "memory.h" +#include "message.h" +#include "object.h" +#include "proto.h" +#include "proto_instance.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "string_parsers.h" +#include "text_object.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include + +// 0x519D9C +int gPartyMemberDescriptionsLength = 0; + +// 0x519DA0 +int* gPartyMemberPids = NULL; + +// +STRUCT_519DA8* _itemSaveListHead = NULL; + +// List of party members, it's length is [gPartyMemberDescriptionsLength] + 20. +// +// 0x519DA8 +STRUCT_519DA8* gPartyMembers = NULL; + +// Number of critters added to party. +// +// 0x519DAC +int gPartyMembersLength = 0; + +// 0x519DB0 +int _partyMemberItemCount = 20000; + +// 0x519DB4 +int _partyStatePrepped = 0; + +// 0x519DB8 +PartyMemberDescription* gPartyMemberDescriptions = NULL; + +// 0x519DBC +STRU_519DBC* _partyMemberLevelUpInfoList = NULL; + +// 0x519DC0 +int _curID = 20000; + +// partyMember_init +// 0x493BC0 +int partyMembersInit() +{ + Config config; + + gPartyMemberDescriptionsLength = 0; + + if (!configInit(&config)) { + return -1; + } + + if (!configRead(&config, "data\\party.txt", true)) { + goto err; + } + + char section[50]; + sprintf(section, "Party Member %d", gPartyMemberDescriptionsLength); + + int partyMemberPid; + while (configGetInt(&config, section, "party_member_pid", &partyMemberPid)) { + gPartyMemberDescriptionsLength++; + sprintf(section, "Party Member %d", gPartyMemberDescriptionsLength); + } + + gPartyMemberPids = internal_malloc(sizeof(*gPartyMemberPids) * gPartyMemberDescriptionsLength); + if (gPartyMemberPids == NULL) { + goto err; + } + + memset(gPartyMemberPids, 0, sizeof(*gPartyMemberPids) * gPartyMemberDescriptionsLength); + + gPartyMembers = internal_malloc(sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20)); + if (gPartyMembers == NULL) { + goto err; + } + + memset(gPartyMembers, 0, sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20)); + + gPartyMemberDescriptions = internal_malloc(sizeof(*gPartyMemberDescriptions) * gPartyMemberDescriptionsLength); + if (gPartyMemberDescriptions == NULL) { + goto err; + } + + memset(gPartyMemberDescriptions, 0, sizeof(*gPartyMemberDescriptions) * gPartyMemberDescriptionsLength); + + _partyMemberLevelUpInfoList = internal_malloc(sizeof(*_partyMemberLevelUpInfoList) * gPartyMemberDescriptionsLength); + if (_partyMemberLevelUpInfoList == NULL) goto err; + + memset(_partyMemberLevelUpInfoList, 0, sizeof(*_partyMemberLevelUpInfoList) * gPartyMemberDescriptionsLength); + + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + sprintf(section, "Party Member %d", index); + + if (!configGetInt(&config, section, "party_member_pid", &partyMemberPid)) { + break; + } + + PartyMemberDescription* partyMemberDescription = &(gPartyMemberDescriptions[index]); + + gPartyMemberPids[index] = partyMemberPid; + + partyMemberDescriptionInit(partyMemberDescription); + + char* string; + + if (configGetString(&config, section, "area_attack_mode", &string)) { + while (*string != '\0') { + int areaAttackMode; + strParseStrFromList(&string, &areaAttackMode, gAreaAttackModeKeys, AREA_ATTACK_MODE_COUNT); + partyMemberDescription->areaAttackMode[areaAttackMode] = true; + } + } + + if (configGetString(&config, section, "attack_who", &string)) { + while (*string != '\0') { + int attachWho; + strParseStrFromList(&string, &attachWho, gAttackWhoKeys, ATTACK_WHO_COUNT); + partyMemberDescription->attackWho[attachWho] = true; + } + } + + if (configGetString(&config, section, "best_weapon", &string)) { + while (*string != '\0') { + int bestWeapon; + strParseStrFromList(&string, &bestWeapon, gBestWeaponKeys, BEST_WEAPON_COUNT); + partyMemberDescription->bestWeapon[bestWeapon] = true; + } + } + + if (configGetString(&config, section, "chem_use", &string)) { + while (*string != '\0') { + int chemUse; + strParseStrFromList(&string, &chemUse, gChemUseKeys, CHEM_USE_COUNT); + partyMemberDescription->chemUse[chemUse] = true; + } + } + + if (configGetString(&config, section, "distance", &string)) { + while (*string != '\0') { + int distanceMode; + strParseStrFromList(&string, &distanceMode, gDistanceModeKeys, DISTANCE_COUNT); + partyMemberDescription->distanceMode[distanceMode] = true; + } + } + + if (configGetString(&config, section, "run_away_mode", &string)) { + while (*string != '\0') { + int runAwayMode; + strParseStrFromList(&string, &runAwayMode, gRunAwayModeKeys, RUN_AWAY_MODE_COUNT); + partyMemberDescription->runAwayMode[runAwayMode] = true; + } + } + + if (configGetString(&config, section, "disposition", &string)) { + while (*string != '\0') { + int disposition; + strParseStrFromList(&string, &disposition, gDispositionKeys, DISPOSITION_COUNT); + partyMemberDescription->disposition[disposition] = true; + } + } + + int levelUpEvery; + if (configGetInt(&config, section, "level_up_every", &levelUpEvery)) { + partyMemberDescription->level_up_every = levelUpEvery; + + int levelMinimum; + if (configGetInt(&config, section, "level_minimum", &levelMinimum)) { + partyMemberDescription->level_minimum = levelMinimum; + } + + if (configGetString(&config, section, "level_pids", &string)) { + while (*string != '\0' && partyMemberDescription->level_pids_num < 5) { + int levelPid; + strParseInt(&string, &levelPid); + partyMemberDescription->level_pids[partyMemberDescription->level_pids_num] = levelPid; + partyMemberDescription->level_pids_num++; + } + } + } + } + + configFree(&config); + + return 0; + +err: + + configFree(&config); + + return -1; +} + +// 0x4940E4 +void partyMembersReset() +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + _partyMemberLevelUpInfoList[index].field_0 = 0; + _partyMemberLevelUpInfoList[index].field_4 = 0; + _partyMemberLevelUpInfoList[index].field_8 = 0; + } +} + +// 0x494134 +void partyMembersExit() +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + _partyMemberLevelUpInfoList[index].field_0 = 0; + _partyMemberLevelUpInfoList[index].field_4 = 0; + _partyMemberLevelUpInfoList[index].field_8 = 0; + } + + gPartyMemberDescriptionsLength = 0; + + if (gPartyMemberPids != NULL) { + internal_free(gPartyMemberPids); + gPartyMemberPids = NULL; + } + + if (gPartyMembers != NULL) { + internal_free(gPartyMembers); + gPartyMembers = NULL; + } + + if (gPartyMemberDescriptions != NULL) { + internal_free(gPartyMemberDescriptions); + gPartyMemberDescriptions = NULL; + } + + if (_partyMemberLevelUpInfoList != NULL) { + internal_free(_partyMemberLevelUpInfoList); + _partyMemberLevelUpInfoList = NULL; + } +} + +// 0x4941F0 +int partyMemberGetDescription(Object* object, PartyMemberDescription** partyMemberDescriptionPtr) +{ + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + if (gPartyMemberPids[index] == object->pid) { + *partyMemberDescriptionPtr = &(gPartyMemberDescriptions[index]); + return 0; + } + } + + return -1; +} + +// 0x49425C +void partyMemberDescriptionInit(PartyMemberDescription* partyMemberDescription) +{ + for (int index = 0; index < AREA_ATTACK_MODE_COUNT; index++) { + partyMemberDescription->areaAttackMode[index] = 0; + } + + for (int index = 0; index < RUN_AWAY_MODE_COUNT; index++) { + partyMemberDescription->runAwayMode[index] = 0; + } + + for (int index = 0; index < BEST_WEAPON_COUNT; index++) { + partyMemberDescription->bestWeapon[index] = 0; + } + + for (int index = 0; index < DISTANCE_COUNT; index++) { + partyMemberDescription->distanceMode[index] = 0; + } + + for (int index = 0; index < ATTACK_WHO_COUNT; index++) { + partyMemberDescription->attackWho[index] = 0; + } + + for (int index = 0; index < CHEM_USE_COUNT; index++) { + partyMemberDescription->chemUse[index] = 0; + } + + for (int index = 0; index < DISPOSITION_COUNT; index++) { + partyMemberDescription->disposition[index] = 0; + } + + partyMemberDescription->level_minimum = 0; + partyMemberDescription->level_up_every = 0; + partyMemberDescription->level_pids_num = 0; + + partyMemberDescription->level_pids[0] = -1; + + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + _partyMemberLevelUpInfoList[index].field_0 = 0; + _partyMemberLevelUpInfoList[index].field_4 = 0; + _partyMemberLevelUpInfoList[index].field_8 = 0; + } +} + +// partyMemberAdd +// 0x494378 +int partyMemberAdd(Object* object) +{ + if (gPartyMembersLength >= gPartyMemberDescriptionsLength + 20) { + return -1; + } + + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + if (partyMember->object == object || partyMember->object->pid == object->pid) { + return 0; + } + } + + if (_partyStatePrepped) { + debugPrint("\npartyMemberAdd DENIED: %s\n", critterGetName(object)); + return -1; + } + + STRUCT_519DA8* partyMember = &(gPartyMembers[gPartyMembersLength]); + partyMember->object = object; + partyMember->script = NULL; + partyMember->vars = NULL; + + object->id = (object->pid & 0xFFFFFF) + 18000; + object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); + + gPartyMembersLength++; + + Script* script; + if (scriptGetScript(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; + } + + critterSetTeam(object, 0); + queueRemoveEventsByType(object, EVENT_TYPE_SCRIPT); + + if (_gdialogActive()) { + if (object == gGameDialogSpeaker) { + _gdialogUpdatePartyStatus(); + } + } + + return 0; +} + +// partyMemberRemove +// 0x4944DC +int partyMemberRemove(Object* object) +{ + if (gPartyMembersLength == 0) { + return -1; + } + + if (object == NULL) { + return -1; + } + + int index; + for (index = 1; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + if (partyMember->object == object) { + break; + } + } + + if (index == gPartyMembersLength) { + return -1; + } + + if (_partyStatePrepped) { + debugPrint("\npartyMemberRemove DENIED: %s\n", critterGetName(object)); + return -1; + } + + if (index < gPartyMembersLength - 1) { + gPartyMembers[index].object = gPartyMembers[gPartyMembersLength - 1].object; + } + + object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); + + gPartyMembersLength--; + + Script* script; + if (scriptGetScript(object->sid, &script) != -1) { + script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); + } + + queueRemoveEventsByType(object, EVENT_TYPE_SCRIPT); + + if (_gdialogActive()) { + if (object == gGameDialogSpeaker) { + _gdialogUpdatePartyStatus(); + } + } + + return 0; +} + +// 0x49460C +int _partyMemberPrepSave() +{ + _partyStatePrepped = 1; + + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* ptr = &(gPartyMembers[index]); + + if (index > 0) { + ptr->object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); + } + + Script* script; + if (scriptGetScript(ptr->object->sid, &script) != -1) { + script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); + } + } + + return 0; +} + +// 0x49466C +int _partyMemberUnPrepSave() +{ + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* ptr = &(gPartyMembers[index]); + + if (index > 0) { + ptr->object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); + } + + Script* script; + if (scriptGetScript(ptr->object->sid, &script) != -1) { + script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); + } + } + + _partyStatePrepped = 0; + + return 0; +} + +// 0x4946CC +int partyMembersSave(File* stream) +{ + if (fileWriteInt32(stream, gPartyMembersLength) == -1) return -1; + if (fileWriteInt32(stream, _partyMemberItemCount) == -1) return -1; + + for (int index = 1; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + if (fileWriteInt32(stream, partyMember->object->id) == -1) return -1; + } + + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + STRU_519DBC* ptr = &(_partyMemberLevelUpInfoList[index]); + if (fileWriteInt32(stream, ptr->field_0) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_4) == -1) return -1; + if (fileWriteInt32(stream, ptr->field_8) == -1) return -1; + } + + return 0; +} + +// 0x4947AC +int _partyMemberPrepLoad() +{ + if (_partyStatePrepped) { + return -1; + } + + _partyStatePrepped = 1; + + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* ptr_519DA8 = &(gPartyMembers[index]); + if (_partyMemberPrepLoadInstance(ptr_519DA8) != 0) { + return -1; + } + } + + return 0; +} + +// partyMemberPrepLoadInstance +// 0x49480C +int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1) +{ + Object* obj = a1->object; + + if (obj == NULL) { + debugPrint("\n Error!: partyMemberPrepLoadInstance: No Critter Object!"); + a1->script = NULL; + a1->vars = NULL; + a1->next = NULL; + return 0; + } + + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + obj->data.critter.combat.whoHitMe = NULL; + } + + Script* script; + if (scriptGetScript(obj->sid, &script) == -1) { + debugPrint("\n Error!: partyMemberPrepLoadInstance: Can't find script!"); + debugPrint("\n partyMemberPrepLoadInstance: script was: (%s)", critterGetName(obj)); + a1->script = NULL; + a1->vars = NULL; + a1->next = NULL; + return 0; + } + + a1->script = internal_malloc(sizeof(*script)); + if (a1->script == NULL) { + showMesageBox("\n Error!: partyMemberPrepLoad: Out of memory!"); + exit(1); + } + + memcpy(a1->script, script, sizeof(*script)); + + if (script->localVarsCount != 0 && script->localVarsOffset != -1) { + a1->vars = internal_malloc(sizeof(*a1->vars) * script->localVarsCount); + if (a1->vars == NULL) { + showMesageBox("\n Error!: partyMemberPrepLoad: Out of memory!"); + exit(1); + } + + if (gMapLocalVars != NULL) { + memcpy(a1->vars, gMapLocalVars + script->localVarsOffset, sizeof(int) * script->localVarsCount); + } else { + debugPrint("\nWarning: partyMemberPrepLoadInstance: No map_local_vars found, but script references them!"); + memset(a1->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); + + scriptRemove(script->sid); + + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + _dude_stand(obj, obj->rotation, -1); + } + + return 0; +} + +// partyMemberRecoverLoad +// 0x4949C4 +int _partyMemberRecoverLoad() +{ + if (_partyStatePrepped != 1) { + debugPrint("\npartyMemberRecoverLoad DENIED"); + return -1; + } + + debugPrint("\n"); + + for (int index = 0; index < gPartyMembersLength; index++) { + if (_partyMemberRecoverLoadInstance(&(gPartyMembers[index])) != 0) { + return -1; + } + + debugPrint("[Party Member %d]: %s\n", index, critterGetName(gPartyMembers[index].object)); + } + + STRUCT_519DA8* v6 = _itemSaveListHead; + while (v6 != NULL) { + _itemSaveListHead = v6->next; + + _partyMemberItemRecover(v6); + internal_free(v6); + + v6 = _itemSaveListHead; + } + + _partyStatePrepped = 0; + + if (!_isLoadingGame()) { + _partyFixMultipleMembers(); + } + + return 0; +} + +// partyMemberRecoverLoadInstance +// 0x494A88 +int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1) +{ + if (a1->script == NULL) { + showMesageBox("\n Error!: partyMemberRecoverLoadInstance: No script!"); + return 0; + } + + int scriptType = SCRIPT_TYPE_CRITTER; + if ((a1->object->pid >> 24) != OBJ_TYPE_CRITTER) { + scriptType = SCRIPT_TYPE_ITEM; + } + + int v1 = -1; + if (scriptAdd(&v1, scriptType) == -1) { + showMesageBox("\n Error!: partyMemberRecoverLoad: Can't create script!"); + exit(1); + } + + Script* script; + if (scriptGetScript(v1, &script) == -1) { + showMesageBox("\n Error!: partyMemberRecoverLoad: Can't find script!"); + exit(1); + } + + memcpy(script, a1->script, sizeof(*script)); + + int sid = (scriptType << 24) | ((a1->object->pid & 0xFFFFFF) + 18000); + a1->object->sid = sid; + script->sid = sid; + + script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04); + + internal_free(a1->script); + a1->script = NULL; + + script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); + + if (a1->vars != NULL) { + script->localVarsOffset = _map_malloc_local_var(script->localVarsCount); + memcpy(gMapLocalVars + script->localVarsOffset, a1->vars, sizeof(int) * script->localVarsCount); + } + + return 0; +} + +// 0x494BBC +int partyMembersLoad(File* stream) +{ + int* partyMemberObjectIds = internal_malloc(sizeof(*partyMemberObjectIds) * (gPartyMemberDescriptionsLength + 20)); + if (partyMemberObjectIds == NULL) { + return -1; + } + + // FIXME: partyMemberObjectIds is never free'd in this function, obviously memory leak. + + if (fileReadInt32(stream, &gPartyMembersLength) == -1) return -1; + if (fileReadInt32(stream, &_partyMemberItemCount) == -1) return -1; + + gPartyMembers->object = gDude; + + if (gPartyMembersLength != 0) { + for (int index = 1; index < gPartyMembersLength; index++) { + if (fileReadInt32(stream, &(partyMemberObjectIds[index])) == -1) return -1; + } + + for (int index = 1; index < gPartyMembersLength; index++) { + int objectId = partyMemberObjectIds[index]; + + Object* object = objectFindFirst(); + while (object != NULL) { + if (object->id == objectId) { + break; + } + object = objectFindNext(); + } + + if (object != NULL) { + gPartyMembers[index].object = object; + } else { + debugPrint("Couldn't find party member on map...trying to load anyway.\n"); + if (index + 1 >= gPartyMembersLength) { + partyMemberObjectIds[index] = 0; + } else { + memcpy(&(partyMemberObjectIds[index]), &(partyMemberObjectIds[index + 1]), sizeof(*partyMemberObjectIds) * (gPartyMembersLength - (index + 1))); + } + + index--; + gPartyMembersLength--; + } + } + + if (_partyMemberUnPrepSave() == -1) { + return -1; + } + } + + _partyFixMultipleMembers(); + + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + STRU_519DBC* ptr_519DBC = &(_partyMemberLevelUpInfoList[index]); + + if (fileReadInt32(stream, &(ptr_519DBC->field_0)) == -1) return -1; + if (fileReadInt32(stream, &(ptr_519DBC->field_4)) == -1) return -1; + if (fileReadInt32(stream, &(ptr_519DBC->field_8)) == -1) return -1; + } + + return 0; +} + +// 0x494D7C +void _partyMemberClear() +{ + if (_partyStatePrepped) { + _partyMemberUnPrepSave(); + } + + for (int index = gPartyMembersLength; index > 1; index--) { + partyMemberRemove(gPartyMembers[1].object); + } + + gPartyMembersLength = 1; + + _scr_remove_all(); + _partyMemberClearItemList(); + + _partyStatePrepped = 0; +} + +// 0x494DD0 +int _partyMemberSyncPosition() +{ + int clockwiseRotation = (gDude->rotation + 2) % ROTATION_COUNT; + int counterClockwiseRotation = (gDude->rotation + 4) % ROTATION_COUNT; + + int n = 0; + int distance = 2; + for (int index = 1; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + Object* partyMemberObj = partyMember->object; + if ((partyMemberObj->flags & OBJECT_HIDDEN) == 0 && (partyMemberObj->pid >> 24) == OBJ_TYPE_CRITTER) { + int rotation; + if ((n % 2) != 0) { + rotation = clockwiseRotation; + } else { + rotation = counterClockwiseRotation; + } + + int tile = tileGetTileInDirection(gDude->tile, rotation, distance / 2); + _objPMAttemptPlacement(partyMemberObj, tile, gDude->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 < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + if ((partyMember->object->pid >> 24) == OBJ_TYPE_CRITTER) { + int healingRate = critterGetStat(partyMember->object, STAT_HEALING_RATE); + critterAdjustHitPoints(partyMember->object, v1 * healingRate); + } + } + + return 1; +} + +// 0x494F24 +Object* partyMemberFindByPid(int pid) +{ + for (int index = 0; index < gPartyMembersLength; index++) { + Object* object = gPartyMembers[index].object; + if (object->pid == pid) { + return object; + } + } + + return NULL; +} + +// 0x494F64 +bool _isPotentialPartyMember(Object* object) +{ + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + if (partyMember->object->pid == gPartyMemberPids[index]) { + return true; + } + } + + return false; +} + +// Returns `true` if specified object is a party member. +// +// 0x494FC4 +bool objectIsPartyMember(Object* object) +{ + if (object == NULL) { + return false; + } + + if (object->id < 18000) { + return false; + } + + bool isPartyMember = false; + + for (int index = 0; index < gPartyMembersLength; index++) { + if (gPartyMembers[index].object == object) { + isPartyMember = true; + break; + } + } + + return isPartyMember; +} + +// Returns number of active critters in the party. +// +// 0x495010 +int _getPartyMemberCount() +{ + int count = gPartyMembersLength; + + for (int index = 1; index < gPartyMembersLength; index++) { + Object* object = gPartyMembers[index].object; + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER || critterIsDead(object) || (object->flags & OBJECT_HIDDEN) != 0) { + count--; + } + } + + return count; +} + +// 0x495070 +int _partyMemberNewObjID() +{ + Object* object; + + do { + _curID++; + + object = objectFindFirst(); + 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 = objectFindNext(); + } + } while (object != NULL); + + _curID++; + + return _curID; +} + +// 0x4950F4 +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 < gPartyMembersLength; partyMemberIndex++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[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; +} + +// partyMemberPrepItemSaveAll +int _partyMemberPrepItemSave(Object* object) +{ + if (object->sid != -1) { + Script* script; + if (scriptGetScript(object->sid, &script) == -1) { + showMesageBox("\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 +int _partyMemberItemSave(Object* object) +{ + if (object->sid != -1) { + Script* script; + if (scriptGetScript(object->sid, &script) == -1) { + showMesageBox("\n Error!: partyMemberItemSave: Can't find script!"); + exit(1); + } + + if (object->id < 20000) { + script->field_1C = _partyMemberNewObjID(); + object->id = script->field_1C; + } + + STRUCT_519DA8* node = internal_malloc(sizeof(*node)); + if (node == NULL) { + showMesageBox("\n Error!: partyMemberItemSave: Out of memory!"); + exit(1); + } + + node->object = object; + + node->script = internal_malloc(sizeof(*script)); + if (node->script == NULL) { + showMesageBox("\n Error!: partyMemberItemSave: Out of memory!"); + exit(1); + } + + memcpy(node->script, script, sizeof(*script)); + + if (script->localVarsCount != 0 && script->localVarsOffset != -1) { + node->vars = internal_malloc(sizeof(*node->vars) * script->localVarsCount); + if (node->vars == NULL) { + showMesageBox("\n Error!: partyMemberItemSave: Out of memory!"); + exit(1); + } + + memcpy(node->vars, gMapLocalVars + script->localVarsOffset, sizeof(int) * script->localVarsCount); + } else { + node->vars = NULL; + } + + STRUCT_519DA8* 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; +} + +// partyMemberItemRecover +// 0x495388 +int _partyMemberItemRecover(STRUCT_519DA8* a1) +{ + int sid = -1; + if (scriptAdd(&sid, SCRIPT_TYPE_ITEM) == -1) { + showMesageBox("\n Error!: partyMemberItemRecover: Can't create script!"); + exit(1); + } + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + showMesageBox("\n Error!: partyMemberItemRecover: Can't find script!"); + exit(1); + } + + memcpy(script, a1->script, sizeof(*script)); + + a1->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++; + + internal_free(a1->script); + a1->script = NULL; + + if (a1->vars != NULL) { + script->localVarsOffset = _map_malloc_local_var(script->localVarsCount); + memcpy(gMapLocalVars + script->localVarsOffset, a1->vars, sizeof(int) * script->localVarsCount); + } + + return 0; +} + +// 0x4954C4 +int _partyMemberClearItemList() +{ + while (_itemSaveListHead != NULL) { + STRUCT_519DA8* node = _itemSaveListHead; + _itemSaveListHead = _itemSaveListHead->next; + + if (node->script != NULL) { + internal_free(node->script); + } + + if (node->vars != NULL) { + internal_free(node->vars); + } + + internal_free(node); + } + + _partyMemberItemCount = 20000; + + return 0; +} + +// Returns best skill of the specified party member. +// +// 0x495520 +int partyMemberGetBestSkill(Object* object) +{ + int bestSkill = SKILL_SMALL_GUNS; + + if (object == NULL) { + return bestSkill; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return bestSkill; + } + + int bestValue = 0; + for (int skill = 0; skill < SKILL_COUNT; skill++) { + int value = skillGetValue(object, skill); + if (value > bestValue) { + bestSkill = skill; + bestValue = value; + } + } + + return bestSkill; +} + +// Returns party member with highest skill level. +// +// 0x495560 +Object* partyMemberGetBestInSkill(int skill) +{ + int bestValue = 0; + Object* bestPartyMember = NULL; + + for (int index = 0; index < gPartyMembersLength; index++) { + Object* object = gPartyMembers[index].object; + if ((object->flags & OBJECT_HIDDEN) == 0 && (object->pid >> 24) == OBJ_TYPE_CRITTER) { + int value = skillGetValue(object, skill); + if (value > bestValue) { + bestValue = value; + bestPartyMember = object; + } + } + } + + return bestPartyMember; +} + +// Returns highest skill level in party. +// +// 0x4955C8 +int partyGetBestSkillValue(int skill) +{ + int bestValue = 0; + + for (int index = 0; index < gPartyMembersLength; index++) { + Object* object = gPartyMembers[index].object; + if ((object->flags & OBJECT_HIDDEN) == 0 && (object->pid >> 24) == OBJ_TYPE_CRITTER) { + int value = skillGetValue(object, skill); + if (value > bestValue) { + bestValue = value; + } + } + } + + return bestValue; +} + +// 0x495620 +int _partyFixMultipleMembers() +{ + debugPrint("\n\n\n[Party Members]:"); + + int critterCount = 0; + for (Object* obj = objectFindFirst(); obj != NULL; obj = objectFindNext()) { + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + critterCount++; + } + + bool isPartyMember = false; + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + if (obj->pid == gPartyMemberPids[index]) { + isPartyMember = true; + break; + } + } + + if (!isPartyMember) { + continue; + } + + debugPrint("\n PM: %s", critterGetName(obj)); + + bool v19 = false; + if (obj->sid == -1) { + v19 = true; + } else { + Object* v7 = NULL; + for (int i = 0; i < gPartyMembersLength; i++) { + if (obj->pid == gPartyMembers[i].object->pid) { + v7 = gPartyMembers[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 < gPartyMembersLength; i++) { + if (obj->pid == gPartyMembers[i].object->pid) { + v10 = gPartyMembers[i].object; + } + } + + // TODO: Probably wrong. + if (obj == v10) { + debugPrint("\nError: Attempting to destroy evil critter doppleganger FAILED!"); + continue; + } + + debugPrint("\nDestroying evil critter doppleganger!"); + + if (obj->sid != -1) { + scriptRemove(obj->sid); + obj->sid = -1; + } else { + if (queueRemoveEventsByType(obj, EVENT_TYPE_SCRIPT) == -1) { + debugPrint("\nERROR Removing Timed Events on FIX remove!!\n"); + } + } + + objectDestroy(obj, NULL); + } + + for (int index = 0; index < gPartyMembersLength; index++) { + STRUCT_519DA8* partyMember = &(gPartyMembers[index]); + + Script* script; + if (scriptGetScript(partyMember->object->sid, &script) != -1) { + script->owner = partyMember->object; + } else { + debugPrint("\nError: Failed to fix party member critter scripts!"); + } + } + + debugPrint("\nTotal Critter Count: %d\n\n", critterCount); + + return 0; +} + +// 0x495870 +void _partyMemberSaveProtos() +{ + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + int pid = gPartyMemberPids[index]; + if (pid != -1) { + _proto_save_pid(pid); + } + } +} + +// 0x4958B0 +bool partyMemberSupportsDisposition(Object* critter, int disposition) +{ + if (critter == NULL) { + return false; + } + + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (disposition == -1 || disposition > 5) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(critter, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->disposition[disposition + 1]; +} + +// 0x495920 +bool partyMemberSupportsAreaAttackMode(Object* object, int areaAttackMode) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->areaAttackMode[areaAttackMode]; +} + +// 0x495980 +bool partyMemberSupportsRunAwayMode(Object* object, int runAwayMode) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (runAwayMode >= RUN_AWAY_MODE_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->runAwayMode[runAwayMode + 1]; +} + +// 0x4959E0 +bool partyMemberSupportsBestWeapon(Object* object, int bestWeapon) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (bestWeapon >= BEST_WEAPON_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->bestWeapon[bestWeapon]; +} + +// 0x495A40 +bool partyMemberSupportsDistance(Object* object, int distanceMode) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (distanceMode >= DISTANCE_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->distanceMode[distanceMode]; +} + +// 0x495AA0 +bool partyMemberSupportsAttackWho(Object* object, int attackWho) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (attackWho >= ATTACK_WHO_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->attackWho[attackWho]; +} + +// 0x495B00 +bool partyMemberSupportsChemUse(Object* object, int chemUse) +{ + if (object == NULL) { + return false; + } + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) { + return false; + } + + if (chemUse >= CHEM_USE_COUNT) { + return false; + } + + PartyMemberDescription* partyMemberDescription; + if (partyMemberGetDescription(object, &partyMemberDescription) == -1) { + return false; + } + + return partyMemberDescription->chemUse[chemUse]; +} + +// partyMemberIncLevels +// 0x495B60 +int _partyMemberIncLevels() +{ + int i; + STRUCT_519DA8* ptr; + Object* obj; + PartyMemberDescription* party_member; + const char* name; + int j; + int v0; + STRU_519DBC* ptr_519DBC; + int v24; + char* text; + MessageListItem msg; + char str[260]; + Rect v19; + + v0 = -1; + for (i = 1; i < gPartyMembersLength; i++) { + ptr = &(gPartyMembers[i]); + obj = ptr->object; + + if (partyMemberGetDescription(obj, &party_member) == -1) { + break; + } + + if ((obj->pid >> 24) != 1) { + continue; + } + + name = critterGetName(obj); + debugPrint("\npartyMemberIncLevels: %s", name); + + if (party_member->level_up_every == 0) { + continue; + } + + for (j = 1; j < gPartyMemberDescriptionsLength; j++) { + if (gPartyMemberPids[j] == obj->pid) { + v0 = j; + } + } + + if (v0 == -1) { + continue; + } + + if (pcGetStat(PC_STAT_LEVEL) < party_member->level_minimum) { + continue; + } + + ptr_519DBC = &(_partyMemberLevelUpInfoList[v0]); + + if (ptr_519DBC->field_0 >= party_member->level_pids_num) { + continue; + } + + ptr_519DBC->field_4++; + + v24 = ptr_519DBC->field_4 % party_member->level_pids_num; + debugPrint("pm: levelMod: %d, Lvl: %d, Early: %d, Every: %d", v24, ptr_519DBC->field_4, ptr_519DBC->field_8, party_member->level_up_every); + + if (v24 != 0 || ptr_519DBC->field_8 == 0) { + if (ptr_519DBC->field_8 == 0) { + if (v24 == 0 || randomBetween(0, 100) <= 100 * v24 / party_member->level_up_every) { + ptr_519DBC->field_0++; + if (v24 != 0) { + ptr_519DBC->field_8 = 1; + } + + if (_partyMemberCopyLevelInfo(obj, party_member->level_pids[ptr_519DBC->field_0]) == -1) { + return -1; + } + + name = critterGetName(obj); + // %s has gained in some abilities. + text = getmsg(&gMiscMessageList, &msg, 9000); + sprintf(str, text, name); + displayMonitorAddMessage(text); + + debugPrint(text); + + // Individual message + msg.num = 9000 + 10 * v0 + ptr_519DBC->field_0 - 1; + if (messageListGetItem(&gMiscMessageList, &msg)) { + name = critterGetName(obj); + sprintf(str, msg.text, name); + textObjectAdd(obj, str, 101, _colorTable[0x7FFF], _colorTable[0], &v19); + tileWindowRefreshRect(&v19, obj->elevation); + } + } + } + } else { + ptr_519DBC->field_8 = 0; + } + } + + return 0; +} + +// 0x495EA8 +int _partyMemberCopyLevelInfo(Object* critter, int a2) +{ + if (critter == NULL) { + return -1; + } + + if (a2 == -1) { + return -1; + } + + Proto* proto1; + if (protoGetProto(critter->pid, &proto1) == -1) { + return -1; + } + + Proto* proto2; + if (protoGetProto(a2, &proto2) == -1) { + return -1; + } + + Object* item2 = critterGetItem2(critter); + _invenUnwieldFunc(critter, 1, 0); + + Object* armor = critterGetArmor(critter); + _adjust_ac(critter, armor, NULL); + itemRemove(critter, armor, 1); + + int maxHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS); + critterAdjustHitPoints(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) { + itemAdd(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 partyIsAnyoneCanBeHealedByRest() +{ + for (int index = 1; index < gPartyMembersLength; index++) { + STRUCT_519DA8* ptr = &(gPartyMembers[index]); + Object* object = ptr->object; + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) continue; + if (critterIsDead(object)) continue; + if ((object->flags & OBJECT_HIDDEN) != 0) continue; + if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue; + + int currentHp = critterGetHitPoints(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 partyGetMaxWoundToHealByRest() +{ + int maxWound = 0; + + for (int index = 1; index < gPartyMembersLength; index++) { + STRUCT_519DA8* ptr = &(gPartyMembers[index]); + Object* object = ptr->object; + + if ((object->pid >> 24) != OBJ_TYPE_CRITTER) continue; + if (critterIsDead(object)) continue; + if ((object->flags & OBJECT_HIDDEN) != 0) continue; + if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue; + + int currentHp = critterGetHitPoints(object); + int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS); + int wound = maximumHp - currentHp; + if (wound > 0) { + if (wound > maxWound) { + maxWound = wound; + } + } + } + + return maxWound; +} diff --git a/src/party_member.h b/src/party_member.h new file mode 100644 index 0000000..5931a19 --- /dev/null +++ b/src/party_member.h @@ -0,0 +1,95 @@ +#ifndef PARTY_MEMBER_H +#define PARTY_MEMBER_H + +#include "combat_ai_defs.h" +#include "db.h" +#include "obj_types.h" +#include "scripts.h" + +#include + +typedef struct PartyMemberDescription { + bool areaAttackMode[AREA_ATTACK_MODE_COUNT]; + bool runAwayMode[RUN_AWAY_MODE_COUNT]; + bool bestWeapon[BEST_WEAPON_COUNT]; + bool distanceMode[DISTANCE_COUNT]; + bool attackWho[ATTACK_WHO_COUNT]; + bool chemUse[CHEM_USE_COUNT]; + bool disposition[DISPOSITION_COUNT]; + int level_minimum; + int level_up_every; + int level_pids_num; + int level_pids[5]; +} PartyMemberDescription; + +typedef struct STRU_519DBC { + int field_0; + int field_4; // party member level + int field_8; // early what? +} STRU_519DBC; + +typedef struct STRUCT_519DA8 { + Object* object; + Script* script; + int* vars; + struct STRUCT_519DA8* next; +} STRUCT_519DA8; + +extern int gPartyMemberDescriptionsLength; +extern int* gPartyMemberPids; +extern STRUCT_519DA8* _itemSaveListHead; +extern STRUCT_519DA8* gPartyMembers; +extern int gPartyMembersLength; +extern int _partyMemberItemCount; +extern int _partyStatePrepped; +extern PartyMemberDescription* gPartyMemberDescriptions; +extern STRU_519DBC* _partyMemberLevelUpInfoList; +extern int _curID; + +int partyMembersInit(); +void partyMembersReset(); +void partyMembersExit(); +int partyMemberGetDescription(Object* object, PartyMemberDescription** partyMemberDescriptionPtr); +void partyMemberDescriptionInit(PartyMemberDescription* partyMemberDescription); +int partyMemberAdd(Object* object); +int partyMemberRemove(Object* object); +int _partyMemberPrepSave(); +int _partyMemberUnPrepSave(); +int partyMembersSave(File* stream); +int _partyMemberPrepLoad(); +int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1); +int _partyMemberRecoverLoad(); +int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1); +int partyMembersLoad(File* stream); +void _partyMemberClear(); +int _partyMemberSyncPosition(); +int _partyMemberRestingHeal(int a1); +Object* partyMemberFindByPid(int a1); +bool _isPotentialPartyMember(Object* object); +bool objectIsPartyMember(Object* object); +int _getPartyMemberCount(); +int _partyMemberNewObjID(); +int _partyMemberNewObjIDRecurseFind(Object* object, int objectId); +int _partyMemberPrepItemSaveAll(); +int _partyMemberPrepItemSave(Object* object); +int _partyMemberItemSave(Object* object); +int _partyMemberItemRecover(STRUCT_519DA8* a1); +int _partyMemberClearItemList(); +int partyMemberGetBestSkill(Object* object); +Object* partyMemberGetBestInSkill(int skill); +int partyGetBestSkillValue(int skill); +int _partyFixMultipleMembers(); +void _partyMemberSaveProtos(); +bool partyMemberSupportsDisposition(Object* object, int disposition); +bool partyMemberSupportsAreaAttackMode(Object* object, int areaAttackMode); +bool partyMemberSupportsRunAwayMode(Object* object, int runAwayMode); +bool partyMemberSupportsBestWeapon(Object* object, int bestWeapon); +bool partyMemberSupportsDistance(Object* object, int distanceMode); +bool partyMemberSupportsAttackWho(Object* object, int attackWho); +bool partyMemberSupportsChemUse(Object* object, int chemUse); +int _partyMemberIncLevels(); +int _partyMemberCopyLevelInfo(Object* object, int a2); +bool partyIsAnyoneCanBeHealedByRest(); +int partyGetMaxWoundToHealByRest(); + +#endif /* PARTY_MEMBER_H */ diff --git a/src/perk.c b/src/perk.c new file mode 100644 index 0000000..aa722b2 --- /dev/null +++ b/src/perk.c @@ -0,0 +1,689 @@ +#include "perk.h" + +#include "debug.h" +#include "game.h" +#include "game_config.h" +#include "memory.h" +#include "object.h" +#include "party_member.h" +#include "skill.h" +#include "stat.h" + +#include + +// 0x519DCC +PerkDescription gPerkDescriptions[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 +PerkRankData* gPartyMemberPerkRanks = NULL; + +// Amount of experience points granted when player selected "Here and now" +// perk. +// +// 0x51C124 +int gHereAndNowBonusExperience = 0; + +// perk.msg +// +// 0x6642D4 +MessageList gPerksMessageList; + +// 0x4965A0 +int perksInit() +{ + gPartyMemberPerkRanks = internal_malloc(sizeof(*gPartyMemberPerkRanks) * gPartyMemberDescriptionsLength); + if (gPartyMemberPerkRanks == NULL) { + return -1; + } + + perkResetRanks(); + + if (!messageListInit(&gPerksMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "perk.msg"); + + if (!messageListLoad(&gPerksMessageList, path)) { + return -1; + } + + for (int perk = 0; perk < PERK_COUNT; perk++) { + MessageListItem messageListItem; + + messageListItem.num = 101 + perk; + if (messageListGetItem(&gPerksMessageList, &messageListItem)) { + gPerkDescriptions[perk].name = messageListItem.text; + } + + messageListItem.num = 1101 + perk; + if (messageListGetItem(&gPerksMessageList, &messageListItem)) { + gPerkDescriptions[perk].description = messageListItem.text; + } + } + + return 0; +} + +// 0x4966B0 +void perksReset() +{ + perkResetRanks(); +} + +// 0x4966B8 +void perksExit() +{ + messageListFree(&gPerksMessageList); + + if (gPartyMemberPerkRanks != NULL) { + internal_free(gPartyMemberPerkRanks); + gPartyMemberPerkRanks = NULL; + } +} + +// 0x4966E4 +int perksLoad(File* stream) +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]); + for (int perk = 0; perk < PERK_COUNT; perk++) { + if (fileReadInt32(stream, &(ranksData->ranks[perk])) == -1) { + return -1; + } + } + } + + return 0; +} + +// 0x496738 +int perksSave(File* stream) +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]); + for (int perk = 0; perk < PERK_COUNT; perk++) { + if (fileWriteInt32(stream, ranksData->ranks[perk]) == -1) { + return -1; + } + } + } + + return 0; +} + +// perkGetLevelData +// 0x49678C +PerkRankData* perkGetRankData(Object* critter) +{ + if (critter == gDude) { + return gPartyMemberPerkRanks; + } + + for (int index = 1; index < gPartyMemberDescriptionsLength; index++) { + if (critter->pid == gPartyMemberPids[index]) { + return gPartyMemberPerkRanks + index; + } + } + + debugPrint("\nError: perkGetLevelData: Can't find party member match!"); + + return gPartyMemberPerkRanks; +} + +// 0x49680C +bool perkCanAdd(Object* critter, int perk) +{ + if (!perkIsValid(perk)) { + return false; + } + + PerkDescription* perkDescription = &(gPerkDescriptions[perk]); + + if (perkDescription->maxRank == -1) { + return false; + } + + PerkRankData* ranksData = perkGetRankData(critter); + if (ranksData->ranks[perk] >= perkDescription->maxRank) { + return false; + } + + if (critter == gDude) { + if (pcGetStat(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 (gameGetGlobalVar(param1) >= value1) { + v1 = false; + } + } else { + if (skillGetValue(critter, param1) >= -value1) { + v1 = false; + } + } + } else { + if (isVariable) { + if (gameGetGlobalVar(param1) < value1) { + v1 = false; + } + } else { + if (skillGetValue(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 (gameGetGlobalVar(param2) >= value2) { + return false; + } + } else { + if (skillGetValue(critter, param2) >= -value2) { + return false; + } + } + } else { + if (isVariable) { + if (gameGetGlobalVar(param2) < value2) { + return false; + } + } else { + if (skillGetValue(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 +void perkResetRanks() +{ + for (int index = 0; index < gPartyMemberDescriptionsLength; index++) { + PerkRankData* ranksData = &(gPartyMemberPerkRanks[index]); + for (int perk = 0; perk < PERK_COUNT; perk++) { + ranksData->ranks[perk] = 0; + } + } +} + +// 0x496A5C +int perkAdd(Object* critter, int perk) +{ + if (!perkIsValid(perk)) { + return -1; + } + + if (!perkCanAdd(critter, perk)) { + return -1; + } + + PerkRankData* ranksData = perkGetRankData(critter); + ranksData->ranks[perk] += 1; + + perkAddEffect(critter, perk); + + return 0; +} + +// perk_add_force +// 0x496A9C +int perkAddForce(Object* critter, int perk) +{ + if (!perkIsValid(perk)) { + return -1; + } + + PerkRankData* ranksData = perkGetRankData(critter); + int value = ranksData->ranks[perk]; + + int maxRank = gPerkDescriptions[perk].maxRank; + + if (maxRank != -1 && value >= maxRank) { + return -1; + } + + ranksData->ranks[perk] += 1; + + perkAddEffect(critter, perk); + + return 0; +} + +// perk_sub +// 0x496AFC +int perkRemove(Object* critter, int perk) +{ + if (!perkIsValid(perk)) { + return -1; + } + + PerkRankData* ranksData = perkGetRankData(critter); + int value = ranksData->ranks[perk]; + + if (value < 1) { + return -1; + } + + ranksData->ranks[perk] -= 1; + + perkRemoveEffect(critter, perk); + + return 0; +} + +// Returns perks available to pick. +// +// 0x496B44 +int perkGetAvailablePerks(Object* critter, int* perks) +{ + int count = 0; + for (int perk = 0; perk < PERK_COUNT; perk++) { + if (perkCanAdd(critter, perk)) { + perks[count] = perk; + count++; + } + } + return count; +} + +// has_perk +// 0x496B78 +int perkGetRank(Object* critter, int perk) +{ + if (!perkIsValid(perk)) { + return 0; + } + + PerkRankData* ranksData = perkGetRankData(critter); + return ranksData->ranks[perk]; +} + +// 0x496B90 +char* perkGetName(int perk) +{ + if (!perkIsValid(perk)) { + return NULL; + } + return gPerkDescriptions[perk].name; +} + +// 0x496BB4 +char* perkGetDescription(int perk) +{ + if (!perkIsValid(perk)) { + return NULL; + } + return gPerkDescriptions[perk].description; +} + +// 0x496BD8 +int perkGetFrmId(int perk) +{ + if (!perkIsValid(perk)) { + return 0; + } + return gPerkDescriptions[perk].frmId; +} + +// perk_add_effect +// 0x496BFC +void perkAddEffect(Object* critter, int perk) +{ + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + debugPrint("\nERROR: perk_add_effect: Was called on non-critter!"); + return; + } + + if (!perkIsValid(perk)) { + return; + } + + PerkDescription* perkDescription = &(gPerkDescriptions[perk]); + + if (perkDescription->stat != -1) { + int value = critterGetBonusStat(critter, perkDescription->stat); + critterSetBonusStat(critter, perkDescription->stat, value + perkDescription->statModifier); + } + + if (perk == PERK_HERE_AND_NOW) { + PerkRankData* ranksData = perkGetRankData(critter); + ranksData->ranks[PERK_HERE_AND_NOW] -= 1; + + int level = pcGetStat(PC_STAT_LEVEL); + + gHereAndNowBonusExperience = pcGetExperienceForLevel(level + 1) - pcGetStat(PC_STAT_EXPERIENCE); + pcAddExperienceWithOptions(gHereAndNowBonusExperience, false); + + ranksData->ranks[PERK_HERE_AND_NOW] += 1; + } + + if (perkDescription->maxRank == -1) { + for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { + int value = critterGetBonusStat(critter, stat); + critterSetBonusStat(critter, stat, value + perkDescription->stats[stat]); + } + } +} + +// perk_remove_effect +// 0x496CE0 +void perkRemoveEffect(Object* critter, int perk) +{ + if ((critter->pid >> 24) != OBJ_TYPE_CRITTER) { + debugPrint("\nERROR: perk_remove_effect: Was called on non-critter!"); + return; + } + + if (!perkIsValid(perk)) { + return; + } + + PerkDescription* perkDescription = &(gPerkDescriptions[perk]); + + if (perkDescription->stat != -1) { + int value = critterGetBonusStat(critter, perkDescription->stat); + critterSetBonusStat(critter, perkDescription->stat, value - perkDescription->statModifier); + } + + if (perk == PERK_HERE_AND_NOW) { + int xp = pcGetStat(PC_STAT_EXPERIENCE); + pcSetStat(PC_STAT_EXPERIENCE, xp - gHereAndNowBonusExperience); + } + + if (perkDescription->maxRank == -1) { + for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { + int value = critterGetBonusStat(critter, stat); + critterSetBonusStat(critter, stat, value - perkDescription->stats[stat]); + } + } +} + +// Returns modifier to specified skill accounting for perks. +// +// 0x496DD0 +int perkGetSkillModifier(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 = objectGetLightIntensity(gDude); + 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; +} diff --git a/src/perk.h b/src/perk.h new file mode 100644 index 0000000..044f045 --- /dev/null +++ b/src/perk.h @@ -0,0 +1,79 @@ +#ifndef PERK_H +#define PERK_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "perk_defs.h" +#include "stat_defs.h" + +#include + +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; + +typedef struct PerkRankData { + int ranks[PERK_COUNT]; +} PerkRankData; + +extern PerkDescription gPerkDescriptions[PERK_COUNT]; +extern PerkRankData* gPartyMemberPerkRanks; +extern int gHereAndNowBonusExperience; + +extern MessageList gPerksMessageList; + +int perksInit(); +void perksReset(); +void perksExit(); +int perksLoad(File* stream); +int perksSave(File* stream); +PerkRankData* perkGetRankData(Object* critter); +bool perkCanAdd(Object* critter, int perk); +void perkResetRanks(); +int perkAdd(Object* critter, int perk); +int perkAddForce(Object* critter, int perk); +int perkRemove(Object* critter, int perk); +int perkGetAvailablePerks(Object* critter, int* perks); +int perkGetRank(Object* critter, int perk); +char* perkGetName(int perk); +char* perkGetDescription(int perk); +int perkGetFrmId(int perk); +void perkAddEffect(Object* critter, int perk); +void perkRemoveEffect(Object* critter, int perk); +int perkGetSkillModifier(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 [perkGetRank], 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 [perkGetRank] it could have been +// collapsed by compiler. +static inline bool perkHasRank(Object* critter, int perk) +{ + return perkGetRank(critter, perk) != 0; +} + +#endif /* PERK_H */ diff --git a/src/perk_defs.h b/src/perk_defs.h new file mode 100644 index 0000000..bb97ebd --- /dev/null +++ b/src/perk_defs.h @@ -0,0 +1,127 @@ +#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 */ diff --git a/src/pipboy.c b/src/pipboy.c new file mode 100644 index 0000000..2ef6349 --- /dev/null +++ b/src/pipboy.c @@ -0,0 +1,2370 @@ +#include "pipboy.h" + +#include "automap.h" +#include "color.h" +#include "combat.h" +#include "config.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "dbox.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "stat.h" +#include "text_font.h" +#include "window_manager.h" +#include "word_wrap.h" +#include "world_map.h" + +#include +#include +#include + +// 0x496FC0 +const Rect gPipboyWindowContentRect = { + 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 +const int gPipboyFrmIds[PIPBOY_FRM_COUNT] = { + 8, + 9, + 82, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 226, +}; + +// 0x51C128 +QuestDescription* gQuestDescriptions = NULL; + +// 0x51C12C +int gQuestsCount = 0; + +// 0x51C130 +HolodiskDescription* gHolodiskDescriptions = NULL; + +// 0x51C134 +int gHolodisksCount = 0; + +// Number of rest options available. +// +// 0x51C138 +int gPipboyRestOptionsCount = PIPBOY_REST_DURATION_COUNT; + +// 0x51C13C +bool gPipboyWindowIsoWasEnabled = false; + +// 0x51C140 +const HolidayDescription gHolidayDescriptions[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] = { + pipboyWindowHandleStatus, + pipboyWindowHandleAutomaps, + pipboyHandleVideoArchive, + pipboyHandleAlarmClock, + pipboyHandleAlarmClock, +}; + +// 0x6642E0 +Size gPipboyFrmSizes[PIPBOY_FRM_COUNT]; + +// 0x664338 +MessageListItem gPipboyMessageListItem; + +// pipboy.msg +// +// 0x664348 +MessageList gPipboyMessageList; + +// 0x664350 +STRUCT_664350 _sortlist[24]; + +// quests.msg +// +// 0x664410 +MessageList gQuestsMessageList; + +// 0x664418 +int gPipboyQuestLocationsCount; + +// 0x66441C +unsigned char* gPipboyWindowBuffer; + +// 0x664420 +unsigned char* gPipboyFrmData[PIPBOY_FRM_COUNT]; + +// 0x66444C +int gPipboyWindowHolodisksCount; + +// 0x664450 +int gPipboyMouseY; + +// 0x664454 +int gPipboyMouseX; + +// 0x664458 +unsigned int gPipboyLastEventTimestamp; + +// Index of the last page when rendering holodisk content. +// +// 0x66445C +int gPipboyHolodiskLastPage; + +// 0x664460 +int _HotLines[22]; + +// 0x6644B8 +int _button; + +// 0x6644BC +int gPipboyPreviousMouseX; + +// 0x6644C0 +int gPipboyPreviousMouseY; + +// 0x6644C4 +int gPipboyWindow; + +// 0x6644C8 +CacheEntry* gPipboyFrmHandles[PIPBOY_FRM_COUNT]; + +int _holodisk; + +// 0x6644F8 +int gPipboyWindowButtonCount; + +// 0x6644FC +int gPipboyWindowOldFont; + +// 0x664500 +bool _proc_bail_flag; + +// 0x664504 +int _amlst_mode; + +// 0x664508 +int gPipboyTab; + +// 0x66450C +int _actcnt; + +// 0x664510 +int gPipboyWindowButtonStart; + +// 0x664514 +int gPipboyCurrentLine; + +// 0x664518 +int _rest_time; + +// 0x66451C +int _amcty_indx; + +// 0x664520 +int _view_page; + +// 0x664524 +int gPipboyLinesCount; + +// 0x664528 +unsigned char _hot_back_line; + +// 0x664529 +unsigned char _holo_flag; + +// 0x66452A +unsigned char _stat_flag; + +// 0x497004 +int pipboyOpen(bool forceRest) +{ + if (!_wmMapPipboyActive()) { + // You aren't wearing the pipboy! + const char* text = getmsg(&gMiscMessageList, &gPipboyMessageListItem, 7000); + showDialogBox(text, NULL, 0, 192, 135, _colorTable[32328], NULL, _colorTable[32328], 1); + return 0; + } + + if (pipboyWindowInit(forceRest) == -1) { + return -1; + } + + mouseGetPosition(&gPipboyPreviousMouseX, &gPipboyPreviousMouseY); + gPipboyLastEventTimestamp = _get_time(); + + while (true) { + int keyCode = _get_input(); + + if (forceRest) { + keyCode = 504; + forceRest = false; + } + + mouseGetPosition(&gPipboyMouseX, &gPipboyMouseY); + + if (keyCode != -1 || gPipboyMouseX != gPipboyPreviousMouseX || gPipboyMouseY != gPipboyPreviousMouseY) { + gPipboyLastEventTimestamp = _get_time(); + gPipboyPreviousMouseX = gPipboyMouseX; + gPipboyPreviousMouseY = gPipboyMouseY; + } else { + if (_get_time() - gPipboyLastEventTimestamp > PIPBOY_IDLE_TIMEOUT) { + pipboyRenderScreensaver(); + + gPipboyLastEventTimestamp = _get_time(); + mouseGetPosition(&gPipboyPreviousMouseX, &gPipboyPreviousMouseY); + } + } + + if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { + showQuitConfirmationDialog(); + 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) { + takeScreenshot(); + } else if (keyCode >= 500 && keyCode <= 504) { + gPipboyTab = keyCode - 500; + _PipFnctn[gPipboyTab](1024); + } else if (keyCode >= 505 && keyCode <= 527) { + _PipFnctn[gPipboyTab](keyCode - 506); + } else if (keyCode == 528) { + _PipFnctn[gPipboyTab](1025); + } else if (keyCode == KEY_PAGE_DOWN) { + _PipFnctn[gPipboyTab](1026); + } else if (keyCode == KEY_PAGE_UP) { + _PipFnctn[gPipboyTab](1027); + } + + if (_proc_bail_flag) { + break; + } + } + + pipboyWindowFree(); + + return 0; +} + +// 0x497228 +int pipboyWindowInit(bool forceRest) +{ + gPipboyWindowIsoWasEnabled = isoDisable(); + + colorCycleDisable(); + gameMouseObjectsHide(); + indicatorBarHide(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + gPipboyRestOptionsCount = PIPBOY_REST_DURATION_COUNT_WITHOUT_PARTY; + + if (_getPartyMemberCount() > 1 && partyIsAnyoneCanBeHealedByRest()) { + gPipboyRestOptionsCount = PIPBOY_REST_DURATION_COUNT; + } + + gPipboyWindowOldFont = fontGetCurrent(); + fontSetCurrent(101); + + _proc_bail_flag = 0; + _rest_time = 0; + gPipboyCurrentLine = 0; + gPipboyWindowButtonCount = 0; + gPipboyLinesCount = PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT / fontGetLineHeight() - 1; + gPipboyWindowButtonStart = 0; + _hot_back_line = 0; + + if (holodiskInit() == -1) { + return -1; + } + + if (!messageListInit(&gPipboyMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "pipboy.msg"); + + if (!(messageListLoad(&gPipboyMessageList, path))) { + return -1; + } + + int index; + for (index = 0; index < PIPBOY_FRM_COUNT; index++) { + int fid = buildFid(6, gPipboyFrmIds[index], 0, 0, 0); + gPipboyFrmData[index] = artLockFrameDataReturningSize(fid, &(gPipboyFrmHandles[index]), &(gPipboyFrmSizes[index].width), &(gPipboyFrmSizes[index].height)); + if (gPipboyFrmData[index] == NULL) { + break; + } + } + + if (index != PIPBOY_FRM_COUNT) { + debugPrint("\n** Error loading pipboy graphics! **\n"); + + while (--index >= 0) { + artUnlock(gPipboyFrmHandles[index]); + } + + return -1; + } + + gPipboyWindow = windowCreate(0, 0, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_HEIGHT, _colorTable[0], WINDOW_FLAG_0x10); + if (gPipboyWindow == -1) { + debugPrint("\n** Error opening pipboy window! **\n"); + for (int index = 0; index < PIPBOY_FRM_COUNT; index++) { + artUnlock(gPipboyFrmHandles[index]); + } + return -1; + } + + gPipboyWindowBuffer = windowGetBuffer(gPipboyWindow); + memcpy(gPipboyWindowBuffer, gPipboyFrmData[PIPBOY_FRM_BACKGROUND], PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_HEIGHT); + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + + int alarmButton = buttonCreate(gPipboyWindow, + 124, + 13, + gPipboyFrmSizes[PIPBOY_FRM_ALARM_UP].width, + gPipboyFrmSizes[PIPBOY_FRM_ALARM_UP].height, + -1, + -1, + -1, + 504, + gPipboyFrmData[PIPBOY_FRM_ALARM_UP], + gPipboyFrmData[PIPBOY_FRM_ALARM_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (alarmButton != -1) { + buttonSetCallbacks(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 = buttonCreate(gPipboyWindow, + 53, + y, + gPipboyFrmSizes[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].width, + gPipboyFrmSizes[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].height, + -1, + -1, + -1, + eventCode, + gPipboyFrmData[PIPBOY_FRM_LITTLE_RED_BUTTON_UP], + gPipboyFrmData[PIPBOY_FRM_LITTLE_RED_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + eventCode += 1; + } + + y += 27; + } + + if (forceRest) { + if (!_critter_can_obj_dude_rest()) { + blitBufferToBufferTrans( + gPipboyFrmData[PIPBOY_FRM_LOGO], + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width, + gPipboyFrmSizes[PIPBOY_FRM_LOGO].height, + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 156 + 323, + PIPBOY_WINDOW_WIDTH); + + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + int holiday = 0; + for (; holiday < HOLIDAY_COUNT; holiday += 1) { + const HolidayDescription* holidayDescription = &(gHolidayDescriptions[holiday]); + if (holidayDescription->month == month && holidayDescription->day == day) { + break; + } + } + + if (holiday != HOLIDAY_COUNT) { + const HolidayDescription* holidayDescription = &(gHolidayDescriptions[holiday]); + const char* holidayName = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holidayDescription->textId); + char holidayNameCopy[256]; + strcpy(holidayNameCopy, holidayName); + + int len = fontGetStringWidth(holidayNameCopy); + fontDrawText(gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * (gPipboyFrmSizes[PIPBOY_FRM_LOGO].height + 174) + 6 + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width / 2 + 323 - len / 2, + holidayNameCopy, + 350, + PIPBOY_WINDOW_WIDTH, + _colorTable[992]); + } + + windowRefresh(gPipboyWindow); + + soundPlayFile("iisxxxx1"); + + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 215); + showDialogBox(text, NULL, 0, 192, 135, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + } + } else { + blitBufferToBufferTrans( + gPipboyFrmData[PIPBOY_FRM_LOGO], + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width, + gPipboyFrmSizes[PIPBOY_FRM_LOGO].height, + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 156 + 323, + PIPBOY_WINDOW_WIDTH); + + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + int holiday; + for (holiday = 0; holiday < HOLIDAY_COUNT; holiday += 1) { + const HolidayDescription* holidayDescription = &(gHolidayDescriptions[holiday]); + if (holidayDescription->month == month && holidayDescription->day == day) { + break; + } + } + + if (holiday != HOLIDAY_COUNT) { + const HolidayDescription* holidayDescription = &(gHolidayDescriptions[holiday]); + const char* holidayName = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holidayDescription->textId); + char holidayNameCopy[256]; + strcpy(holidayNameCopy, holidayName); + + int length = fontGetStringWidth(holidayNameCopy); + fontDrawText(gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * (gPipboyFrmSizes[PIPBOY_FRM_LOGO].height + 174) + 6 + gPipboyFrmSizes[PIPBOY_FRM_LOGO].width / 2 + 323 - length / 2, + holidayNameCopy, + 350, + PIPBOY_WINDOW_WIDTH, + _colorTable[992]); + } + + windowRefresh(gPipboyWindow); + } + + if (questInit() == -1) { + return -1; + } + + soundPlayFile("pipon"); + windowRefresh(gPipboyWindow); + + return 0; +} + +// 0x497828 +void pipboyWindowFree() +{ + bool showScriptMessages = false; + configGetBool(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); + + if (showScriptMessages) { + debugPrint("\nScript "); + } + + scriptsExecMapUpdateProc(); + + windowDestroy(gPipboyWindow); + + messageListFree(&gPipboyMessageList); + + // NOTE: Uninline. + holodiskFree(); + + for (int index = 0; index < PIPBOY_FRM_COUNT; index++) { + artUnlock(gPipboyFrmHandles[index]); + } + + pipboyWindowDestroyButtons(); + + fontSetCurrent(gPipboyWindowOldFont); + + if (gPipboyWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + indicatorBarShow(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + interfaceBarRefresh(); + + // NOTE: Uninline. + questFree(); +} + +// NOTE: Collapsed. +// +// 0x497918 +void _pip_init_() +{ +} + +// NOTE: Uncollapsed 0x497918. +// +// pip_init +void pipboyInit() +{ + _pip_init_(); +} + +// NOTE: Uncollapsed 0x497918. +void pipboyReset() +{ + _pip_init_(); +} + +// 0x49791C +void pipboyDrawNumber(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++) { + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_NUMBERS] + 9 * (value % 10), 9, 17, 360, gPipboyWindowBuffer + offset, PIPBOY_WINDOW_WIDTH); + offset -= 9; + value /= 10; + } +} + +// 0x4979B4 +void pipboyDrawDate() +{ + int day; + int month; + int year; + + gameTimeGetDate(&month, &day, &year); + pipboyDrawNumber(day, 2, PIPBOY_WINDOW_DAY_X, PIPBOY_WINDOW_DAY_Y); + + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_MONTHS] + 435 * (month - 1), 29, 14, 29, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_MONTH_Y + PIPBOY_WINDOW_MONTH_X, PIPBOY_WINDOW_WIDTH); + + pipboyDrawNumber(year, 4, PIPBOY_WINDOW_YEAR_X, PIPBOY_WINDOW_YEAR_Y); +} + +// 0x497A40 +void pipboyDrawText(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 = fontGetStringWidth(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; + } + + fontDrawText(gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * (gPipboyCurrentLine * fontGetLineHeight() + 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 = gPipboyCurrentLine * fontGetLineHeight() + 49; + bufferDrawLine(gPipboyWindowBuffer, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_X + left, top, PIPBOY_WINDOW_CONTENT_VIEW_X + left + length, top, color); + } + + if (gPipboyCurrentLine < gPipboyLinesCount) { + gPipboyCurrentLine += 1; + } +} + +// 0x497B64 +void pipboyDrawBackButton(int color) +{ + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); + + // BACK + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(text, PIPBOY_TEXT_ALIGNMENT_CENTER, color); +} + +// NOTE: Collapsed. +// +// 0x497BD4 +int _save_pipboy(File* stream) +{ + return 0; +} + +// NOTE: Uncollapsed 0x497BD4. +int pipboySave(File* stream) +{ + return _save_pipboy(stream); +} + +// NOTE: Uncollapsed 0x497BD4. +int pipboyLoad(File* stream) +{ + return _save_pipboy(stream); +} + +// 0x497BD8 +void pipboyWindowHandleStatus(int a1) +{ + if (a1 == 1024) { + pipboyWindowDestroyButtons(); + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + _holo_flag = 0; + _holodisk = -1; + gPipboyWindowHolodisksCount = 0; + _view_page = 0; + _stat_flag = 0; + + for (int index = 0; index < gHolodisksCount; index += 1) { + HolodiskDescription* holodiskDescription = &(gHolodiskDescriptions[index]); + if (gGameGlobalVars[holodiskDescription->gvar] != 0) { + gPipboyWindowHolodisksCount += 1; + break; + } + } + + pipboyWindowRenderQuestLocationList(-1); + + if (gPipboyQuestLocationsCount == 0) { + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 203); + pipboyDrawText(text, 0, _colorTable[992]); + } + + gPipboyWindowHolodisksCount = pipboyWindowRenderHolodiskList(-1); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + pipboyWindowCreateButtons(2, gPipboyQuestLocationsCount + gPipboyWindowHolodisksCount + 1, false); + windowRefresh(gPipboyWindow); + return; + } + + if (_stat_flag == 0 && _holo_flag == 0) { + if (gPipboyQuestLocationsCount != 0 && gPipboyMouseX < 429) { + soundPlayFile("ib1p1xx1"); + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + pipboyWindowRenderQuestLocationList(a1); + pipboyWindowRenderHolodiskList(-1); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + _stat_flag = 1; + } else { + if (gPipboyWindowHolodisksCount != 0 && gPipboyWindowHolodisksCount >= a1 && gPipboyMouseX > 429) { + soundPlayFile("ib1p1xx1"); + _holodisk = 0; + + int index = 0; + for (; index < gHolodisksCount; index += 1) { + HolodiskDescription* holodiskDescription = &(gHolodiskDescriptions[index]); + if (gGameGlobalVars[holodiskDescription->gvar] > 0) { + if (a1 - 1 == _holodisk) { + break; + } + _holodisk += 1; + } + } + _holodisk = index; + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + pipboyWindowRenderHolodiskList(_holodisk); + pipboyWindowRenderQuestLocationList(-1); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + pipboyWindowDestroyButtons(); + pipboyRenderHolodiskText(); + pipboyWindowCreateButtons(0, 0, true); + _holo_flag = 1; + } + } + } + + if (_stat_flag == 0) { + if (_holo_flag == 0 || a1 < 1025 || a1 > 1027) { + return; + } + + if (gPipboyMouseX > 459 && a1 != 1027 || a1 == 1026) { + if (gPipboyHolodiskLastPage <= _view_page) { + if (a1 != 1026) { + soundPlayFile("ib1p1xx1"); + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // Back + const char* text1 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // Done + const char* text2 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 214); + pipboyDrawText(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, _colorTable[992]); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + pipboyWindowHandleStatus(1024); + } + } else { + soundPlayFile("ib1p1xx1"); + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // Back + const char* text1 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // More + const char* text2 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 200); + pipboyDrawText(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, _colorTable[992]); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + + _view_page += 1; + + pipboyRenderHolodiskText(); + } + return; + } + + if (a1 == 1027) { + soundPlayFile("ib1p1xx1"); + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // Back + const char* text1 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // More + const char* text2 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 200); + pipboyDrawText(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, _colorTable[992]); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + + _view_page -= 1; + + if (_view_page < 0) { + pipboyWindowHandleStatus(1024); + return; + } + } else { + if (gPipboyMouseX > 395) { + return; + } + + soundPlayFile("ib1p1xx1"); + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // Back + const char* text1 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + // More + const char* text2 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 200); + pipboyDrawText(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, _colorTable[992]); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + + if (_view_page <= 0) { + pipboyWindowHandleStatus(1024); + return; + } + + _view_page -= 1; + } + + pipboyRenderHolodiskText(); + return; + } + + if (a1 == 1025) { + soundPlayFile("ib1p1xx1"); + pipboyDrawBackButton(_colorTable[32747]); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + coreDelayProcessingEvents(200); + pipboyWindowHandleStatus(1024); + } + + if (a1 <= gPipboyQuestLocationsCount) { + soundPlayFile("ib1p1xx1"); + + int v13 = 0; + int index = 0; + for (; index < gQuestsCount; index++) { + QuestDescription* questDescription = &(gQuestDescriptions[index]); + if (questDescription->displayThreshold <= gGameGlobalVars[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 + // `pipboyWindowRenderQuestLocationList`. See buffer overread + // bug involved. + for (; index < gQuestsCount; index++) { + if (gQuestDescriptions[index].location != gQuestDescriptions[index + 1].location) { + break; + } + } + } + } + + pipboyWindowDestroyButtons(); + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + if (gPipboyLinesCount >= 1) { + gPipboyCurrentLine = 1; + } + + pipboyWindowCreateButtons(0, 0, true); + + QuestDescription* questDescription = &(gQuestDescriptions[index]); + + const char* text1 = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 210); + const char* text2 = getmsg(&gMapMessageList, &gPipboyMessageListItem, questDescription->location); + char formattedText[1024]; + sprintf(formattedText, "%s %s", text2, text1); + pipboyDrawText(formattedText, PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + if (gPipboyLinesCount >= 3) { + gPipboyCurrentLine = 3; + } + + int number = 1; + for (; index < gQuestsCount; index++) { + QuestDescription* questDescription = &(gQuestDescriptions[index]); + if (gGameGlobalVars[questDescription->gvar] >= questDescription->displayThreshold) { + const char* text = getmsg(&gQuestsMessageList, &gPipboyMessageListItem, questDescription->description); + char formattedText[1024]; + sprintf(formattedText, "%d. %s", number, text); + number += 1; + + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(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 (gGameGlobalVars[questDescription->gvar] < questDescription->completedThreshold) { + flags = 0; + color = _colorTable[992]; + } else { + flags = PIPBOY_TEXT_STYLE_STRIKE_THROUGH; + color = _colorTable[8804]; + } + + pipboyDrawText(beginning, flags, color); + + *ending = c; + gPipboyCurrentLine += 1; + } + } else { + debugPrint("\n ** Word wrap error in pipboy! **\n"); + } + } + + if (index != gQuestsCount - 1) { + QuestDescription* nextQuestDescription = &(gQuestDescriptions[index + 1]); + if (questDescription->location != nextQuestDescription->location) { + break; + } + } + } + + pipboyDrawBackButton(_colorTable[992]); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + _stat_flag = 1; + } +} + +// [a1] is likely selected location, or -1 if nothing is selected +// +// 0x498734 +void pipboyWindowRenderQuestLocationList(int a1) +{ + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + int flags = gPipboyWindowHolodisksCount != 0 ? PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER : PIPBOY_TEXT_ALIGNMENT_CENTER; + flags |= PIPBOY_TEXT_STYLE_UNDERLINE; + + // STATUS + const char* statusText = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 202); + pipboyDrawText(statusText, flags, _colorTable[992]); + + if (gPipboyLinesCount >= 2) { + gPipboyCurrentLine = 2; + } + + gPipboyQuestLocationsCount = 0; + + for (int index = 0; index < gQuestsCount; index += 1) { + QuestDescription* quest = &(gQuestDescriptions[index]); + if (quest->displayThreshold > gGameGlobalVars[quest->gvar]) { + continue; + } + + int color = (gPipboyCurrentLine - 1) / 2 == (a1 - 1) ? _colorTable[32747] : _colorTable[992]; + + // Render location. + const char* questLocation = getmsg(&gMapMessageList, &gPipboyMessageListItem, quest->location); + pipboyDrawText(questLocation, 0, color); + + gPipboyCurrentLine += 1; + gPipboyQuestLocationsCount += 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 < gQuestsCount; index++) { + if (gQuestDescriptions[index].location != gQuestDescriptions[index + 1].location) { + break; + } + } + } +} + +// 0x4988A0 +void pipboyRenderHolodiskText() +{ + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + HolodiskDescription* holodisk = &(gHolodiskDescriptions[_holodisk]); + + int holodiskTextId; + int linesCount = 0; + + gPipboyHolodiskLastPage = 0; + + for (holodiskTextId = holodisk->description; holodiskTextId < holodisk->description + 500; holodiskTextId += 1) { + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holodiskTextId); + if (strcmp(text, "**END-DISK**") == 0) { + break; + } + + linesCount += 1; + if (linesCount >= PIPBOY_HOLODISK_LINES_MAX) { + linesCount = 0; + gPipboyHolodiskLastPage += 1; + } + } + + if (holodiskTextId >= holodisk->description + 500) { + debugPrint("\nPIPBOY: #1 Holodisk text end not found!\n"); + } + + holodiskTextId = holodisk->description; + + if (_view_page != 0) { + int page = 0; + int numberOfLines = 0; + for (; holodiskTextId < holodiskTextId + 500; holodiskTextId += 1) { + const char* line = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holodiskTextId); + if (strcmp(line, "**END-DISK**") == 0) { + debugPrint("\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->description + 500) { + debugPrint("\nPIPBOY: #2 Holodisk text end not found!\n"); + } + } else { + const char* name = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holodisk->name); + pipboyDrawText(name, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + } + + if (gPipboyHolodiskLastPage != 0) { + // of + const char* of = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 212); + char formattedText[60]; // TODO: Size is probably wrong. + sprintf(formattedText, "%d %s %d", _view_page + 1, of, gPipboyHolodiskLastPage + 1); + + int len = fontGetStringWidth(of); + fontDrawText(gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * 47 + 616 + 604 - len, formattedText, 350, PIPBOY_WINDOW_WIDTH, _colorTable[992]); + } + + if (gPipboyLinesCount >= 3) { + gPipboyCurrentLine = 3; + } + + for (int line = 0; line < PIPBOY_HOLODISK_LINES_MAX; line += 1) { + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holodiskTextId); + if (strcmp(text, "**END-DISK**") == 0) { + break; + } + + if (strcmp(text, "**END-PAR**") == 0) { + gPipboyCurrentLine += 1; + } else { + pipboyDrawText(text, PIPBOY_TEXT_NO_INDENT, _colorTable[992]); + } + + holodiskTextId += 1; + } + + int moreOrDoneTextId; + if (gPipboyHolodiskLastPage <= _view_page) { + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + const char* back = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + moreOrDoneTextId = 214; + } else { + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + const char* back = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 201); + pipboyDrawText(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = gPipboyLinesCount; + } + + moreOrDoneTextId = 200; + } + + const char* moreOrDoneText = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, moreOrDoneTextId); + pipboyDrawText(moreOrDoneText, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, _colorTable[992]); + windowRefresh(gPipboyWindow); +} + +// 0x498C40 +int pipboyWindowRenderHolodiskList(int a1) +{ + if (gPipboyLinesCount >= 2) { + gPipboyCurrentLine = 2; + } + + int knownHolodisksCount = 0; + for (int index = 0; index < gHolodisksCount; index++) { + HolodiskDescription* holodisk = &(gHolodiskDescriptions[index]); + if (gGameGlobalVars[holodisk->gvar] != 0) { + int color; + if ((gPipboyCurrentLine - 2) / 2 == a1) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, holodisk->name); + pipboyDrawText(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN, color); + + gPipboyCurrentLine++; + knownHolodisksCount++; + } + } + + if (knownHolodisksCount != 0) { + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 211); // DATA + pipboyDrawText(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + } + + return knownHolodisksCount; +} + +// 0x498D34 +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 +void pipboyWindowHandleAutomaps(int a1) +{ + if (a1 == 1024) { + pipboyWindowDestroyButtons(); + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + const char* title = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 205); + pipboyDrawText(title, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + _actcnt = _PrintAMList(-1); + + pipboyWindowCreateButtons(2, _actcnt, 0); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + _amlst_mode = 0; + return; + } + + if (_amlst_mode != 0) { + if (a1 == 1025 || a1 <= -1) { + pipboyWindowHandleAutomaps(1024); + soundPlayFile("ib1p1xx1"); + } + + if (a1 >= 1 && a1 <= _actcnt + 3) { + soundPlayFile("ib1p1xx1"); + _PrintAMelevList(a1); + automapRenderInPipboyWindow(gPipboyWindow, _sortlist[a1 - 1].field_6, _sortlist[a1 - 1].field_4); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + } + + return; + } + + if (a1 > 0 && a1 <= _actcnt) { + soundPlayFile("ib1p1xx1"); + pipboyWindowDestroyButtons(); + _PrintAMList(a1); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + _amcty_indx = _sortlist[a1 - 1].field_4; + _actcnt = _PrintAMelevList(1); + pipboyWindowCreateButtons(0, _actcnt + 2, 1); + automapRenderInPipboyWindow(gPipboyWindow, _sortlist[0].field_6, _sortlist[0].field_4); + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + _amlst_mode = 1; + } +} + +// 0x498F30 +int _PrintAMelevList(int a1) +{ + AutomapHeader* automapHeader; + if (automapGetHeader(&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 = mapGetName(_amcty_indx, elevation); + _sortlist[v4].field_4 = elevation; + _sortlist[v4].field_6 = _amcty_indx; + v4++; + } + } + + int mapCount = mapGetCount(); + 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 = mapGetName(map, elevation); + _sortlist[v4].field_4 = elevation; + _sortlist[v4].field_6 = map; + v4++; + } + } + } + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + const char* msg = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 205); + pipboyDrawText(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + if (gPipboyLinesCount >= 2) { + gPipboyCurrentLine = 2; + } + + const char* name = _map_get_description_idx_(_amcty_indx); + pipboyDrawText(name, PIPBOY_TEXT_ALIGNMENT_CENTER, _colorTable[992]); + + if (gPipboyLinesCount >= 4) { + gPipboyCurrentLine = 4; + } + + int selectedPipboyLine = (a1 - 1) * 2; + + for (int index = 0; index < v4; index++) { + int color; + if (gPipboyCurrentLine - 4 == selectedPipboyLine) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + pipboyDrawText(_sortlist[index].name, 0, color); + gPipboyCurrentLine++; + } + + pipboyDrawBackButton(_colorTable[992]); + + return v4; +} + +// 0x499150 +int _PrintAMList(int a1) +{ + AutomapHeader* automapHeader; + if (automapGetHeader(&automapHeader) == -1) { + return -1; + } + + int count = 0; + int index = 0; + + int mapCount = mapGetCount(); + 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 = mapGetCityName(map); + _sortlist[count].field_4 = map; + count++; + } + } + } + + if (count != 0) { + if (count > 1) { + qsort(_sortlist, count, sizeof(*_sortlist), _qscmp); + } + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + const char* msg = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 205); + pipboyDrawText(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + if (gPipboyLinesCount >= 2) { + gPipboyCurrentLine = 2; + } + + for (int index = 0; index < count; index++) { + int color; + if (gPipboyCurrentLine - 1 == a1) { + color = _colorTable[32747]; + } else { + color = _colorTable[992]; + } + + pipboyDrawText(_sortlist[index].name, 0, color); + gPipboyCurrentLine++; + } + } + + return count; +} + +// 0x49932C +void pipboyHandleVideoArchive(int a1) +{ + if (a1 == 1024) { + pipboyWindowDestroyButtons(); + _view_page = pipboyRenderVideoArchive(-1); + pipboyWindowCreateButtons(2, _view_page, false); + } else if (a1 >= 0 && a1 <= _view_page) { + soundPlayFile("ib1p1xx1"); + + pipboyRenderVideoArchive(a1); + + int movie; + for (movie = 2; movie < 16; movie++) { + if (gameMovieIsSeen(movie)) { + a1--; + if (a1 <= 0) { + break; + } + } + } + + if (movie <= MOVIE_COUNT) { + gameMoviePlay(movie, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC); + } else { + debugPrint("\n ** Selected movie not found in list! **\n"); + } + + fontSetCurrent(101); + + gPipboyLastEventTimestamp = _get_time(); + pipboyRenderVideoArchive(-1); + } +} + +// 0x4993DC +int pipboyRenderVideoArchive(int a1) +{ + const char* text; + int i; + int v12; + int msg_num; + int v5; + int v8; + int v9; + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + // VIDEO ARCHIVES + text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 206); + pipboyDrawText(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + if (gPipboyLinesCount >= 2) { + gPipboyCurrentLine = 2; + } + + v5 = 0; + v12 = a1 - 1; + + // 502 - Elder Speech + // ... + // 516 - Credits + msg_num = 502; + + for (i = 2; i < 16; i++) { + if (gameMovieIsSeen(i)) { + v8 = v5++; + if (v8 == v12) { + v9 = _colorTable[32747]; + } else { + v9 = _colorTable[992]; + } + + text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, msg_num); + pipboyDrawText(text, 0, v9); + + gPipboyCurrentLine++; + } + + msg_num++; + } + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + + return v5; +} + +// 0x499518 +void pipboyHandleAlarmClock(int a1) +{ + if (a1 == 1024) { + if (_critter_can_obj_dude_rest()) { + pipboyWindowDestroyButtons(); + pipboyWindowRenderRestOptions(0); + pipboyWindowCreateButtons(5, gPipboyRestOptionsCount, false); + } else { + soundPlayFile("iisxxxx1"); + + // You cannot rest at this location! + const char* text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 215); + showDialogBox(text, NULL, 0, 192, 135, _colorTable[32328], 0, _colorTable[32328], DIALOG_BOX_LARGE); + } + } else if (a1 >= 4 && a1 <= 17) { + soundPlayFile("ib1p1xx1"); + + pipboyWindowRenderRestOptions(a1 - 3); + + int duration = a1 - 4; + int minutes = 0; + int hours = 0; + int v10 = 0; + + switch (duration) { + case PIPBOY_REST_DURATION_TEN_MINUTES: + pipboyRest(0, 10, 0); + break; + case PIPBOY_REST_DURATION_THIRTY_MINUTES: + pipboyRest(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: + pipboyRest(duration - 1, 0, 0); + break; + case PIPBOY_REST_DURATION_UNTIL_MORNING: + _ClacTime(&hours, &minutes, 8); + pipboyRest(hours, minutes, 0); + break; + case PIPBOY_REST_DURATION_UNTIL_NOON: + _ClacTime(&hours, &minutes, 12); + pipboyRest(hours, minutes, 0); + break; + case PIPBOY_REST_DURATION_UNTIL_EVENING: + _ClacTime(&hours, &minutes, 18); + pipboyRest(hours, minutes, 0); + break; + case PIPBOY_REST_DURATION_UNTIL_MIDNIGHT: + _ClacTime(&hours, &minutes, 0); + if (pipboyRest(hours, minutes, 0) == 0) { + pipboyDrawNumber(0, 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + } + windowRefresh(gPipboyWindow); + break; + case PIPBOY_REST_DURATION_UNTIL_HEALED: + case PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED: + pipboyRest(0, 0, duration); + break; + } + + soundPlayFile("ib2lu1x1"); + + pipboyWindowRenderRestOptions(0); + } +} + +// 0x4996B4 +void pipboyWindowRenderRestOptions(int a1) +{ + const char* text; + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + if (gPipboyLinesCount >= 0) { + gPipboyCurrentLine = 0; + } + + // ALARM CLOCK + text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 300); + pipboyDrawText(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, _colorTable[992]); + + if (gPipboyLinesCount >= 5) { + gPipboyCurrentLine = 5; + } + + pipboyDrawHitPoints(); + + // 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 < gPipboyRestOptionsCount + 1; option++) { + // 302 - Rest for ten minutes + // ... + // 315 - Rest until party is healed + text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 302 + option - 1); + int color = option == a1 ? _colorTable[32747] : _colorTable[992]; + + pipboyDrawText(text, 0, color); + + gPipboyCurrentLine++; + } + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); +} + +// 0x4997B8 +void pipboyDrawHitPoints() +{ + int max_hp; + int cur_hp; + char* text; + char msg[64]; + int len; + + blitBufferToBuffer(gPipboyFrmData[PIPBOY_FRM_BACKGROUND] + 66 * PIPBOY_WINDOW_WIDTH + 254, + 350, + 10, + PIPBOY_WINDOW_WIDTH, + gPipboyWindowBuffer + 66 * PIPBOY_WINDOW_WIDTH + 254, + PIPBOY_WINDOW_WIDTH); + + max_hp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + cur_hp = critterGetHitPoints(gDude); + text = getmsg(&gPipboyMessageList, &gPipboyMessageListItem, 301); // Hit Points + sprintf(msg, "%s %d/%d", text, cur_hp, max_hp); + len = fontGetStringWidth(msg); + fontDrawText(gPipboyWindowBuffer + 66 * PIPBOY_WINDOW_WIDTH + 254 + (350 - len) / 2, msg, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_WIDTH, _colorTable[992]); +} + +// 0x4998C0 +void pipboyWindowCreateButtons(int start, int count, bool a3) +{ + fontSetCurrent(101); + + int height = fontGetLineHeight(); + + gPipboyWindowButtonStart = start; + gPipboyWindowButtonCount = 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 (gPipboyWindowButtonCount + gPipboyWindowButtonStart <= index) { + break; + } + + _HotLines[index] = buttonCreate(gPipboyWindow, 254, y, 350, height, -1, -1, -1, eventCode, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); + y += height * 2; + eventCode += 1; + } + } + + if (a3) { + _button = buttonCreate(gPipboyWindow, 254, height * gPipboyLinesCount + PIPBOY_WINDOW_CONTENT_VIEW_Y, 350, height, -1, -1, -1, 528, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); + } +} + +// 0x4999C0 +void pipboyWindowDestroyButtons() +{ + if (gPipboyWindowButtonCount != 0) { + // NOTE: There is a buffer overread bug. In original binary it leads to + // reading continuation (from 0x6644B8 onwards), which finally destroys + // button in `gPipboyWindow` (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. + int end = gPipboyWindowButtonStart + gPipboyWindowButtonCount; + if (end > 22) { + end = 22; + } + + for (int index = gPipboyWindowButtonStart; index < end; index++) { + buttonDestroy(_HotLines[index]); + } + } + + if (_hot_back_line) { + buttonDestroy(_button); + } + + gPipboyWindowButtonCount = 0; + _hot_back_line = 0; +} + +// 0x499A24 +bool pipboyRest(int hours, int minutes, int duration) +{ + gameMouseSetCursor(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 = gameTimeGetTime(); + + 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 = queueGetNextEventTime(); + if (v6 >= nextEventTime) { + gameTimeSetTime(nextEventTime + 1); + if (queueProcessEvents()) { + rc = true; + debugPrint("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; + } + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + windowRefresh(gPipboyWindow); + + while (getTicksSince(start) < 50) { + } + } + } + + if (!rc) { + gameTimeSetTime(gameTime + 600 * minutes); + + if (_Check4Health(minutes)) { + // NOTE: Uninline. + _AddHealth(); + } + } + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + pipboyDrawHitPoints(); + windowRefresh(gPipboyWindow); + } + + if (hours != 0 && !rc) { + int gameTime = gameTimeGetTime(); + 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 * 36000.0) + gameTime); + unsigned int nextEventTime = queueGetNextEventTime(); + if (!rc && v8 >= nextEventTime) { + gameTimeSetTime(nextEventTime + 1); + + if (queueProcessEvents()) { + rc = true; + debugPrint("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(); + } + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + pipboyDrawHitPoints(); + windowRefresh(gPipboyWindow); + + while (getTicksSince(start) < 50) { + } + } + } + + if (!rc) { + gameTimeSetTime(gameTime + 36000 * hours); + } + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + pipboyDrawHitPoints(); + windowRefresh(gPipboyWindow); + } + } else if (duration == PIPBOY_REST_DURATION_UNTIL_HEALED || duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { + int currentHp = critterGetHitPoints(gDude); + int maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + if (currentHp != maxHp + || (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED && partyIsAnyoneCanBeHealedByRest())) { + // First pass - healing dude is the top priority. + int hpToHeal = maxHp - currentHp; + int healingRate = critterGetStat(gDude, STAT_HEALING_RATE); + int hoursToHeal = (int)((double)hpToHeal / (double)healingRate * 3.0); + while (!rc && hoursToHeal != 0) { + if (hoursToHeal <= 24) { + rc = pipboyRest(hoursToHeal, 0, 0); + hoursToHeal = 0; + } else { + rc = pipboyRest(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 = critterGetHitPoints(gDude); + maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + hpToHeal = maxHp - currentHp; + + if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { + int partyHpToHeal = partyGetMaxWoundToHealByRest(); + if (partyHpToHeal > hpToHeal) { + hpToHeal = partyHpToHeal; + } + } + + while (!rc && hpToHeal != 0) { + currentHp = critterGetHitPoints(gDude); + maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + hpToHeal = maxHp - currentHp; + + if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { + int partyHpToHeal = partyGetMaxWoundToHealByRest(); + if (partyHpToHeal > hpToHeal) { + hpToHeal = partyHpToHeal; + } + } + + rc = pipboyRest(3, 0, 0); + } + } else { + // No one needs healing. + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + return rc; + } + } + + int gameTime = gameTimeGetTime(); + int nextEventGameTime = queueGetNextEventTime(); + if (gameTime > nextEventGameTime) { + if (queueProcessEvents()) { + debugPrint("PIPBOY: Returning from Queue trigger...\n"); + _proc_bail_flag = 1; + rc = true; + } + } + + pipboyDrawNumber(gameTimeGetHour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); + pipboyDrawDate(); + windowRefresh(gPipboyWindow); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + return rc; +} + +// 0x499FCC +bool _Check4Health(int a1) +{ + _rest_time += a1; + + if (_rest_time < 180) { + return false; + } + + debugPrint("\n health added!\n"); + _rest_time = 0; + + return true; +} + +// NOTE: Inlined. +bool _AddHealth() +{ + _partyMemberRestingHeal(3); + + int currentHp = critterGetHitPoints(gDude); + int maxHp = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + return currentHp == maxHp; +} + +// Returns [hours] and [minutes] needed to rest until [wakeUpHour]. +void _ClacTime(int* hours, int* minutes, int wakeUpHour) +{ + int gameTimeHour = gameTimeGetHour(); + + *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 +int pipboyRenderScreensaver() +{ + PipboyBomb bombs[PIPBOY_BOMB_COUNT]; + + mouseGetPosition(&gPipboyPreviousMouseX, &gPipboyPreviousMouseY); + + for (int index = 0; index < PIPBOY_BOMB_COUNT; index += 1) { + bombs[index].field_10 = 0; + } + + _gmouse_disable(0); + + unsigned char* buf = internal_malloc(412 * 374); + if (buf == NULL) { + return -1; + } + + blitBufferToBuffer(gPipboyWindowBuffer + 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); + + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + 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(); + + mouseGetPosition(&gPipboyMouseX, &gPipboyMouseY); + if (_get_input() != -1 || gPipboyPreviousMouseX != gPipboyMouseX || gPipboyPreviousMouseY != gPipboyMouseY) { + break; + } + + double random = randomBetween(0, RAND_MAX); + + // TODO: Figure out what this constant means. Probably somehow related + // to 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 - gPipboyFrmSizes[PIPBOY_FRM_BOMB].width / 4) + (406 - gPipboyFrmSizes[PIPBOY_FRM_BOMB].height / 4); + int v5 = (int)((double)randomBetween(0, RAND_MAX) / (double)RAND_MAX * (double)v27); + int v6 = gPipboyFrmSizes[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 + gPipboyFrmSizes[PIPBOY_FRM_BOMB].width / 4; + bomb->y = PIPBOY_WINDOW_CONTENT_VIEW_Y - gPipboyFrmSizes[PIPBOY_FRM_BOMB].height + 2; + } + + bomb->field_10 = 1; + bomb->field_8 = (float)((double)randomBetween(0, RAND_MAX) * (2.75 / RAND_MAX) + 0.15); + bomb->field_C = 0; + } + } + + if (v31 == 0) { + blitBufferToBuffer(gPipboyFrmData[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, + gPipboyWindowBuffer + 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 = gPipboyFrmSizes[PIPBOY_FRM_BOMB].width; + int srcHeight = gPipboyFrmSizes[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 + gPipboyFrmSizes[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 >= gPipboyFrmSizes[PIPBOY_FRM_BOMB].width) { + bomb->field_10 = 0; + } + destX = PIPBOY_WINDOW_CONTENT_VIEW_X; + srcWidth = gPipboyFrmSizes[PIPBOY_FRM_BOMB].width - srcX; + } + + if (destY >= PIPBOY_WINDOW_CONTENT_VIEW_Y) { + if (destY + gPipboyFrmSizes[PIPBOY_FRM_BOMB].height >= 452) { + srcHeight = 452 - destY; + if (srcHeight < 1) { + bomb->field_10 = 0; + } + } + } else { + if (destY + gPipboyFrmSizes[PIPBOY_FRM_BOMB].height < PIPBOY_WINDOW_CONTENT_VIEW_Y) { + bomb->field_10 = 0; + } + + srcY = PIPBOY_WINDOW_CONTENT_VIEW_Y - destY; + srcHeight = gPipboyFrmSizes[PIPBOY_FRM_BOMB].height - srcY; + destY = PIPBOY_WINDOW_CONTENT_VIEW_Y; + } + + if (bomb->field_10 == 1 && v31 == 0) { + blitBufferToBufferTrans( + gPipboyFrmData[PIPBOY_FRM_BOMB] + gPipboyFrmSizes[PIPBOY_FRM_BOMB].width * srcY + srcX, + srcWidth, + srcHeight, + gPipboyFrmSizes[PIPBOY_FRM_BOMB].width, + gPipboyWindowBuffer + 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 { + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + while (getTicksSince(time) < 50) { + } + } + } + + blitBufferToBuffer(buf, + PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, + PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, + PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, + gPipboyWindowBuffer + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, + PIPBOY_WINDOW_WIDTH); + + internal_free(buf); + + windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect); + _gmouse_enable(); + + return 0; +} + +// 0x49A5D4 +int questInit() +{ + if (gQuestDescriptions != NULL) { + internal_free(gQuestDescriptions); + gQuestDescriptions = NULL; + } + + gQuestsCount = 0; + + messageListFree(&gQuestsMessageList); + + if (!messageListInit(&gQuestsMessageList)) { + return -1; + } + + if (!messageListLoad(&gQuestsMessageList, "game\\quests.msg")) { + return -1; + } + + File* stream = fileOpen("data\\quests.txt", "rt"); + if (stream == NULL) { + return -1; + } + + char string[256]; + while (fileReadString(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 = internal_realloc(gQuestDescriptions, sizeof(*gQuestDescriptions) * (gQuestsCount + 1)); + if (entries == NULL) { + goto err; + } + + memcpy(&(entries[gQuestsCount]), &entry, sizeof(entry)); + + gQuestDescriptions = entries; + gQuestsCount++; + } + + qsort(gQuestDescriptions, gQuestsCount, sizeof(*gQuestDescriptions), questDescriptionCompare); + + fileClose(stream); + + return 0; + +err: + + fileClose(stream); + + return -1; +} + +// 0x49A7E4 +void questFree() +{ + if (gQuestDescriptions != NULL) { + internal_free(gQuestDescriptions); + gQuestDescriptions = NULL; + } + + gQuestsCount = 0; + + messageListFree(&gQuestsMessageList); +} + +// 0x49A818 +int questDescriptionCompare(const void* a1, const void* a2) +{ + QuestDescription* v1 = (QuestDescription*)a1; + QuestDescription* v2 = (QuestDescription*)a2; + return v1->location - v2->location; +} + +// 0x49A824 +int holodiskInit() +{ + if (gHolodiskDescriptions != NULL) { + internal_free(gHolodiskDescriptions); + gHolodiskDescriptions = NULL; + } + + gHolodisksCount = 0; + + File* stream = fileOpen("data\\holodisk.txt", "rt"); + if (stream == NULL) { + return -1; + } + + char str[256]; + while (fileReadString(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 = internal_realloc(gHolodiskDescriptions, sizeof(*gHolodiskDescriptions) * (gHolodisksCount + 1)); + if (entries == NULL) { + goto err; + } + + memcpy(&(entries[gHolodisksCount]), &entry, sizeof(*gHolodiskDescriptions)); + + gHolodiskDescriptions = entries; + gHolodisksCount++; + } + + fileClose(stream); + + return 0; + +err: + + fileClose(stream); + + return -1; +} + +// 0x49A968 +void holodiskFree() +{ + if (gHolodiskDescriptions != NULL) { + internal_free(gHolodiskDescriptions); + gHolodiskDescriptions = NULL; + } + + gHolodisksCount = 0; +} diff --git a/src/pipboy.h b/src/pipboy.h new file mode 100644 index 0000000..eee8de2 --- /dev/null +++ b/src/pipboy.h @@ -0,0 +1,247 @@ +#ifndef PIPBOY_H +#define PIPBOY_H + +#include "art.h" +#include "db.h" +#include "geometry.h" +#include "message.h" + +#include + +#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) + +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; + +typedef void PipboyRenderProc(int a1); + +extern const Rect gPipboyWindowContentRect; +extern const int gPipboyFrmIds[PIPBOY_FRM_COUNT]; + +extern QuestDescription* gQuestDescriptions; +extern int gQuestsCount; +extern HolodiskDescription* gHolodiskDescriptions; +extern int gHolodisksCount; +extern int gPipboyRestOptionsCount; +extern bool gPipboyWindowIsoWasEnabled; +extern const HolidayDescription gHolidayDescriptions[HOLIDAY_COUNT]; +extern PipboyRenderProc* _PipFnctn[5]; + +extern Size gPipboyFrmSizes[PIPBOY_FRM_COUNT]; +extern MessageListItem gPipboyMessageListItem; +extern MessageList gPipboyMessageList; +extern STRUCT_664350 _sortlist[24]; +extern MessageList gQuestsMessageList; +extern int gPipboyQuestLocationsCount; +extern unsigned char* gPipboyWindowBuffer; +extern unsigned char* gPipboyFrmData[PIPBOY_FRM_COUNT]; +extern int gPipboyWindowHolodisksCount; +extern int gPipboyMouseY; +extern int gPipboyMouseX; +extern unsigned int gPipboyLastEventTimestamp; +extern int gPipboyHolodiskLastPage; +extern int _HotLines[22]; +extern int _button; +extern int gPipboyPreviousMouseX; +extern int gPipboyPreviousMouseY; +extern int gPipboyWindow; +extern CacheEntry* gPipboyFrmHandles[PIPBOY_FRM_COUNT]; +extern int _holodisk; +extern int gPipboyWindowButtonCount; +extern int gPipboyWindowOldFont; +extern bool _proc_bail_flag; +extern int _amlst_mode; +extern int gPipboyTab; +extern int _actcnt; +extern int gPipboyWindowButtonStart; +extern int gPipboyCurrentLine; +extern int _rest_time; +extern int _amcty_indx; +extern int _view_page; +extern int gPipboyLinesCount; +extern unsigned char _hot_back_line; +extern unsigned char _holo_flag; +extern unsigned char _stat_flag; + +int pipboyOpen(bool forceRest); +int pipboyWindowInit(bool forceRest); +void pipboyWindowFree(); +void _pip_init_(); +void pipboyInit(); +void pipboyReset(); +void pipboyDrawNumber(int value, int digits, int x, int y); +void pipboyDrawDate(); +void pipboyDrawText(const char* text, int a2, int a3); +void pipboyDrawBackButton(int a1); +int _save_pipboy(File* stream); +int pipboySave(File* stream); +int pipboyLoad(File* stream); +void pipboyWindowHandleStatus(int a1); +void pipboyWindowRenderQuestLocationList(int a1); +void pipboyRenderHolodiskText(); +int pipboyWindowRenderHolodiskList(int a1); +int _qscmp(const void* a1, const void* a2); +void pipboyWindowHandleAutomaps(int a1); +int _PrintAMelevList(int a1); +int _PrintAMList(int a1); +void pipboyHandleVideoArchive(int a1); +int pipboyRenderVideoArchive(int a1); +void pipboyHandleAlarmClock(int a1); +void pipboyWindowRenderRestOptions(int a1); +void pipboyDrawHitPoints(); +void pipboyWindowCreateButtons(int a1, int a2, bool a3); +void pipboyWindowDestroyButtons(); +bool pipboyRest(int hours, int minutes, int kind); +bool _Check4Health(int a1); +bool _AddHealth(); +void _ClacTime(int* hours, int* minutes, int wakeUpHour); +int pipboyRenderScreensaver(); +int questInit(); +void questFree(); +int questDescriptionCompare(const void* a1, const void* a2); +int holodiskInit(); +void holodiskFree(); + +#endif /* PIPBOY_H */ diff --git a/src/proto.c b/src/proto.c new file mode 100644 index 0000000..e225855 --- /dev/null +++ b/src/proto.c @@ -0,0 +1,1865 @@ +#include "proto.h" + +#include "art.h" +#include "character_editor.h" +#include "combat.h" +#include "config.h" +#include "critter.h" +#include "debug.h" +#include "dialog.h" +#include "game.h" +#include "game_config.h" +#include "game_movie.h" +#include "interface.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "skill.h" +#include "stat.h" +#include "trait.h" + +#include +#include +#include + +// 0x50CF3C +char _aProto_0[] = "proto\\"; + +// 0x50D1B0 +char _aDrugStatSpecia[] = "Drug Stat (Special)"; + +// 0x50D1C4 +char _aNone_1[] = "None"; + +// 0x51C18C +char _cd_path_base[MAX_PATH]; + +// 0x51C290 +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 +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 +int _protos_been_initialized = 0; + +// obj_dude_proto +// 0x51C370 +CritterProto gDudeProto = { + 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 = _aProto_0; + +// 0x51C538 +int _init_true = 0; + +// 0x51C53C +int _retval = 0; + +// 0x66452C +char* _mp_perk_code_None; + +// 0x664530 +char* _mp_perk_code_strs[PERK_COUNT]; + +// 0x66470C +char* _mp_critter_stats_list; + +// 0x664710 +char* _critter_stats_list_None; + +// 0x664714 +char* _critter_stats_list_strs[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* gRaceTypeNames[RACE_TYPE_COUNT]; + +// 0x6647E4 +char* gSceneryTypeNames[SCENERY_TYPE_COUNT]; + +// proto.msg +// +// 0x6647FC +MessageList gProtoMessageList; + +// 0x664804 +char* gMaterialTypeNames[MATERIAL_TYPE_COUNT]; + +// "" from proto.msg +// +// 0x664824 +char* _proto_none_str; + +// 0x664828 +char* gBodyTypeNames[BODY_TYPE_COUNT]; + +// 0x664834 +char* gItemTypeNames[ITEM_TYPE_COUNT]; + +// 0x66484C +char* gDamageTypeNames[DAMAGE_TYPE_COUNT]; + +// 0x66486C +char* gCaliberTypeNames[CALIBER_TYPE_COUNT]; + +// Perk names. +// +// 0x6648B8 +char** _perk_code_strs; + +// Stat names. +// +// 0x6648BC +char** _critter_stats_list; + +// Append proto file name to proto_path from proto.lst. +// +// 0x49E758 +int _proto_list_str(int pid, char* proto_path) +{ + char path[MAX_PATH]; + char str[MAX_PATH]; + char* pch; + File* stream; + int i; + + if (pid == -1) { + return -1; + } + + if (str == NULL) { + return -1; + } + + strcpy(path, _cd_path_base); + strcat(path, "proto\\"); + strcat(path, artGetObjectTypeName(pid >> 24)); + strcat(path, "\\"); + strcat(path, artGetObjectTypeName(pid >> 24)); + strcat(path, ".lst"); + + stream = fileOpen(path, "rt"); + + i = 1; + while (fileReadString(str, sizeof(str), stream)) { + if (i == (pid & 0xFFFFFF)) { + break; + } + + i++; + } + + fileClose(stream); + + if (i != (pid & 0xFFFFFF)) { + return -1; + } + + pch = str; + while (*pch != '\0' && *pch != '\n') { + *proto_path = *pch; + proto_path++; + pch++; + } + + *proto_path = '\0'; + + return 0; +} + +// 0x49E99C +bool _proto_action_can_use(int pid) +{ + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return false; + } + + if ((proto->item.extendedFlags & 0x0800) != 0) { + return true; + } + + if ((pid >> 24) == 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 (protoGetProto(pid, &proto) == -1) { + return false; + } + + if ((proto->item.extendedFlags & 0x1000) != 0) { + return true; + } + + if ((pid >> 24) == 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 (protoGetProto(pid, &proto) == -1) { + return false; + } + + if ((pid >> 24) == 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 >> 24) != OBJ_TYPE_ITEM) { + return false; + } + + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return false; + } + + if (proto->item.type == ITEM_TYPE_CONTAINER) { + return (proto->item.extendedFlags & 0x8000) != 0; + } + + return true; +} + +// 0x49EAA4 +char* protoGetMessage(int pid, int message) +{ + char* v1 = _proto_none_str; + + Proto* proto; + if (protoGetProto(pid, &proto) != -1) { + if (proto->messageId != -1) { + MessageList* messageList = &(_proto_msg_files[pid >> 24]); + + MessageListItem messageListItem; + messageListItem.num = proto->messageId + message; + if (messageListGetItem(messageList, &messageListItem)) { + v1 = messageListItem.text; + } + } + } + + return v1; +} + +// 0x49EAFC +char* protoGetName(int pid) +{ + if (pid == 0x1000000) { + return critterGetName(gDude); + } + + return protoGetMessage(pid, PROTOTYPE_MESSAGE_NAME); +} + +// 0x49EB1C +char* protoGetDescription(int pid) +{ + return protoGetMessage(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 = buildFid(1, 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 (!artExists(a1->fid)) { + a1->fid = buildFid(1, 0, 0, 0, 0); + } + + CritterProtoData* data = &(a1->critter.data); + data->experience = 60; + data->killType = 0; + data->damageType = 0; + protoCritterDataResetStats(data); + protoCritterDataResetSkills(data); + + return 0; +} + +// 0x49EEA4 +void objectDataReset(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 +int objectCritterCombatDataRead(CritterCombatData* data, File* stream) +{ + if (fileReadInt32(stream, &(data->damageLastTurn)) == -1) return -1; + if (fileReadInt32(stream, &(data->maneuver)) == -1) return -1; + if (fileReadInt32(stream, &(data->ap)) == -1) return -1; + if (fileReadInt32(stream, &(data->results)) == -1) return -1; + if (fileReadInt32(stream, &(data->aiPacket)) == -1) return -1; + if (fileReadInt32(stream, &(data->team)) == -1) return -1; + if (fileReadInt32(stream, &(data->whoHitMeCid)) == -1) return -1; + + return 0; +} + +// 0x49EF40 +int objectCritterCombatDataWrite(CritterCombatData* data, File* stream) +{ + if (fileWriteInt32(stream, data->damageLastTurn) == -1) return -1; + if (fileWriteInt32(stream, data->maneuver) == -1) return -1; + if (fileWriteInt32(stream, data->ap) == -1) return -1; + if (fileWriteInt32(stream, data->results) == -1) return -1; + if (fileWriteInt32(stream, data->aiPacket) == -1) return -1; + if (fileWriteInt32(stream, data->team) == -1) return -1; + if (fileWriteInt32(stream, data->whoHitMeCid) == -1) return -1; + + return 0; +} + +// 0x49F004 +int objectDataRead(Object* obj, File* stream) +{ + Proto* proto; + + Inventory* inventory = &(obj->data.inventory); + if (fileReadInt32(stream, &(inventory->length)) == -1) return -1; + if (fileReadInt32(stream, &(inventory->capacity)) == -1) return -1; + // TODO: See below. + if (fileReadInt32(stream, (int*)&(inventory->items)) == -1) return -1; + + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + if (fileReadInt32(stream, &(obj->data.critter.field_0)) == -1) return -1; + if (objectCritterCombatDataRead(&(obj->data.critter.combat), stream) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.critter.hp)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.critter.radiation)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.critter.poison)) == -1) return -1; + } else { + if (fileReadInt32(stream, &(obj->data.flags)) == -1) return -1; + + if (obj->data.flags == 0xCCCCCCCC) { + debugPrint("\nNote: Reading pud: updated_flags was un-Set!"); + obj->data.flags = 0; + } + + switch (obj->pid >> 24) { + case OBJ_TYPE_ITEM: + if (protoGetProto(obj->pid, &proto) == -1) return -1; + + switch (proto->item.type) { + case ITEM_TYPE_WEAPON: + if (fileReadInt32(stream, &(obj->data.item.weapon.ammoQuantity)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.item.weapon.ammoTypePid)) == -1) return -1; + break; + case ITEM_TYPE_AMMO: + if (fileReadInt32(stream, &(obj->data.item.ammo.quantity)) == -1) return -1; + break; + case ITEM_TYPE_MISC: + if (fileReadInt32(stream, &(obj->data.item.misc.charges)) == -1) return -1; + break; + case ITEM_TYPE_KEY: + if (fileReadInt32(stream, &(obj->data.item.key.keyCode)) == -1) return -1; + break; + default: + break; + } + + break; + case OBJ_TYPE_SCENERY: + if (protoGetProto(obj->pid, &proto) == -1) return -1; + + switch (proto->scenery.type) { + case SCENERY_TYPE_DOOR: + if (fileReadInt32(stream, &(obj->data.scenery.door.openFlags)) == -1) return -1; + break; + case SCENERY_TYPE_STAIRS: + if (fileReadInt32(stream, &(obj->data.scenery.stairs.field_4)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.scenery.stairs.field_0)) == -1) return -1; + break; + case SCENERY_TYPE_ELEVATOR: + if (fileReadInt32(stream, &(obj->data.scenery.elevator.field_0)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.scenery.elevator.field_4)) == -1) return -1; + break; + case SCENERY_TYPE_LADDER_UP: + if (gMapHeader.version == 19) { + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_4)) == -1) return -1; + } else { + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_0)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_4)) == -1) return -1; + } + break; + case SCENERY_TYPE_LADDER_DOWN: + if (gMapHeader.version == 19) { + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_4)) == -1) return -1; + } else { + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_0)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.scenery.ladder.field_4)) == -1) return -1; + } + break; + } + + break; + case OBJ_TYPE_MISC: + if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { + if (fileReadInt32(stream, &(obj->data.misc.map)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.misc.tile)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.misc.elevation)) == -1) return -1; + if (fileReadInt32(stream, &(obj->data.misc.rotation)) == -1) return -1; + } + break; + } + } + + return 0; +} + +// 0x49F428 +int objectDataWrite(Object* obj, File* stream) +{ + Proto* proto; + + ObjectData* data = &(obj->data); + if (fileWriteInt32(stream, data->inventory.length) == -1) return -1; + if (fileWriteInt32(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 (fileWriteInt32(stream, (intptr_t)data->inventory.items) == -1) return -1; + + if ((obj->pid >> 24) == OBJ_TYPE_CRITTER) { + if (fileWriteInt32(stream, data->flags) == -1) return -1; + if (objectCritterCombatDataWrite(&(obj->data.critter.combat), stream) == -1) return -1; + if (fileWriteInt32(stream, data->critter.hp) == -1) return -1; + if (fileWriteInt32(stream, data->critter.radiation) == -1) return -1; + if (fileWriteInt32(stream, data->critter.poison) == -1) return -1; + } else { + if (fileWriteInt32(stream, data->flags) == -1) return -1; + + switch (obj->pid >> 24) { + case OBJ_TYPE_ITEM: + if (protoGetProto(obj->pid, &proto) == -1) return -1; + + switch (proto->item.type) { + case ITEM_TYPE_WEAPON: + if (fileWriteInt32(stream, data->item.weapon.ammoQuantity) == -1) return -1; + if (fileWriteInt32(stream, data->item.weapon.ammoTypePid) == -1) return -1; + break; + case ITEM_TYPE_AMMO: + if (fileWriteInt32(stream, data->item.ammo.quantity) == -1) return -1; + break; + case ITEM_TYPE_MISC: + if (fileWriteInt32(stream, data->item.misc.charges) == -1) return -1; + break; + case ITEM_TYPE_KEY: + if (fileWriteInt32(stream, data->item.key.keyCode) == -1) return -1; + break; + } + break; + case OBJ_TYPE_SCENERY: + if (protoGetProto(obj->pid, &proto) == -1) return -1; + + switch (proto->scenery.type) { + case SCENERY_TYPE_DOOR: + if (fileWriteInt32(stream, data->scenery.door.openFlags) == -1) return -1; + break; + case SCENERY_TYPE_STAIRS: + if (fileWriteInt32(stream, data->scenery.stairs.field_4) == -1) return -1; + if (fileWriteInt32(stream, data->scenery.stairs.field_0) == -1) return -1; + break; + case SCENERY_TYPE_ELEVATOR: + if (fileWriteInt32(stream, data->scenery.elevator.field_0) == -1) return -1; + if (fileWriteInt32(stream, data->scenery.elevator.field_4) == -1) return -1; + break; + case SCENERY_TYPE_LADDER_UP: + if (fileWriteInt32(stream, data->scenery.ladder.field_0) == -1) return -1; + if (fileWriteInt32(stream, data->scenery.elevator.field_4) == -1) return -1; + break; + case SCENERY_TYPE_LADDER_DOWN: + if (fileWriteInt32(stream, data->scenery.ladder.field_0) == -1) return -1; + if (fileWriteInt32(stream, data->scenery.elevator.field_4) == -1) return -1; + break; + default: + break; + } + break; + case OBJ_TYPE_MISC: + if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { + if (fileWriteInt32(stream, data->misc.map) == -1) return -1; + if (fileWriteInt32(stream, data->misc.tile) == -1) return -1; + if (fileWriteInt32(stream, data->misc.elevation) == -1) return -1; + if (fileWriteInt32(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 (protoGetProto(obj->pid, &proto) == -1) { + return -1; + } + + switch (obj->pid >> 24) { + 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.field_4 = proto->scenery.data.stairs.field_0; + data->scenery.stairs.field_0 = proto->scenery.data.stairs.field_4; + break; + case SCENERY_TYPE_ELEVATOR: + data->scenery.elevator.field_0 = proto->scenery.data.elevator.field_0; + data->scenery.elevator.field_4 = proto->scenery.data.elevator.field_4; + break; + case SCENERY_TYPE_LADDER_UP: + case SCENERY_TYPE_LADDER_DOWN: + data->scenery.ladder.field_0 = 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 ((obj->pid >> 24) != 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); + critterUpdateDerivedStats(obj); + obj->data.critter.combat.whoHitMe = NULL; + + Proto* proto; + if (protoGetProto(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 (protoGetProto(0x1000000, &proto) == -1) { + return -1; + } + + int nativeLook = DUDE_NATIVE_LOOK_TRIBAL; + if (gameMovieIsSeen(MOVIE_VSUIT)) { + nativeLook = DUDE_NATIVE_LOOK_JUMPSUIT; + } + + int frmId; + if (critterGetStat(gDude, 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 (critterGetArmor(gDude) == NULL) { + int v1 = 0; + if (critterGetItem2(gDude) != NULL || critterGetItem1(gDude) != NULL) { + v1 = (gDude->fid & 0xF000) >> 12; + } + + int fid = buildFid(1, _art_vault_guy_num, 0, v1, 0); + objectSetFid(gDude, fid, NULL); + } + + proto->fid = buildFid(1, _art_vault_guy_num, 0, 0, 0); + + return 0; +} + +// proto_dude_init +// 0x49FA64 +int _proto_dude_init(const char* path) +{ + gDudeProto.fid = buildFid(1, _art_vault_guy_num, 0, 0, 0); + + if (_init_true) { + _obj_inven_free(&(gDude->data.inventory)); + } + + _init_true = 1; + + Proto* proto; + if (protoGetProto(0x1000000, &proto) == -1) { + return -1; + } + + protoGetProto(gDude->pid, &proto); + + _proto_update_init(gDude); + gDude->data.critter.combat.aiPacket = 0; + gDude->data.critter.combat.team = 0; + _ResetPlayer(); + + if (gcdLoad(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 ((gDude->flags & OBJECT_FLAG_0x08) != 0) { + _obj_toggle_flat(gDude, NULL); + } + + if ((gDude->flags & OBJECT_NO_BLOCK) != 0) { + gDude->flags &= ~OBJECT_NO_BLOCK; + } + + critterUpdateDerivedStats(gDude); + critterAdjustHitPoints(gDude, 10000); + + if (_retval) { + debugPrint("\n ** Error in proto_dude_init()! **\n"); + } + + return 0; +} + +// proto_data_member +// 0x49FFD8 +int _proto_data_member(int pid, int member, int* value) +{ + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + switch (pid >> 24) { + case OBJ_TYPE_ITEM: + switch (member) { + case ITEM_DATA_MEMBER_PID: + *value = proto->pid; + break; + case ITEM_DATA_MEMBER_NAME: + // NOTE: uninline + *value = (int)protoGetName(proto->scenery.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case ITEM_DATA_MEMBER_DESCRIPTION: + // NOTE: Uninline. + *value = (int)protoGetDescription(proto->pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case ITEM_DATA_MEMBER_FID: + *value = proto->fid; + break; + case ITEM_DATA_MEMBER_LIGHT_DISTANCE: + *value = proto->item.lightDistance; + break; + case ITEM_DATA_MEMBER_LIGHT_INTENSITY: + *value = proto->item.lightIntensity; + break; + case ITEM_DATA_MEMBER_FLAGS: + *value = proto->item.flags; + break; + case ITEM_DATA_MEMBER_EXTENDED_FLAGS: + *value = proto->item.extendedFlags; + break; + case ITEM_DATA_MEMBER_SID: + *value = proto->item.sid; + break; + case ITEM_DATA_MEMBER_TYPE: + *value = proto->item.type; + break; + case ITEM_DATA_MEMBER_MATERIAL: + *value = proto->item.material; + break; + case ITEM_DATA_MEMBER_SIZE: + *value = proto->item.size; + break; + case ITEM_DATA_MEMBER_WEIGHT: + *value = proto->item.weight; + break; + case ITEM_DATA_MEMBER_COST: + *value = proto->item.cost; + break; + case ITEM_DATA_MEMBER_INVENTORY_FID: + *value = proto->item.inventoryFid; + break; + case ITEM_DATA_MEMBER_WEAPON_RANGE: + if (proto->item.type == ITEM_TYPE_WEAPON) { + *value = proto->item.data.weapon.maxRange1; + } + break; + default: + debugPrint("\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 = proto->critter.pid; + break; + case CRITTER_DATA_MEMBER_NAME: + // NOTE: Uninline. + *value = (int)protoGetName(proto->critter.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case CRITTER_DATA_MEMBER_DESCRIPTION: + // NOTE: Uninline. + *value = (int)protoGetDescription(proto->critter.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case CRITTER_DATA_MEMBER_FID: + *value = proto->critter.fid; + break; + case CRITTER_DATA_MEMBER_LIGHT_DISTANCE: + *value = proto->critter.lightDistance; + break; + case CRITTER_DATA_MEMBER_LIGHT_INTENSITY: + *value = proto->critter.lightIntensity; + break; + case CRITTER_DATA_MEMBER_FLAGS: + *value = proto->critter.flags; + break; + case CRITTER_DATA_MEMBER_EXTENDED_FLAGS: + *value = proto->critter.extendedFlags; + break; + case CRITTER_DATA_MEMBER_SID: + *value = proto->critter.sid; + break; + case CRITTER_DATA_MEMBER_HEAD_FID: + *value = proto->critter.headFid; + break; + case CRITTER_DATA_MEMBER_BODY_TYPE: + *value = proto->critter.data.bodyType; + break; + default: + debugPrint("\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 = proto->scenery.pid; + break; + case SCENERY_DATA_MEMBER_NAME: + // NOTE: Uninline. + *value = (int)protoGetName(proto->scenery.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case SCENERY_DATA_MEMBER_DESCRIPTION: + // NOTE: Uninline. + *value = (int)protoGetDescription(proto->scenery.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case SCENERY_DATA_MEMBER_FID: + *value = proto->scenery.fid; + break; + case SCENERY_DATA_MEMBER_LIGHT_DISTANCE: + *value = proto->scenery.lightDistance; + break; + case SCENERY_DATA_MEMBER_LIGHT_INTENSITY: + *value = proto->scenery.lightIntensity; + break; + case SCENERY_DATA_MEMBER_FLAGS: + *value = proto->scenery.flags; + break; + case SCENERY_DATA_MEMBER_EXTENDED_FLAGS: + *value = proto->scenery.extendedFlags; + break; + case SCENERY_DATA_MEMBER_SID: + *value = proto->scenery.sid; + break; + case SCENERY_DATA_MEMBER_TYPE: + *value = proto->scenery.type; + break; + case SCENERY_DATA_MEMBER_MATERIAL: + *value = proto->scenery.field_2C; + break; + default: + debugPrint("\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 = proto->wall.pid; + break; + case WALL_DATA_MEMBER_NAME: + // NOTE: Uninline. + *value = (int)protoGetName(proto->wall.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case WALL_DATA_MEMBER_DESCRIPTION: + // NOTE: Uninline. + *value = (int)protoGetDescription(proto->wall.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case WALL_DATA_MEMBER_FID: + *value = proto->wall.fid; + break; + case WALL_DATA_MEMBER_LIGHT_DISTANCE: + *value = proto->wall.lightDistance; + break; + case WALL_DATA_MEMBER_LIGHT_INTENSITY: + *value = proto->wall.lightIntensity; + break; + case WALL_DATA_MEMBER_FLAGS: + *value = proto->wall.flags; + break; + case WALL_DATA_MEMBER_EXTENDED_FLAGS: + *value = proto->wall.extendedFlags; + break; + case WALL_DATA_MEMBER_SID: + *value = proto->wall.sid; + break; + case WALL_DATA_MEMBER_MATERIAL: + *value = proto->wall.material; + break; + default: + debugPrint("\n\tError: Unimp'd data member in member in proto_data_member!"); + break; + } + break; + case OBJ_TYPE_TILE: + debugPrint("\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 = proto->misc.pid; + break; + case MISC_DATA_MEMBER_NAME: + // NOTE: Uninline. + *value = (int)protoGetName(proto->misc.pid); + return PROTO_DATA_MEMBER_TYPE_STRING; + case MISC_DATA_MEMBER_DESCRIPTION: + // NOTE: Uninline. + *value = (int)protoGetDescription(proto->misc.pid); + // FIXME: Errornously report type as int, should be string. + return PROTO_DATA_MEMBER_TYPE_INT; + case MISC_DATA_MEMBER_FID: + *value = proto->misc.fid; + return 1; + case MISC_DATA_MEMBER_LIGHT_DISTANCE: + *value = proto->misc.lightDistance; + return 1; + case MISC_DATA_MEMBER_LIGHT_INTENSITY: + *value = proto->misc.lightIntensity; + break; + case MISC_DATA_MEMBER_FLAGS: + *value = proto->misc.flags; + break; + case MISC_DATA_MEMBER_EXTENDED_FLAGS: + *value = proto->misc.extendedFlags; + break; + default: + debugPrint("\n\tError: Unimp'd data member in member in proto_data_member!"); + break; + } + break; + } + + return PROTO_DATA_MEMBER_TYPE_INT; +} + +// proto_init +// 0x4A0390 +int protoInit() +{ + char* master_patches; + int len; + MessageListItem messageListItem; + char path[MAX_PATH]; + int i; + + if (!configGetString(&gGameConfig, 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*)&gDudeProto, 0x1000000); + + gDudeProto.pid = 0x1000000; + gDudeProto.fid = buildFid(1, 1, 0, 0, 0); + + gDude->pid = 0x1000000; + gDude->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 (!messageListInit(&(_proto_msg_files[i]))) { + debugPrint("\nError: Initing proto message files!"); + return -1; + } + } + + for (i = 0; i < 6; i++) { + sprintf(path, "%spro_%.4s%s", asc_5186C8, artGetObjectTypeName(i), ".msg"); + + if (!messageListLoad(&(_proto_msg_files[i]), path)) { + debugPrint("\nError: Loading proto message files!"); + return -1; + } + } + + _mp_critter_stats_list = _aDrugStatSpecia; + _critter_stats_list = _critter_stats_list_strs; + _critter_stats_list_None = _aNone_1; + for (i = 0; i < STAT_COUNT; i++) { + _critter_stats_list_strs[i] = statGetName(i); + if (_critter_stats_list_strs[i] == NULL) { + debugPrint("\nError: Finding stat names!"); + return -1; + } + } + + _mp_perk_code_None = _aNone_1; + _perk_code_strs = _mp_perk_code_strs; + for (i = 0; i < PERK_COUNT; i++) { + _mp_perk_code_strs[i] = perkGetName(i); + if (_mp_perk_code_strs[i] == NULL) { + debugPrint("\nError: Finding perk names!"); + return -1; + } + } + + if (!messageListInit(&gProtoMessageList)) { + debugPrint("\nError: Initing main proto message file!"); + return -1; + } + + sprintf(path, "%sproto.msg", asc_5186C8); + + if (!messageListLoad(&gProtoMessageList, path)) { + debugPrint("\nError: Loading main proto message file!"); + return -1; + } + + _proto_none_str = getmsg(&gProtoMessageList, &messageListItem, 10); + + // material type names + for (i = 0; i < MATERIAL_TYPE_COUNT; i++) { + gMaterialTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 100 + i); + } + + // item type names + for (i = 0; i < ITEM_TYPE_COUNT; i++) { + gItemTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 150 + i); + } + + // scenery type names + for (i = 0; i < SCENERY_TYPE_COUNT; i++) { + gSceneryTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 200 + i); + } + + // damage code types + for (i = 0; i < DAMAGE_TYPE_COUNT; i++) { + gDamageTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 250 + i); + } + + // caliber types + for (i = 0; i < CALIBER_TYPE_COUNT; i++) { + gCaliberTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 300 + i); + } + + // race types + for (i = 0; i < RACE_TYPE_COUNT; i++) { + gRaceTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 350 + i); + } + + // body types + for (i = 0; i < BODY_TYPE_COUNT; i++) { + gBodyTypeNames[i] = getmsg(&gProtoMessageList, &messageListItem, 400 + i); + } + + return 0; +} + +// 0x4A0814 +void protoReset() +{ + int i; + + // TODO: Get rid of cast. + _proto_critter_init((Proto*)&gDudeProto, 0x1000000); + gDudeProto.pid = 0x1000000; + gDudeProto.fid = buildFid(1, 1, 0, 0, 0); + + gDude->pid = 0x1000000; + gDude->sid = -1; + gDude->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 protoExit() +{ + int i; + + for (i = 0; i < 6; i++) { + _proto_remove_list(i); + } + + for (i = 0; i < 6; i++) { + messageListFree(&(_proto_msg_files[i])); + } + + messageListFree(&gProtoMessageList); +} + +// 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]; + strcpy(path, _cd_path_base); + strcat(path, _proto_path_base); + strcat(path, artGetObjectTypeName(index)); + strcat(path, "\\"); + strcat(path, artGetObjectTypeName(index)); + strcat(path, ".lst"); + + File* stream = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + int ch = '\0'; + while (1) { + ch = fileReadChar(stream); + if (ch == -1) { + break; + } + + if (ch == '\n') { + ptr->max_entries_num++; + } + } + + if (ch != '\n') { + ptr->max_entries_num++; + } + + fileClose(stream); + } + + return 0; +} + +// 0x4A0AEC +int protoItemDataRead(ItemProtoData* item_data, int type, File* stream) +{ + switch (type) { + case ITEM_TYPE_ARMOR: + if (fileReadInt32(stream, &(item_data->armor.armorClass)) == -1) return -1; + if (fileReadInt32List(stream, item_data->armor.damageResistance, 7) == -1) return -1; + if (fileReadInt32List(stream, item_data->armor.damageThreshold, 7) == -1) return -1; + if (fileReadInt32(stream, &(item_data->armor.perk)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->armor.maleFid)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->armor.femaleFid)) == -1) return -1; + + return 0; + case ITEM_TYPE_CONTAINER: + if (fileReadInt32(stream, &(item_data->container.maxSize)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->container.openFlags)) == -1) return -1; + + return 0; + case ITEM_TYPE_DRUG: + if (fileReadInt32(stream, &(item_data->drug.stat[0])) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.stat[1])) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.stat[2])) == -1) return -1; + if (fileReadInt32List(stream, item_data->drug.amount, 3) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.duration1)) == -1) return -1; + if (fileReadInt32List(stream, item_data->drug.amount1, 3) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.duration2)) == -1) return -1; + if (fileReadInt32List(stream, item_data->drug.amount2, 3) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.addictionChance)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.withdrawalEffect)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->drug.withdrawalOnset)) == -1) return -1; + + return 0; + case ITEM_TYPE_WEAPON: + if (fileReadInt32(stream, &(item_data->weapon.animationCode)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.minDamage)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.maxDamage)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.damageType)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.maxRange1)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.maxRange2)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.projectilePid)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.minStrength)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.actionPointCost1)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.actionPointCost2)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.criticalFailureType)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.perk)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.rounds)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.caliber)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.ammoTypePid)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->weapon.ammoCapacity)) == -1) return -1; + if (fileReadUInt8(stream, &(item_data->weapon.soundCode)) == -1) return -1; + + return 0; + case ITEM_TYPE_AMMO: + if (fileReadInt32(stream, &(item_data->ammo.caliber)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->ammo.quantity)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->ammo.armorClassModifier)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->ammo.damageResistanceModifier)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->ammo.damageMultiplier)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->ammo.damageDivisor)) == -1) return -1; + + return 0; + case ITEM_TYPE_MISC: + if (fileReadInt32(stream, &(item_data->misc.powerTypePid)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->misc.powerType)) == -1) return -1; + if (fileReadInt32(stream, &(item_data->misc.charges)) == -1) return -1; + + return 0; + case ITEM_TYPE_KEY: + if (fileReadInt32(stream, &(item_data->key.keyCode)) == -1) return -1; + + return 0; + } + + return 0; +} + +// 0x4A0ED0 +int protoSceneryDataRead(SceneryProtoData* scenery_data, int type, File* stream) +{ + switch (type) { + case SCENERY_TYPE_DOOR: + if (fileReadInt32(stream, &(scenery_data->door.openFlags)) == -1) return -1; + if (fileReadInt32(stream, &(scenery_data->door.keyCode)) == -1) return -1; + + return 0; + case SCENERY_TYPE_STAIRS: + if (fileReadInt32(stream, &(scenery_data->stairs.field_0)) == -1) return -1; + if (fileReadInt32(stream, &(scenery_data->stairs.field_4)) == -1) return -1; + + return 0; + case SCENERY_TYPE_ELEVATOR: + if (fileReadInt32(stream, &(scenery_data->elevator.field_0)) == -1) return -1; + if (fileReadInt32(stream, &(scenery_data->elevator.field_4)) == -1) return -1; + + return 0; + case SCENERY_TYPE_LADDER_UP: + case SCENERY_TYPE_LADDER_DOWN: + if (fileReadInt32(stream, &(scenery_data->ladder.field_0)) == -1) return -1; + + return 0; + case SCENERY_TYPE_GENERIC: + if (fileReadInt32(stream, &(scenery_data->generic.field_0)) == -1) return -1; + + return 0; + } + + return 0; +} + +// read .pro file +// 0x4A0FA0 +int protoRead(Proto* proto, File* stream) +{ + if (fileReadInt32(stream, &(proto->pid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->messageId)) == -1) return -1; + if (fileReadInt32(stream, &(proto->fid)) == -1) return -1; + + switch (proto->pid >> 24) { + case OBJ_TYPE_ITEM: + if (fileReadInt32(stream, &(proto->item.lightDistance)) == -1) return -1; + if (_db_freadInt(stream, &(proto->item.lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.extendedFlags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.sid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.type)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.material)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.size)) == -1) return -1; + if (_db_freadInt(stream, &(proto->item.weight)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.cost)) == -1) return -1; + if (fileReadInt32(stream, &(proto->item.inventoryFid)) == -1) return -1; + if (fileReadUInt8(stream, &(proto->item.field_80)) == -1) return -1; + if (protoItemDataRead(&(proto->item.data), proto->item.type, stream) == -1) return -1; + + return 0; + case OBJ_TYPE_CRITTER: + if (fileReadInt32(stream, &(proto->critter.lightDistance)) == -1) return -1; + if (_db_freadInt(stream, &(proto->critter.lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.extendedFlags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.sid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.headFid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.aiPacket)) == -1) return -1; + if (fileReadInt32(stream, &(proto->critter.team)) == -1) return -1; + + if (protoCritterDataRead(stream, &(proto->critter.data)) == -1) return -1; + + return 0; + case OBJ_TYPE_SCENERY: + if (fileReadInt32(stream, &(proto->scenery.lightDistance)) == -1) return -1; + if (_db_freadInt(stream, &(proto->scenery.lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &(proto->scenery.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->scenery.extendedFlags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->scenery.sid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->scenery.type)) == -1) return -1; + if (fileReadInt32(stream, &(proto->scenery.field_2C)) == -1) return -1; + if (fileReadUInt8(stream, &(proto->scenery.field_34)) == -1) return -1; + if (protoSceneryDataRead(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1; + return 0; + case OBJ_TYPE_WALL: + if (fileReadInt32(stream, &(proto->wall.lightDistance)) == -1) return -1; + if (_db_freadInt(stream, &(proto->wall.lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &(proto->wall.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->wall.extendedFlags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->wall.sid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->wall.material)) == -1) return -1; + + return 0; + case OBJ_TYPE_TILE: + if (fileReadInt32(stream, &(proto->tile.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->tile.extendedFlags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->tile.sid)) == -1) return -1; + if (fileReadInt32(stream, &(proto->tile.material)) == -1) return -1; + + return 0; + case OBJ_TYPE_MISC: + if (fileReadInt32(stream, &(proto->misc.lightDistance)) == -1) return -1; + if (_db_freadInt(stream, &(proto->misc.lightIntensity)) == -1) return -1; + if (fileReadInt32(stream, &(proto->misc.flags)) == -1) return -1; + if (fileReadInt32(stream, &(proto->misc.extendedFlags)) == -1) return -1; + + return 0; + } + + return -1; +} + +// 0x4A1390 +int protoItemDataWrite(ItemProtoData* item_data, int type, File* stream) +{ + switch (type) { + case ITEM_TYPE_ARMOR: + if (fileWriteInt32(stream, item_data->armor.armorClass) == -1) return -1; + if (fileWriteInt32List(stream, item_data->armor.damageResistance, 7) == -1) return -1; + if (fileWriteInt32List(stream, item_data->armor.damageThreshold, 7) == -1) return -1; + if (fileWriteInt32(stream, item_data->armor.perk) == -1) return -1; + if (fileWriteInt32(stream, item_data->armor.maleFid) == -1) return -1; + if (fileWriteInt32(stream, item_data->armor.femaleFid) == -1) return -1; + + return 0; + case ITEM_TYPE_CONTAINER: + if (fileWriteInt32(stream, item_data->container.maxSize) == -1) return -1; + if (fileWriteInt32(stream, item_data->container.openFlags) == -1) return -1; + + return 0; + case ITEM_TYPE_DRUG: + if (fileWriteInt32(stream, item_data->drug.stat[0]) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.stat[1]) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.stat[2]) == -1) return -1; + if (fileWriteInt32List(stream, item_data->drug.amount, 3) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.duration1) == -1) return -1; + if (fileWriteInt32List(stream, item_data->drug.amount1, 3) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.duration2) == -1) return -1; + if (fileWriteInt32List(stream, item_data->drug.amount2, 3) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.addictionChance) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.withdrawalEffect) == -1) return -1; + if (fileWriteInt32(stream, item_data->drug.withdrawalOnset) == -1) return -1; + + return 0; + case ITEM_TYPE_WEAPON: + if (fileWriteInt32(stream, item_data->weapon.animationCode) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.maxDamage) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.minDamage) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.damageType) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.maxRange1) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.maxRange2) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.projectilePid) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.minStrength) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.actionPointCost1) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.actionPointCost2) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.criticalFailureType) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.perk) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.rounds) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.caliber) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.ammoTypePid) == -1) return -1; + if (fileWriteInt32(stream, item_data->weapon.ammoCapacity) == -1) return -1; + if (fileWriteUInt8(stream, item_data->weapon.soundCode) == -1) return -1; + + return 0; + case ITEM_TYPE_AMMO: + if (fileWriteInt32(stream, item_data->ammo.caliber) == -1) return -1; + if (fileWriteInt32(stream, item_data->ammo.quantity) == -1) return -1; + if (fileWriteInt32(stream, item_data->ammo.armorClassModifier) == -1) return -1; + if (fileWriteInt32(stream, item_data->ammo.damageResistanceModifier) == -1) return -1; + if (fileWriteInt32(stream, item_data->ammo.damageMultiplier) == -1) return -1; + if (fileWriteInt32(stream, item_data->ammo.damageDivisor) == -1) return -1; + + return 0; + case ITEM_TYPE_MISC: + if (fileWriteInt32(stream, item_data->misc.powerTypePid) == -1) return -1; + if (fileWriteInt32(stream, item_data->misc.powerType) == -1) return -1; + if (fileWriteInt32(stream, item_data->misc.charges) == -1) return -1; + + return 0; + case ITEM_TYPE_KEY: + if (fileWriteInt32(stream, item_data->key.keyCode) == -1) return -1; + + return 0; + } + + return 0; +} + +// 0x4A16E4 +int protoSceneryDataWrite(SceneryProtoData* scenery_data, int type, File* stream) +{ + switch (type) { + case SCENERY_TYPE_DOOR: + if (fileWriteInt32(stream, scenery_data->door.openFlags) == -1) return -1; + if (fileWriteInt32(stream, scenery_data->door.keyCode) == -1) return -1; + + return 0; + case SCENERY_TYPE_STAIRS: + if (fileWriteInt32(stream, scenery_data->stairs.field_0) == -1) return -1; + if (fileWriteInt32(stream, scenery_data->stairs.field_4) == -1) return -1; + + return 0; + case SCENERY_TYPE_ELEVATOR: + if (fileWriteInt32(stream, scenery_data->elevator.field_0) == -1) return -1; + if (fileWriteInt32(stream, scenery_data->elevator.field_4) == -1) return -1; + + return 0; + case SCENERY_TYPE_LADDER_UP: + case SCENERY_TYPE_LADDER_DOWN: + if (fileWriteInt32(stream, scenery_data->ladder.field_0) == -1) return -1; + + return 0; + case SCENERY_TYPE_GENERIC: + if (fileWriteInt32(stream, scenery_data->generic.field_0) == -1) return -1; + + return 0; + } + + return 0; +} + +// 0x4A17B4 +int protoWrite(Proto* proto, File* stream) +{ + if (fileWriteInt32(stream, proto->pid) == -1) return -1; + if (fileWriteInt32(stream, proto->messageId) == -1) return -1; + if (fileWriteInt32(stream, proto->fid) == -1) return -1; + + switch (proto->pid >> 24) { + case OBJ_TYPE_ITEM: + if (fileWriteInt32(stream, proto->item.lightDistance) == -1) return -1; + if (_db_fwriteLong(stream, proto->item.lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, proto->item.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->item.extendedFlags) == -1) return -1; + if (fileWriteInt32(stream, proto->item.sid) == -1) return -1; + if (fileWriteInt32(stream, proto->item.type) == -1) return -1; + if (fileWriteInt32(stream, proto->item.material) == -1) return -1; + if (fileWriteInt32(stream, proto->item.size) == -1) return -1; + if (_db_fwriteLong(stream, proto->item.weight) == -1) return -1; + if (fileWriteInt32(stream, proto->item.cost) == -1) return -1; + if (fileWriteInt32(stream, proto->item.inventoryFid) == -1) return -1; + if (fileWriteUInt8(stream, proto->item.field_80) == -1) return -1; + if (protoItemDataWrite(&(proto->item.data), proto->item.type, stream) == -1) return -1; + + return 0; + case OBJ_TYPE_CRITTER: + if (fileWriteInt32(stream, proto->critter.lightDistance) == -1) return -1; + if (_db_fwriteLong(stream, proto->critter.lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.extendedFlags) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.sid) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.headFid) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.aiPacket) == -1) return -1; + if (fileWriteInt32(stream, proto->critter.team) == -1) return -1; + if (protoCritterDataWrite(stream, &(proto->critter.data)) == -1) return -1; + + return 0; + case OBJ_TYPE_SCENERY: + if (fileWriteInt32(stream, proto->scenery.lightDistance) == -1) return -1; + if (_db_fwriteLong(stream, proto->scenery.lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, proto->scenery.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->scenery.extendedFlags) == -1) return -1; + if (fileWriteInt32(stream, proto->scenery.sid) == -1) return -1; + if (fileWriteInt32(stream, proto->scenery.type) == -1) return -1; + if (fileWriteInt32(stream, proto->scenery.field_2C) == -1) return -1; + if (fileWriteUInt8(stream, proto->scenery.field_34) == -1) return -1; + if (protoSceneryDataWrite(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1; + case OBJ_TYPE_WALL: + if (fileWriteInt32(stream, proto->wall.lightDistance) == -1) return -1; + if (_db_fwriteLong(stream, proto->wall.lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, proto->wall.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->wall.extendedFlags) == -1) return -1; + if (fileWriteInt32(stream, proto->wall.sid) == -1) return -1; + if (fileWriteInt32(stream, proto->wall.material) == -1) return -1; + + return 0; + case OBJ_TYPE_TILE: + if (fileWriteInt32(stream, proto->tile.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->tile.extendedFlags) == -1) return -1; + if (fileWriteInt32(stream, proto->tile.sid) == -1) return -1; + if (fileWriteInt32(stream, proto->tile.material) == -1) return -1; + + return 0; + case OBJ_TYPE_MISC: + if (fileWriteInt32(stream, proto->misc.lightDistance) == -1) return -1; + if (_db_fwriteLong(stream, proto->misc.lightIntensity) == -1) return -1; + if (fileWriteInt32(stream, proto->misc.flags) == -1) return -1; + if (fileWriteInt32(stream, proto->misc.extendedFlags) == -1) return -1; + + return 0; + } + + return -1; +} + +// 0x4A1B30 +int _proto_save_pid(int pid) +{ + Proto* proto; + if (protoGetProto(pid, &proto) == -1) { + return -1; + } + + char path[260]; + strcpy(path, _cd_path_base); + strcat(path, _proto_path_base); + + if (pid != -1) { + strcat(path, artGetObjectTypeName(pid >> 24)); + } + + strcat(path, "\\"); + + _proto_list_str(pid, path + strlen(path)); + + File* stream = fileOpen(path, "wb"); + if (stream == NULL) { + return -1; + } + + int rc = protoWrite(proto, stream); + + fileClose(stream); + + return rc; +} + +// 0x4A1C3C +int _proto_load_pid(int pid, Proto** protoPtr) +{ + char path[MAX_PATH]; + strcpy(path, _cd_path_base); + + strcat(path, "proto\\"); + + if (pid != -1) { + strcat(path, artGetObjectTypeName(pid >> 24)); + } + + strcat(path, "\\"); + + if (_proto_list_str(pid, path + strlen(path)) == -1) { + return -1; + } + + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + debugPrint("\nError: Can't fopen proto!\n"); + *protoPtr = NULL; + return -1; + } + + if (_proto_find_free_subnode(pid >> 24, protoPtr) == -1) { + fileClose(stream); + return -1; + } + + if (protoRead(*protoPtr, stream) != 0) { + fileClose(stream); + return -1; + } + + fileClose(stream); + return 0; +} + +// allocate memory for proto of given type and adds it to proto cache +int _proto_find_free_subnode(int type, Proto** protoPtr) +{ + size_t size = (type >= 0 && type < 11) ? _proto_sizes[type] : 0; + + Proto* proto = internal_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 = internal_malloc(sizeof(ProtoListExtent)); + if (protoListExtent == NULL) { + internal_free(proto); + *protoPtr = NULL; + return -1; + } + + newExtent->length = 0; + newExtent->next = NULL; + + protoList->tail = newExtent; + protoList->length++; + + protoListExtent = newExtent; + } + } else { + protoListExtent = internal_malloc(sizeof(ProtoListExtent)); + if (protoListExtent == NULL) { + internal_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 +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++) { + internal_free(protoListExtent->proto[index]); + } + + internal_free(protoListExtent); + } +} + +// Clear proto cache of given type. +// +// 0x4A2094 +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++) { + internal_free(curr->proto[index]); + } + internal_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); + } +} + +// proto_ptr +// 0x4A2108 +int protoGetProto(int pid, Proto** protoPtr) +{ + *protoPtr = NULL; + + if (pid == -1) { + return -1; + } + + if (pid == 0x1000000) { + *protoPtr = (Proto*)&gDudeProto; + return 0; + } + + ProtoList* protoList = &(_protoLists[pid >> 24]); + 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 >> 24); + } + } + + return _proto_load_pid(pid, protoPtr); +} + +// 0x4A21DC +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; + protoGetProto(gDude->pid, &proto); + + pcStatsReset(); + protoCritterDataResetStats(&(proto->critter.data)); + critterReset(); + _editor_reset(); + protoCritterDataResetSkills(&(proto->critter.data)); + skillsReset(); + perksReset(); + traitsReset(); + critterUpdateDerivedStats(gDude); + return 0; +} diff --git a/src/proto.h b/src/proto.h new file mode 100644 index 0000000..1609bc2 --- /dev/null +++ b/src/proto.h @@ -0,0 +1,168 @@ +#ifndef PROTO_H +#define PROTO_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "perk_defs.h" +#include "proto_types.h" +#include "skill_defs.h" +#include "stat_defs.h" + +#define WIN32_LEAN_AND_MEAN +#include + +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 enum PrototypeMessage { + PROTOTYPE_MESSAGE_NAME, + PROTOTYPE_MESSAGE_DESCRIPTION, +} PrototypeMesage; + +extern char _aProto_0[]; + +extern char _cd_path_base[MAX_PATH]; +extern ProtoList _protoLists[11]; +extern const size_t _proto_sizes[11]; +extern int _protos_been_initialized; +extern CritterProto gDudeProto; +extern char* _proto_path_base; +extern int _init_true; +extern int _retval; + +extern char* _mp_perk_code_None; +extern char* _mp_perk_code_strs[PERK_COUNT]; +extern char* _mp_critter_stats_list; +extern char* _critter_stats_list_None; +extern char* _critter_stats_list_strs[STAT_COUNT]; +extern MessageList _proto_msg_files[6]; +extern char* gRaceTypeNames[2]; +extern char* gSceneryTypeNames[6]; +extern MessageList gProtoMessageList; +extern char* gMaterialTypeNames[8]; +extern char* _proto_none_str; +extern char* gBodyTypeNames[3]; +extern char* gItemTypeNames[7]; +extern char* gDamageTypeNames[7]; +extern char* gCaliberTypeNames[19]; + +extern char** _perk_code_strs; +extern char** _critter_stats_list; + +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* protoGetMessage(int pid, int message); +char* protoGetName(int pid); +char* protoGetDescription(int pid); +int _proto_critter_init(Proto* a1, int a2); +void objectDataReset(Object* obj); +int objectCritterCombatDataRead(CritterCombatData* data, File* stream); +int objectCritterCombatDataWrite(CritterCombatData* data, File* stream); +int objectDataRead(Object* obj, File* stream); +int objectDataWrite(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, int* value); +int protoInit(); +void protoReset(); +void protoExit(); +int _proto_header_load(); +int protoItemDataRead(ItemProtoData* item_data, int type, File* stream); +int protoSceneryDataRead(SceneryProtoData* scenery_data, int type, File* stream); +int protoRead(Proto* buf, File* stream); +int protoItemDataWrite(ItemProtoData* item_data, int type, File* stream); +int protoSceneryDataWrite(SceneryProtoData* scenery_data, int type, File* stream); +int protoWrite(Proto* buf, File* stream); +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_some_list(int type); +void _proto_remove_list(int type); +void _proto_remove_all(); +int protoGetProto(int pid, Proto** out_proto); +int _proto_new_id(int a1); +int _proto_max_id(int a1); +int _ResetPlayer(); + +#endif /* PROTO_H */ diff --git a/src/proto_instance.c b/src/proto_instance.c new file mode 100644 index 0000000..28f0744 --- /dev/null +++ b/src/proto_instance.c @@ -0,0 +1,2218 @@ +#include "proto_instance.h" + +#include "animation.h" +#include "color.h" +#include "combat.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_sound.h" +#include "geometry.h" +#include "interface.h" +#include "item.h" +#include "map.h" +#include "object.h" +#include "palette.h" +#include "perk.h" +#include "proto.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "tile.h" +#include "world_map.h" + +#include +#include +#include + +// 0x49A990 +MessageListItem stru_49A990; + +// 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 (protoGetProto(object->pid, &proto) == -1) { + return -1; + } + + int sid; + int objectType = object->pid >> 24; + 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 >> 24; + if (scriptAdd(sidPtr, scriptType) == -1) { + return -1; + } + + Script* script; + if (scriptGetScript(*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 = object->tile | ((object->elevation << 29) & 0xE0000000); + script->sp.radius = 3; + } + + if (object->id == -1) { + object->id = scriptsNewObjectId(); + } + + 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 (scriptAdd(&sid, scriptType) == -1) { + return -1; + } + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return -1; + } + + script->field_14 = a3; + if (scriptType == SCRIPT_TYPE_SPATIAL) { + script->sp.built_tile = ((obj->elevation << 29) & 0xE0000000) | obj->tile; + script->sp.radius = 3; + } + + obj->sid = sid; + + obj->id = scriptsNewObjectId(); + script->field_1C = obj->id; + + script->owner = obj; + + _scr_find_str_run_info(a3 & 0xFFFFFF, &(script->field_50), sid); + + if ((obj->pid >> 24) == 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, displayMonitorAddMessage); +} + +// 0x49AC4C +int _obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string)) +{ + if (critterIsDead(a1)) { + return -1; + } + + if (((a2->fid & 0xF000000) >> 24) == OBJ_TYPE_TILE) { + return -1; + } + + Proto* proto; + if (protoGetProto(a2->pid, &proto) == -1) { + return -1; + } + + bool scriptOverrides = false; + + if (a2->sid != -1) { + scriptSetObjects(a2->sid, a1, a2); + scriptExecProc(a2->sid, SCRIPT_PROC_LOOK_AT); + + Script* script; + if (scriptGetScript(a2->sid, &script) == -1) { + return -1; + } + + scriptOverrides = script->scriptOverrides; + } + + if (!scriptOverrides) { + MessageListItem messageListItem; + + if ((a2->pid >> 24) == OBJ_TYPE_CRITTER && critterIsDead(a2)) { + messageListItem.num = 491 + randomBetween(0, 1); + } else { + messageListItem.num = 490; + } + + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + const char* objectName = objectGetName(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, displayMonitorAddMessage); +} + +// 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 (critterIsDead(critter)) { + return -1; + } + + if ((target->fid & 0xF000000) >> 24 == OBJ_TYPE_TILE) { + return -1; + } + + bool scriptOverrides = false; + if (target->sid != -1) { + scriptSetObjects(target->sid, critter, target); + scriptExecProc(target->sid, SCRIPT_PROC_DESCRIPTION); + + Script* script; + if (scriptGetScript(target->sid, &script) == -1) { + return -1; + } + + scriptOverrides = script->scriptOverrides; + } + + if (!scriptOverrides) { + char* description = objectGetDescription(target); + if (description != NULL && strcmp(description, _proto_none_str) == 0) { + description = NULL; + } + + if (description == NULL || *description == '\0') { + MessageListItem messageListItem; + messageListItem.num = 493; + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + debugPrint("\nError: Can't find msg num!"); + } + fn(messageListItem.text); + } else { + if ((target->pid >> 24) != OBJ_TYPE_CRITTER || !critterIsDead(target)) { + fn(description); + } + } + } + + if (critter == NULL || critter != gDude) { + return 0; + } + + char formattedText[260]; + + int type = target->pid >> 24; + if (type == OBJ_TYPE_CRITTER) { + if (target != gDude && perkGetRank(gDude, PERK_AWARENESS) && !critterIsDead(target)) { + MessageListItem hpMessageListItem; + + if (critterGetBodyType(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 = critterGetItem2(target); + if (item2 != NULL && itemGetType(item2) != ITEM_TYPE_WEAPON) { + item2 = NULL; + } + + if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + if (item2 != NULL) { + MessageListItem weaponMessageListItem; + + if (ammoGetCaliber(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 (!messageListGetItem(&gProtoMessageList, &weaponMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + char format[80]; + sprintf(format, "%s%s", hpMessageListItem.text, weaponMessageListItem.text); + + if (ammoGetCaliber(item2) != 0) { + const int ammoTypePid = weaponGetAmmoTypePid(item2); + const char* ammoName = protoGetName(ammoTypePid); + const int ammoCapacity = ammoGetCapacity(item2); + const int ammoQuantity = ammoGetQuantity(item2); + const char* weaponName = objectGetName(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 = objectGetName(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 (critterIsCrippled(target)) { + endingMessageListItem.num = 544; // , + } else { + endingMessageListItem.num = 545; // . + } + + if (!messageListGetItem(&gProtoMessageList, &endingMessageListItem)) { + debugPrint("\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 (critterIsCrippled(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 || critterIsDead(target)) { + v16 = 0; + } else if (currentHitPoints == maxiumHitPoints) { + v16 = 4; + } else { + v16 = (currentHitPoints * 3) / maxiumHitPoints + 1; + } + + MessageListItem hpMessageListItem; + hpMessageListItem.num = 500 + v16; + if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + if (v16 > 4) { + // Error: lookup_val out of range + hpMessageListItem.num = 550; + if (!messageListGetItem(&gProtoMessageList, &hpMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + debugPrint(hpMessageListItem.text); + return 0; + } + + MessageListItem v66; + if (target == gDude) { + // You look %s + v66.num = 520 + v12; + if (!messageListGetItem(&gProtoMessageList, &v66)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + sprintf(formattedText, v66.text, hpMessageListItem.text); + } else { + // %s %s + v66.num = 521 + v12; + if (!messageListGetItem(&gProtoMessageList, &v66)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + MessageListItem v63; + v63.num = 522 + critterGetStat(target, STAT_GENDER); + if (!messageListGetItem(&gProtoMessageList, &v63)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + sprintf(formattedText, v63.text, hpMessageListItem.text); + } + } + + if (critterIsCrippled(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 == gDude) { + v63.num += 2; + } + + if (!messageListGetItem(&gProtoMessageList, &v63)) { + debugPrint("\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 = gameGetGlobalVar(GVAR_PLAYER_GOT_CAR); + if (car == 0) { + carMessageListItem.num = 548; // The car doesn't look like it's working right now. + } + + if (!messageListGetItem(&gProtoMessageList, &carMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + if (car != 0) { + sprintf(formattedText, carMessageListItem.text, 100 * carGetFuel() / 80000); + } else { + strcpy(formattedText, carMessageListItem.text); + } + + fn(formattedText); + } + } else if (type == OBJ_TYPE_ITEM) { + int itemType = itemGetType(target); + if (itemType == ITEM_TYPE_WEAPON) { + if (ammoGetCaliber(target) != 0) { + MessageListItem weaponMessageListItem; + weaponMessageListItem.num = 526; + + if (!messageListGetItem(&gProtoMessageList, &weaponMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + int ammoTypePid = weaponGetAmmoTypePid(target); + const char* ammoName = protoGetName(ammoTypePid); + int ammoCapacity = ammoGetCapacity(target); + int ammoQuantity = ammoGetQuantity(target); + sprintf(formattedText, weaponMessageListItem.text, ammoQuantity, ammoCapacity, ammoName); + fn(formattedText); + } + } else if (itemType == ITEM_TYPE_AMMO) { + MessageListItem ammoMessageListItem; + ammoMessageListItem.num = 510; + + if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + sprintf(formattedText, + ammoMessageListItem.text, + ammoGetArmorClassModifier(target)); + fn(formattedText); + + ammoMessageListItem.num++; + if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + sprintf(formattedText, + ammoMessageListItem.text, + ammoGetDamageResistanceModifier(target)); + fn(formattedText); + + ammoMessageListItem.num++; + if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) { + debugPrint("\nError: Can't find msg num!"); + exit(1); + } + + sprintf(formattedText, + ammoMessageListItem.text, + ammoGetDamageMultiplier(target), + ammoGetDamageDivisor(target)); + fn(formattedText); + } + } + + return 0; +} + +// 0x49B650 +int _obj_pickup(Object* critter, Object* item) +{ + bool overriden = false; + + if (item->sid != -1) { + scriptSetObjects(item->sid, critter, item); + scriptExecProc(item->sid, SCRIPT_PROC_PICKUP); + + Script* script; + if (scriptGetScript(item->sid, &script) == -1) { + return -1; + } + + overriden = script->scriptOverrides; + } + + if (!overriden) { + int rc; + if (item->pid == PROTO_ID_MONEY) { + int amount = itemGetMoney(item); + if (amount <= 0) { + amount = 1; + } + + rc = itemAttemptAdd(critter, item, amount); + if (rc == 0) { + itemSetMoney(item, 0); + } + } else { + rc = itemAttemptAdd(critter, item, 1); + } + + if (rc == 0) { + Rect rect; + _obj_disconnect(item, &rect); + tileWindowRefreshRect(&rect, item->elevation); + } else { + MessageListItem messageListItem; + // You cannot pick up that item. You are at your maximum weight capacity. + messageListItem.num = 905; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } + + return 0; +} + +// 0x49B73C +int _obj_remove_from_inven(Object* critter, Object* item) +{ + Rect updatedRect; + int fid; + int v11 = 0; + if (critterGetItem2(critter) == item) { + if (critter != gDude || interfaceGetCurrentHand()) { + fid = buildFid(1, critter->fid & 0xFFF, (critter->fid & 0xFF0000) >> 16, 0, critter->rotation); + objectSetFid(critter, fid, &updatedRect); + v11 = 2; + } else { + v11 = 1; + } + } else if (critterGetItem1(critter) == item) { + if (critter == gDude && !interfaceGetCurrentHand()) { + fid = buildFid(1, critter->fid & 0xFFF, (critter->fid & 0xFF0000) >> 16, 0, critter->rotation); + objectSetFid(critter, fid, &updatedRect); + v11 = 2; + } else { + v11 = 1; + } + } else if (critterGetArmor(critter) == item) { + if (critter == gDude) { + int v5 = 1; + + Proto* proto; + if (protoGetProto(0x1000000, &proto) != -1) { + v5 = proto->fid; + } + + fid = buildFid(1, v5, (critter->fid & 0xFF0000) >> 16, (critter->fid & 0xF000) >> 12, critter->rotation); + objectSetFid(critter, fid, &updatedRect); + v11 = 3; + } + } + + int rc = itemRemove(critter, item, 1); + + if (v11 >= 2) { + tileWindowRefreshRect(&updatedRect, critter->elevation); + } + + if (v11 <= 2 && critter == gDude) { + interfaceUpdateItems(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) { + scriptSetObjects(a1->sid, a2, NULL); + scriptExecProc(a1->sid, SCRIPT_PROC_IS_DROPPING); + + Script* scr; + if (scriptGetScript(a1->sid, &scr) == -1) { + return -1; + } + + scriptOverrides = scr->scriptOverrides; + } + + if (scriptOverrides) { + return 0; + } + + if (a2->sid != -1) { + scriptSetObjects(a2->sid, a1, a2); + scriptExecProc(a2->sid, SCRIPT_PROC_DROP); + + Script* scr; + if (scriptGetScript(a2->sid, &scr) == -1) { + return -1; + } + + scriptOverrides = scr->scriptOverrides; + } + + if (scriptOverrides) { + return 0; + } + + if (_obj_remove_from_inven(a1, a2) == 0) { + Object* owner = objectGetOwner(a1); + if (owner == NULL) { + owner = a1; + } + + Rect updatedRect; + _obj_connect(a2, owner->tile, owner->elevation, &updatedRect); + tileWindowRefreshRect(&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; + } + + queueRemoveEvents(obj); + + Rect rect; + objectDestroy(obj, &rect); + + if (owner == NULL) { + tileWindowRefreshRect(&rect, elev); + } + + return 0; +} + +// Read a book. +// +// 0x49B9F0 +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 (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return 0; + } + + int increase = (100 - skillGetValue(gDude, skill)) / 10; + if (increase <= 0) { + messageId = 801; + } else { + if (perkGetRank(gDude, PERK_COMPREHENSION)) { + increase = 150 * increase / 100; + } + + for (int i = 0; i < increase; i++) { + skillAddForce(gDude, skill); + } + } + + paletteFadeTo(gPaletteBlack); + + int intelligence = critterGetStat(gDude, STAT_INTELLIGENCE); + gameTimeAddSeconds(3600 * (11 - intelligence)); + + scriptsExecMapUpdateProc(); + + paletteFadeTo(_cmap); + + // You read the book. + messageListItem.num = 800; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + messageListItem.num = messageId; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return 1; +} + +// Light a flare. +// +// 0x49BBA8 +int _obj_use_flare(Object* critter_obj, Object* flare) +{ + MessageListItem messageListItem; + + if (flare->pid != PROTO_ID_FLARE) { + return -1; + } + + if ((flare->flags & OBJECT_FLAG_0x2000) != 0) { + if (critter_obj == gDude) { + // The flare is already lit. + messageListItem.num = 588; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + } else { + if (critter_obj == gDude) { + // You light the flare. + messageListItem.num = 588; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + + flare->pid = PROTO_ID_LIT_FLARE; + + objectSetLight(flare, 8, 0x10000, NULL); + queueAddEvent(72000, flare, NULL, EVENT_TYPE_FLARE); + } + + return 0; +} + +// 0x49BC60 +int _obj_use_radio(Object* item) +{ + Script* scr; + + if (item->sid == -1) { + return -1; + } + + scriptSetObjects(item->sid, gDude, item); + scriptExecProc(item->sid, SCRIPT_PROC_USE); + + if (scriptGetScript(item->sid, &scr) == -1) { + return -1; + } + + return 0; +} + +// 0x49BCB4 +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_FLAG_0x2000) != 0) { + // The timer is already ticking. + messageListItem.num = 590; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } else { + int seconds = _inven_set_timer(explosive); + if (seconds != -1) { + // You set the timer. + messageListItem.num = 589; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(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(gDude, PERK_DEMOLITION_EXPERT)) { + roll = ROLL_SUCCESS; + } else { + roll = skillRoll(gDude, 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; + } + + queueAddEvent(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) +{ + MessageListItem messageListItem; + int messageNum; + + static_assert(sizeof(messageListItem) == sizeof(stru_49A990), "wrong size"); + memcpy(&messageListItem, &stru_49A990, sizeof(messageListItem)); + + 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 (carGetFuel() < CAR_FUEL_MAX) { + int energy = ammoGetQuantity(item) * energyDensity; + int capacity = ammoGetCapacity(item); + + // NOTE: that function will never return -1 + if (carAddFuel(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(&gProtoMessageList, &messageListItem, messageNum); + displayMonitorAddMessage(text); + + return 1; +} + +// 0x49BE88 +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; + } + + scriptSetObjects(item->sid, gDude, item); + scriptExecProc(item->sid, SCRIPT_PROC_USE); + + Script* scr; + if (scriptGetScript(item->sid, &scr) == -1) { + return -1; + } + + return 1; + } + + return -1; +} + +// 0x49BF38 +int _protinst_use_item(Object* critter, Object* item) +{ + int rc; + MessageListItem messageListItem; + + switch (itemGetType(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 (miscItemIsConsumable(item)) { + rc = _item_m_use_charged_item(critter, item); + if (rc == 0) { + break; + } + } + // FALLTHROUGH + default: + // That does nothing + messageListItem.num = 582; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + rc = -1; + } + + return rc; +} + +// 0x49BFE8 +int _protinstTestDroppedExplosive(Object* a1) +{ + if (a1->pid == PROTO_ID_DYNAMITE_II || a1->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) { + Attack attack; + attackInit(&attack, gDude, 0, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); + attack.attackerFlags = DAM_HIT; + attack.tile = gDude->tile; + _compute_explosion_on_extras(&attack, 0, 0, 1); + + int team = gDude->data.critter.combat.team; + Object* v2 = NULL; + for (int index = 0; index < attack.extrasLength; index++) { + Object* v5 = attack.extras[index]; + if (v5 != gDude + && v5->data.critter.combat.team != team + && statRoll(v5, STAT_PERCEPTION, 0, NULL) >= 2) { + _critter_set_who_hit_me(v5, gDude); + if (v2 == NULL) { + v2 = v5; + } + } + } + + if (v2 != NULL && !isInCombat()) { + STRUCT_664980 attack; + attack.attacker = v2; + attack.defender = gDude; + attack.actionPointsBonus = 0; + attack.accuracyBonus = 0; + attack.minDamage = 0; + attack.maxDamage = 99999; + attack.field_1C = 0; + scriptsRequestCombat(&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 = objectGetOwner(a2); + if (root != NULL) { + int flags = a2->flags & OBJECT_IN_ANY_HAND; + itemRemove(root, a2, 1); + Object* v8 = _item_replace(root, a2, flags); + if (root == gDude) { + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&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; + } + } + interfaceUpdateItems(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); + tileWindowRefreshRect(&updatedRect, root->elevation); + _protinstTestDroppedExplosive(a2); + } + + rc = 0; + } + + scriptsExecMapUpdateProc(); + + return rc; +} + +// 0x49C240 +int _protinst_default_use_item(Object* a1, Object* a2, Object* item) +{ + char formattedText[90]; + MessageListItem messageListItem; + + int rc; + switch (itemGetType(item)) { + case ITEM_TYPE_DRUG: + if ((a2->pid >> 24) != OBJ_TYPE_CRITTER) { + if (a1 == gDude) { + // That does nothing + messageListItem.num = 582; + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if (critterIsDead(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 + randomBetween(0, 3); + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return -1; + } + + rc = _item_d_take_drug(a2, item); + + if (a1 == gDude && a2 != gDude) { + // 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 != gDude); + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + return -1; + } + + sprintf(formattedText, messageListItem.text, objectGetName(item), objectGetName(a2)); + displayMonitorAddMessage(formattedText); + } + + if (a2 == gDude) { + interfaceRenderHitPoints(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 (messageListGetItem(&gProtoMessageList, &messageListItem)) { + sprintf(formattedText, messageListItem.text); + displayMonitorAddMessage(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); + } + + scriptSetObjects(a2->sid, a1, item); + scriptExecProc(a2->sid, SCRIPT_PROC_USE_OBJ_ON); + + if (scriptGetScript(a2->sid, &script) == -1) { + return -1; + } + + if (!script->scriptOverrides) { + return _protinst_default_use_item(a1, a2, item); + } + } else { + scriptSetObjects(item->sid, a1, a2); + scriptExecProc(item->sid, SCRIPT_PROC_USE_OBJ_ON); + + if (scriptGetScript(item->sid, &script) == -1) { + return -1; + } + + if (script->field_28 == 0) { + if (a2->sid == -1) { + return _protinst_default_use_item(a1, a2, item); + } + + scriptSetObjects(a2->sid, a1, item); + scriptExecProc(a2->sid, SCRIPT_PROC_USE_OBJ_ON); + + Script* script; + if (scriptGetScript(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 == gDude) { + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + if (skillUse(a1, a2, skill, criticalChanceModifier) != 0) { + return 0; + } + + if (randomBetween(1, 10) != 1) { + return 0; + } + + MessageListItem messageListItem; + messageListItem.num = messageId; + if (a1 == gDude) { + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(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; + itemRemove(a1, a3, 1); + + Object* v7 = _item_replace(a1, a3, flags); + + int leftItemAction; + int rightItemAction; + if (a1 == gDude) { + interfaceGetItemActions(&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; + } + } + + interfaceUpdateItems(false, leftItemAction, rightItemAction); + } + + _obj_destroy(a3); + + rc = 0; + } + + scriptsExecMapUpdateProc(); + + 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 == gDude) { + interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); + } + + return 0; + } + + MessageListItem messageListItem; + // You don't have enough action points. + messageListItem.num = 700; + + if (obj == gDude) { + if (messageListGetItem(&gProtoMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + + return -1; +} + +// 0x49C740 +int _obj_use(Object* a1, Object* a2) +{ + int type = (a2->fid & 0xF000000) >> 24; + if (a1 == gDude) { + if (type != OBJ_TYPE_SCENERY) { + return -1; + } + } else { + if (type != OBJ_TYPE_SCENERY) { + return 0; + } + } + + Proto* sceneryProto; + if (protoGetProto(a2->pid, &sceneryProto) == -1) { + return -1; + } + + if ((a2->pid >> 24) == OBJ_TYPE_SCENERY && sceneryProto->scenery.type == SCENERY_TYPE_DOOR) { + return _obj_use_door(a1, a2, 0); + } + + bool scriptOverrides = false; + + if (a2->sid != -1) { + scriptSetObjects(a2->sid, a1, a2); + scriptExecProc(a2->sid, SCRIPT_PROC_USE); + + Script* script; + if (scriptGetScript(a2->sid, &script) == -1) { + return -1; + } + + scriptOverrides = script->scriptOverrides; + } + + if (!scriptOverrides) { + if ((a2->pid >> 24) == OBJ_TYPE_SCENERY) { + if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_DOWN) { + if (useLadderDown(a1, a2, 0) == 0) { + scriptOverrides = true; + } + } else if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_UP) { + if (useLadderUp(a1, a2, 0) == 0) { + scriptOverrides = true; + } + } else if (sceneryProto->scenery.type == SCENERY_TYPE_STAIRS) { + if (useStairs(a1, a2, 0) == 0) { + scriptOverrides = true; + } + } + } + } + + if (!scriptOverrides) { + if (a1 == gDude) { + // You see: %s + MessageListItem messageListItem; + messageListItem.num = 480; + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + return -1; + } + + char formattedText[260]; + const char* name = objectGetName(a2); + sprintf(formattedText, messageListItem.text, name); + displayMonitorAddMessage(formattedText); + } + } + + scriptsExecMapUpdateProc(); + + return 0; +} + +// 0x49C900 +int useLadderDown(Object* a1, Object* ladder, int a3) +{ + int builtTile = ladder->data.scenery.ladder.field_4; + if (builtTile == -1) { + return -1; + } + + int tile = builtTile & 0x3FFFFFF; + int elevation = (builtTile & 0xE0000000) >> 29; + if (ladder->data.scenery.ladder.field_0 != 0) { + MapTransition transition; + memset(&transition, 0, sizeof(transition)); + + transition.map = ladder->data.scenery.ladder.field_0; + transition.elevation = elevation; + transition.tile = tile; + transition.rotation = (builtTile & 0x1C000000) >> 26; + + mapSetTransition(&transition); + + _wmMapMarkMapEntranceState(transition.map, elevation, 1); + } else { + Rect updatedRect; + if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) { + return -1; + } + + tileWindowRefreshRect(&updatedRect, gElevation); + } + + return 0; +} + +// 0x49C9A4 +int useLadderUp(Object* a1, Object* ladder, int a3) +{ + int builtTile = ladder->data.scenery.ladder.field_4; + if (builtTile == -1) { + return -1; + } + + int tile = builtTile & 0x3FFFFFF; + int elevation = (builtTile & 0xE0000000) >> 29; + if (ladder->data.scenery.ladder.field_0 != 0) { + MapTransition transition; + memset(&transition, 0, sizeof(transition)); + + transition.map = ladder->data.scenery.ladder.field_0; + transition.elevation = elevation; + transition.tile = tile; + transition.rotation = (builtTile & 0x1C000000) >> 26; + + mapSetTransition(&transition); + + _wmMapMarkMapEntranceState(transition.map, elevation, 1); + } else { + Rect updatedRect; + if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) { + return -1; + } + + tileWindowRefreshRect(&updatedRect, gElevation); + } + + return 0; +} + +// 0x49CA48 +int useStairs(Object* a1, Object* stairs, int a3) +{ + int builtTile = stairs->data.scenery.stairs.field_4; + if (builtTile == -1) { + return -1; + } + + int tile = builtTile & 0x3FFFFFF; + int elevation = (builtTile & 0xE0000000) >> 29; + if (stairs->data.scenery.stairs.field_0 > 0) { + MapTransition transition; + memset(&transition, 0, sizeof(transition)); + + transition.map = stairs->data.scenery.stairs.field_0; + transition.elevation = elevation; + transition.tile = tile; + transition.rotation = (builtTile & 0x1C000000) >> 26; + + mapSetTransition(&transition); + + _wmMapMarkMapEntranceState(transition.map, elevation, 1); + } else { + Rect updatedRect; + if (objectSetLocation(a1, tile, elevation, &updatedRect) == -1) { + return -1; + } + + tileWindowRefreshRect(&updatedRect, gElevation); + } + + return 0; +} + +// 0x49CAF4 +int _set_door_state_open(Object* a1, Object* a2) +{ + a1->data.scenery.door.openFlags |= 0x01; + return 0; +} + +// 0x49CB04 +int _set_door_state_closed(Object* a1, Object* a2) +{ + a1->data.scenery.door.openFlags &= ~0x01; + return 0; +} + +// 0x49CB14 +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(); + tileWindowRefresh(); + + if (a1->frame == 0) { + return 0; + } + + CacheEntry* artHandle; + Art* art = artLock(a1->fid, &artHandle); + if (art == NULL) { + return -1; + } + + Rect dirty; + Rect temp; + + objectGetRect(a1, &dirty); + + for (int frame = a1->frame - 1; frame >= 0; frame--) { + int x; + int y; + artGetFrameOffsets(art, frame, a1->rotation, &x, &y); + _obj_offset(a1, -x, -y, &temp); + } + + objectSetFrame(a1, 0, &temp); + rectUnion(&dirty, &temp, &dirty); + + tileWindowRefreshRect(&dirty, gElevation); + + artUnlock(artHandle); + return 0; + } else { + a1->flags |= OBJECT_OPEN_DOOR; + + _obj_rebuild_all_light(); + tileWindowRefresh(); + + CacheEntry* artHandle; + Art* art = artLock(a1->fid, &artHandle); + if (art == NULL) { + return -1; + } + + int frameCount = artGetFrameCount(art); + if (a1->frame == frameCount - 1) { + artUnlock(artHandle); + return 0; + } + + Rect dirty; + Rect temp; + + objectGetRect(a1, &dirty); + + for (int frame = a1->frame + 1; frame < frameCount; frame++) { + int x; + int y; + artGetFrameOffsets(art, frame, a1->rotation, &x, &y); + _obj_offset(a1, x, y, &temp); + } + + objectSetFrame(a1, frameCount - 1, &temp); + rectUnion(&dirty, &temp, &dirty); + + tileWindowRefreshRect(&dirty, gElevation); + + artUnlock(artHandle); + return 0; + } +} + +// 0x49CCB8 +int _obj_use_door(Object* a1, Object* a2, int a3) +{ + if (objectIsLocked(a2)) { + const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_LOCKED); + soundPlayFile(sfx); + } + + bool scriptOverrides = false; + if (a2->sid != -1) { + scriptSetObjects(a2->sid, a1, a2); + scriptExecProc(a2->sid, SCRIPT_PROC_USE); + + Script* script; + if (scriptGetScript(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(&gProtoMessageList, &messageListItem, 597); + displayMonitorAddMessage(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; + } + + reg_anim_begin(2); + + for (int i = start; i != end; i += step) { + if (i != 0) { + if (a3 == 0) { + reg_anim_11_0(a2, a2, _set_door_state_closed, -1); + } + + const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_CLOSED); + reg_anim_play_sfx(a2, sfx, -1); + + reg_anim_animate_reverse(a2, 0, 0); + } else { + if (a3 == 0) { + reg_anim_11_0(a2, a2, _set_door_state_open, -1); + } + + const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_CLOSED); + reg_anim_play_sfx(a2, sfx, -1); + + reg_anim_animate(a2, 0, 0); + } + } + + reg_anim_11_1(a2, a2, _check_door_state, -1); + + reg_anim_end(); + } + + return 0; +} + +// 0x49CE7C +int _obj_use_container(Object* critter, Object* item) +{ + if (((item->fid & 0xF000000) >> 24) != OBJ_TYPE_ITEM) { + return -1; + } + + Proto* itemProto; + if (protoGetProto(item->pid, &itemProto) == -1) { + return -1; + } + + if (itemProto->item.type != ITEM_TYPE_CONTAINER) { + return -1; + } + + if (objectIsLocked(item)) { + const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_LOCKED); + soundPlayFile(sfx); + + if (critter == gDude) { + MessageListItem messageListItem; + // It is locked. + messageListItem.num = 487; + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + return -1; + } + + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + } + + bool overriden = false; + if (item->sid != -1) { + scriptSetObjects(item->sid, critter, item); + scriptExecProc(item->sid, SCRIPT_PROC_USE); + + Script* script; + if (scriptGetScript(item->sid, &script) == -1) { + return -1; + } + + overriden = script->scriptOverrides; + } + + if (overriden) { + return -1; + } + + reg_anim_begin(2); + + if (item->frame == 0) { + const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_OPEN); + reg_anim_play_sfx(item, sfx, 0); + reg_anim_animate(item, 0, 0); + } else { + const char* sfx = sfxBuildOpenName(item, SCENERY_SOUND_EFFECT_CLOSED); + reg_anim_play_sfx(item, sfx, 0); + reg_anim_animate_reverse(item, 0, 0); + } + + reg_anim_end(); + + if (critter == gDude) { + MessageListItem messageListItem; + messageListItem.num = item->frame != 0 + ? 486 // You search the %s. + : 485; // You close the %s. + if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { + return -1; + } + + char formattedText[260]; + const char* objectName = objectGetName(item); + sprintf(formattedText, messageListItem.text, objectName); + displayMonitorAddMessage(formattedText); + } + + return 0; +} + +// 0x49D078 +int _obj_use_skill_on(Object* source, Object* target, int skill) +{ + if (objectIsJammed(target)) { + if (source == gDude) { + MessageListItem messageListItem; + messageListItem.num = 2001; + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + } + return -1; + } + + Proto* proto; + if (protoGetProto(target->pid, &proto) == -1) { + return -1; + } + + bool scriptOverrides = false; + if (target->sid != -1) { + scriptSetObjects(target->sid, source, target); + scriptSetActionBeingUsed(target->sid, skill); + scriptExecProc(target->sid, SCRIPT_PROC_USE_SKILL_ON); + + Script* script; + if (scriptGetScript(target->sid, &script) == -1) { + return -1; + } + + scriptOverrides = script->scriptOverrides; + } + + if (!scriptOverrides) { + skillUse(source, target, skill, 0); + } + + return 0; +} + +// 0x49D178 +bool _obj_is_lockable(Object* obj) +{ + Proto* proto; + + if (obj == NULL) { + return false; + } + + if (protoGetProto(obj->pid, &proto) == -1) { + return false; + } + + switch (obj->pid >> 24) { + 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 objectIsLocked(Object* obj) +{ + if (obj == NULL) { + return false; + } + + ObjectData* data = &(obj->data); + switch (obj->pid >> 24) { + 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 objectLock(Object* object) +{ + if (object == NULL) { + return -1; + } + + switch (object->pid >> 24) { + 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 objectUnlock(Object* object) +{ + if (object == NULL) { + return -1; + } + + switch (object->pid >> 24) { + 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 (protoGetProto(obj->pid, &proto) == -1) { + return false; + } + + switch (obj->pid >> 24) { + 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 objectIsOpen(Object* object) +{ + return object->frame != 0; +} + +// 0x49D2F4 +int objectOpenClose(Object* obj) +{ + if (obj == NULL) { + return -1; + } + + if (!_obj_is_openable(obj)) { + return -1; + } + + if (objectIsLocked(obj)) { + return -1; + } + + objectUnjamLock(obj); + + reg_anim_begin(2); + + if (obj->frame != 0) { + reg_anim_11_1(obj, obj, _set_door_state_closed, -1); + + const char* sfx = sfxBuildOpenName(obj, SCENERY_SOUND_EFFECT_CLOSED); + reg_anim_play_sfx(obj, sfx, -1); + + reg_anim_animate_reverse(obj, 0, 0); + } else { + reg_anim_11_1(obj, obj, _set_door_state_open, -1); + + const char* sfx = sfxBuildOpenName(obj, SCENERY_SOUND_EFFECT_OPEN); + reg_anim_play_sfx(obj, sfx, -1); + reg_anim_animate(obj, 0, 0); + } + + reg_anim_11_1(obj, obj, _check_door_state, -1); + + reg_anim_end(); + + return 0; +} + +// 0x49D3D8 +int objectOpen(Object* obj) +{ + if (obj->frame == 0) { + objectOpenClose(obj); + } + + return 0; +} + +// 0x49D3F4 +int objectClose(Object* obj) +{ + if (obj->frame != 0) { + objectOpenClose(obj); + } + + return 0; +} + +// 0x49D410 +bool objectIsJammed(Object* obj) +{ + if (!_obj_is_lockable(obj)) { + return false; + } + + if ((obj->pid >> 24) == 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 objectJamLock(Object* obj) +{ + if (!_obj_is_lockable(obj)) { + return -1; + } + + ObjectData* data = &(obj->data); + switch (obj->pid >> 24) { + 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 objectUnjamLock(Object* obj) +{ + if (!_obj_is_lockable(obj)) { + return -1; + } + + ObjectData* data = &(obj->data); + switch (obj->pid >> 24) { + 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 objectUnjamAll() +{ + Object* obj = objectFindFirst(); + while (obj != NULL) { + objectUnjamLock(obj); + obj = objectFindNext(); + } + + 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 = tileGetTileInDirection(tile, rotation, v6); + if (_obj_blocking_at(NULL, newTile, elevation) == NULL && v6 > 1 && _make_path(gDude, gDude->tile, newTile, NULL, 0) != 0) { + break; + } + } + + v6++; + } + + if (a4 != 1 && v6 > a4 + 2) { + for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { + int candidate = tileGetTileInDirection(tile, rotation, 1); + if (_obj_blocking_at(NULL, candidate, elevation) == NULL) { + newTile = candidate; + break; + } + } + } + } + + Rect updatedRect; + objectShow(obj, &updatedRect); + + Rect temp; + if (objectSetLocation(obj, newTile, elevation, &temp) != -1) { + rectUnion(&updatedRect, &temp, &updatedRect); + + if (elevation == gElevation) { + tileWindowRefreshRect(&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 = gDude->tile; + for (int v4 = 1; v4 <= 100; v4++) { + // TODO: Check. + v7++; + v9 = tileGetTileInDirection(v9, v7 % 6, 1); + if (_wmEvalTileNumForPlacement(v9) != 0) { + break; + } + + if (tileDistanceBetween(gDude->tile, v9) > 8) { + v9 = tile; + break; + } + } + } + + objectShow(obj, NULL); + objectSetLocation(obj, v9, elevation, NULL); + + return 0; +} diff --git a/src/proto_instance.h b/src/proto_instance.h new file mode 100644 index 0000000..1e93aa0 --- /dev/null +++ b/src/proto_instance.h @@ -0,0 +1,61 @@ +#ifndef PROTOTYPE_INSTANCES_H +#define PROTOTYPE_INSTANCES_H + +#include "message.h" +#include "obj_types.h" + +#include + +extern MessageListItem stru_49A990; + +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_book(Object* item_obj); +int _obj_use_flare(Object* critter_obj, Object* item_obj); +int _obj_use_radio(Object* item_obj); +int _obj_use_explosive(Object* explosive); +int _obj_use_power_on_car(Object* ammo); +int _obj_use_misc_item(Object* item_obj); +int _protinst_use_item(Object* a1, Object* a2); +int _protinstTestDroppedExplosive(Object* a1); +int _obj_use_item(Object* a1, Object* a2); +int _protinst_default_use_item(Object* a1, Object* a2, Object* item); +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 useLadderDown(Object* a1, Object* ladder, int a3); +int useLadderUp(Object* a1, Object* ladder, int a3); +int useStairs(Object* a1, Object* stairs, int a3); +int _set_door_state_open(Object* a1, Object* a2); +int _set_door_state_closed(Object* a1, Object* a2); +int _check_door_state(Object* a1, Object* a2); +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_lockable(Object* obj); +bool objectIsLocked(Object* obj); +int objectLock(Object* obj); +int objectUnlock(Object* obj); +bool _obj_is_openable(Object* obj); +int objectIsOpen(Object* obj); +int objectOpenClose(Object* obj); +int objectOpen(Object* obj); +int objectClose(Object* obj); +bool objectIsJammed(Object* obj); +int objectJamLock(Object* obj); +int objectUnjamLock(Object* obj); +int objectUnjamAll(); +int _obj_attempt_placement(Object* obj, int tile, int elevation, int a4); +int _objPMAttemptPlacement(Object* obj, int tile, int elevation); + +#endif /* PROTOTYPE_INSTANCES_H */ diff --git a/src/proto_types.h b/src/proto_types.h new file mode 100644 index 0000000..16f0d22 --- /dev/null +++ b/src/proto_types.h @@ -0,0 +1,484 @@ +#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 field_0; // d.lower_tile + int field_4; // d.upper_tile +} 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 */ diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..e027163 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,549 @@ +#include "queue.h" + +#include "actions.h" +#include "critter.h" +#include "display_monitor.h" +#include "game.h" +#include "game_sound.h" +#include "item.h" +#include "map.h" +#include "memory.h" +#include "message.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "proto_instance.h" +#include "scripts.h" + +// Last queue list node found during [queueFindFirstEvent] and +// [queueFindNextEvent] calls. +// +// 0x51C690 +QueueListNode* gLastFoundQueueListNode = NULL; + +// 0x6648C0 +QueueListNode* gQueueListHead; + +// 0x51C540 +EventTypeDescription gEventTypeDescriptions[EVENT_TYPE_COUNT] = { + { drugEffectEventProcess, internal_free, drugEffectEventRead, drugEffectEventWrite, true, _item_d_clear }, + { knockoutEventProcess, NULL, NULL, NULL, true, _critter_wake_clear }, + { withdrawalEventProcess, internal_free, withdrawalEventRead, withdrawalEventWrite, true, _item_wd_clear }, + { scriptEventProcess, internal_free, scriptEventRead, scriptEventWrite, true, NULL }, + { gameTimeEventProcess, NULL, NULL, NULL, true, NULL }, + { poisonEventProcess, NULL, NULL, NULL, false, NULL }, + { radiationEventProcess, internal_free, radiationEventRead, radiationEventWrite, false, NULL }, + { flareEventProcess, NULL, NULL, NULL, true, flareEventProcess }, + { explosionEventProcess, NULL, NULL, NULL, true, _queue_explode_exit }, + { miscItemTrickleEventProcess, NULL, NULL, NULL, true, _item_m_turn_off_from_queue }, + { sneakEventProcess, NULL, NULL, NULL, true, _critter_sneak_clear }, + { explosionFailureEventProcess, NULL, NULL, NULL, true, _queue_explode_exit }, + { mapUpdateEventProcess, NULL, NULL, NULL, true, NULL }, + { ambientSoundEffectEventProcess, internal_free, NULL, NULL, true, NULL }, +}; + +// 0x4A2320 +void queueInit() +{ + gQueueListHead = NULL; +} + +// 0x4A2330 +int queueExit() +{ + queueClear(); + return 0; +} + +// 0x4A2338 +int queueLoad(File* stream) +{ + int count; + if (fileReadInt32(stream, &count) == -1) { + return -1; + } + + QueueListNode* oldListHead = gQueueListHead; + gQueueListHead = NULL; + + QueueListNode** nextPtr = &gQueueListHead; + + int rc = 0; + for (int index = 0; index < count; index += 1) { + QueueListNode* queueListNode = internal_malloc(sizeof(*queueListNode)); + if (queueListNode == NULL) { + rc = -1; + break; + } + + if (fileReadInt32(stream, &(queueListNode->time)) == -1) { + internal_free(queueListNode); + rc = -1; + break; + } + + if (fileReadInt32(stream, &(queueListNode->type)) == -1) { + internal_free(queueListNode); + rc = -1; + break; + } + + int objectId; + if (fileReadInt32(stream, &objectId) == -1) { + internal_free(queueListNode); + rc = -1; + break; + } + + Object* obj; + if (objectId == -2) { + obj = NULL; + } else { + obj = objectFindFirst(); + while (obj != NULL) { + obj = _inven_find_id(obj, objectId); + if (obj != NULL) { + break; + } + obj = objectFindNext(); + } + } + + queueListNode->owner = obj; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]); + if (eventTypeDescription->readProc != NULL) { + if (eventTypeDescription->readProc(stream, &(queueListNode->data)) == -1) { + internal_free(queueListNode); + rc = -1; + break; + } + } else { + queueListNode->data = NULL; + } + + queueListNode->next = NULL; + + *nextPtr = queueListNode; + nextPtr = &(queueListNode->next); + } + + if (rc == -1) { + while (gQueueListHead != NULL) { + QueueListNode* next = gQueueListHead->next; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[gQueueListHead->type]); + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(gQueueListHead->data); + } + + internal_free(gQueueListHead); + + gQueueListHead = next; + } + } + + if (oldListHead != NULL) { + QueueListNode** v13 = &gQueueListHead; + 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 queueSave(File* stream) +{ + QueueListNode* queueListNode; + + int count = 0; + + queueListNode = gQueueListHead; + while (queueListNode != NULL) { + count += 1; + queueListNode = queueListNode->next; + } + + if (fileWriteInt32(stream, count) == -1) { + return -1; + } + + queueListNode = gQueueListHead; + while (queueListNode != NULL) { + Object* object = queueListNode->owner; + int objectId = object != NULL ? object->id : -2; + + if (fileWriteInt32(stream, queueListNode->time) == -1) { + return -1; + } + + if (fileWriteInt32(stream, queueListNode->type) == -1) { + return -1; + } + + if (fileWriteInt32(stream, objectId) == -1) { + return -1; + } + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]); + if (eventTypeDescription->writeProc != NULL) { + if (eventTypeDescription->writeProc(stream, queueListNode->data) == -1) { + return -1; + } + } + + queueListNode = queueListNode->next; + } + + return 0; +} + +// 0x4A258C +int queueAddEvent(int delay, Object* obj, void* data, int eventType) +{ + QueueListNode* newQueueListNode = internal_malloc(sizeof(QueueListNode)); + if (newQueueListNode == NULL) { + return -1; + } + + int v1 = gameTimeGetTime(); + int v2 = v1 + delay; + newQueueListNode->time = v2; + newQueueListNode->type = eventType; + newQueueListNode->owner = obj; + newQueueListNode->data = data; + + if (obj != NULL) { + obj->flags |= OBJECT_FLAG_0x2000; + } + + QueueListNode** v3 = &gQueueListHead; + + if (gQueueListHead != 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 queueRemoveEvents(Object* owner) +{ + QueueListNode* queueListNode = gQueueListHead; + QueueListNode** queueListNodePtr = &gQueueListHead; + + while (queueListNode) { + if (queueListNode->owner == owner) { + QueueListNode* temp = queueListNode; + + queueListNode = queueListNode->next; + *queueListNodePtr = queueListNode; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[temp->type]); + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(temp->data); + } + + internal_free(temp); + } else { + queueListNodePtr = &(queueListNode->next); + queueListNode = queueListNode->next; + } + } + + return 0; +} + +// 0x4A264C +int queueRemoveEventsByType(Object* owner, int eventType) +{ + QueueListNode* queueListNode = gQueueListHead; + QueueListNode** queueListNodePtr = &gQueueListHead; + + while (queueListNode) { + if (queueListNode->owner == owner && queueListNode->type == eventType) { + QueueListNode* temp = queueListNode; + + queueListNode = queueListNode->next; + *queueListNodePtr = queueListNode; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[temp->type]); + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(temp->data); + } + + internal_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 queueHasEvent(Object* owner, int eventType) +{ + QueueListNode* queueListEvent = gQueueListHead; + while (queueListEvent != NULL) { + if (owner == queueListEvent->owner && eventType == queueListEvent->type) { + return true; + } + + queueListEvent = queueListEvent->next; + } + + return false; +} + +// 0x4A26D0 +int queueProcessEvents() +{ + int time = gameTimeGetTime(); + int v1 = 0; + + while (gQueueListHead != NULL) { + QueueListNode* queueListNode = gQueueListHead; + if (time < queueListNode->time || v1 != 0) { + break; + } + + gQueueListHead = queueListNode->next; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]); + v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data); + + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(queueListNode->data); + } + + internal_free(queueListNode); + } + + return v1; +} + +// 0x4A2748 +void queueClear() +{ + QueueListNode* queueListNode = gQueueListHead; + while (queueListNode != NULL) { + QueueListNode* next = queueListNode->next; + + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]); + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(queueListNode->data); + } + + internal_free(queueListNode); + + queueListNode = next; + } + + gQueueListHead = NULL; +} + +// 0x4A2790 +void _queue_clear_type(int eventType, QueueEventHandler* fn) +{ + QueueListNode** ptr = &gQueueListHead; + 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 = &(gEventTypeDescriptions[tmp->type]); + if (eventTypeDescription->freeProc != NULL) { + eventTypeDescription->freeProc(tmp->data); + } + + internal_free(tmp); + } + } else { + ptr = &(curr->next); + curr = *ptr; + } + } +} + +// TODO: Make unsigned. +// +// 0x4A2808 +int queueGetNextEventTime() +{ + if (gQueueListHead == NULL) { + return 0; + } + + return gQueueListHead->time; +} + +// 0x4A281C +int flareEventProcess(Object* obj, void* data) +{ + _obj_destroy(obj); + return 1; +} + +// 0x4A2828 +int explosionEventProcess(Object* obj, void* data) +{ + return _queue_do_explosion_(obj, true); +} + +// 0x4A2830 +int _queue_explode_exit(Object* obj, void* data) +{ + return _queue_do_explosion_(obj, false); +} + +// 0x4A2834 +int _queue_do_explosion_(Object* explosive, bool a2) +{ + int tile; + int elevation; + + Object* owner = objectGetOwner(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 (gDude != NULL) { + if (perkHasRank(gDude, PERK_DEMOLITION_EXPERT)) { + maxDamage += 10; + minDamage += 10; + } + } + + if (actionExplode(tile, elevation, minDamage, maxDamage, gDude, a2) == -2) { + queueAddEvent(50, explosive, NULL, EVENT_TYPE_EXPLOSION); + } else { + _obj_destroy(explosive); + } + + return 1; +} + +// 0x4A28E4 +int explosionFailureEventProcess(Object* obj, void* data) +{ + MessageListItem msg; + + // Due to your inept handling, the explosive detonates prematurely. + msg.num = 4000; + if (messageListGetItem(&gMiscMessageList, &msg)) { + displayMonitorAddMessage(msg.text); + } + + return _queue_do_explosion_(obj, true); +} + +// 0x4A2920 +void _queue_leaving_map() +{ + for (int eventType = 0; eventType < EVENT_TYPE_COUNT; eventType++) { + EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[eventType]); + if (eventTypeDescription->field_10) { + _queue_clear_type(eventType, eventTypeDescription->field_14); + } + } +} + +// 0x4A294C +bool queueIsEmpty() +{ + return gQueueListHead == NULL; +} + +// 0x4A295C +void* queueFindFirstEvent(Object* owner, int eventType) +{ + QueueListNode* queueListNode = gQueueListHead; + while (queueListNode != NULL) { + if (owner == queueListNode->owner && eventType == queueListNode->type) { + gLastFoundQueueListNode = queueListNode; + return queueListNode->data; + } + queueListNode = queueListNode->next; + } + + gLastFoundQueueListNode = NULL; + return NULL; +} + +// 0x4A2994 +void* queueFindNextEvent(Object* owner, int eventType) +{ + if (gLastFoundQueueListNode != NULL) { + QueueListNode* queueListNode = gLastFoundQueueListNode->next; + while (queueListNode != NULL) { + if (owner == queueListNode->owner && eventType == queueListNode->type) { + gLastFoundQueueListNode = queueListNode; + return queueListNode->data; + } + queueListNode = queueListNode->next; + } + } + + gLastFoundQueueListNode = NULL; + + return NULL; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..91fd1c4 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,102 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include "db.h" +#include "obj_types.h" + +#include + +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 field_4; +} ScriptEvent; + +typedef struct RadiationEvent { + int radiationLevel; + int isHealing; +} RadiationEvent; + +typedef struct AmbientSoundEffectEvent { + int ambientSoundEffectIndex; +} AmbientSoundEffectEvent; + +typedef struct QueueListNode { + // TODO: Make unsigned. + int time; + int type; + Object* owner; + void* data; + struct QueueListNode* next; +} QueueListNode; + +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 QueueListNode* gLastFoundQueueListNode; +extern QueueListNode* gQueueListHead; +extern EventTypeDescription gEventTypeDescriptions[EVENT_TYPE_COUNT]; + +void queueInit(); +int queueExit(); +int queueLoad(File* stream); +int queueSave(File* stream); +int queueAddEvent(int delay, Object* owner, void* data, int eventType); +int queueRemoveEvents(Object* owner); +int queueRemoveEventsByType(Object* owner, int eventType); +bool queueHasEvent(Object* owner, int eventType); +int queueProcessEvents(); +void queueClear(); +void _queue_clear_type(int eventType, QueueEventHandler* fn); +int queueGetNextEventTime(); +int flareEventProcess(Object* obj, void* data); +int explosionEventProcess(Object* obj, void* data); +int _queue_explode_exit(Object* obj, void* data); +int _queue_do_explosion_(Object* obj, bool a2); +int explosionFailureEventProcess(Object* obj, void* data); +void _queue_leaving_map(); +bool queueIsEmpty(); +void* queueFindFirstEvent(Object* owner, int eventType); +void* queueFindNextEvent(Object* owner, int eventType); + +#endif /* QUEUE_H */ diff --git a/src/random.c b/src/random.c new file mode 100644 index 0000000..6d059af --- /dev/null +++ b/src/random.c @@ -0,0 +1,246 @@ +#include "random.h" + +#include "debug.h" +#include "scripts.h" + +#include +#include + +// clang-format off +#define WIN32_LEAN_AND_MEAN +#include +#include +// clang-format on + +// 0x50D4BA +const double dbl_50D4BA = 36.42; + +// 0x50D4C2 +const double dbl_50D4C2 = 4000; + +// 0x51C694 +int _iy = 0; + +// 0x6648D0 +int _iv[32]; + +// 0x664950 +int _idum; + +// 0x4A2FE0 +void randomInit() +{ + unsigned int randomSeed = randomGetSeed(); + srand(randomSeed); + + int pseudorandomSeed = randomInt32(); + randomSeedPrerandomInternal(pseudorandomSeed); + + randomValidatePrerandom(); +} + +// Note: Collapsed. +// +// 0x4A2FFC +int _roll_reset_() +{ + return 0; +} + +// NOTE: Uncollapsed 0x4A2FFC. +void randomReset() +{ + _roll_reset_(); +} + +// NOTE: Uncollapsed 0x4A2FFC. +void randomExit() +{ + _roll_reset_(); +} + +// NOTE: Uncollapsed 0x4A2FFC. +int randomSave(File* stream) +{ + return _roll_reset_(); +} + +// NOTE: Uncollapsed 0x4A2FFC. +int randomLoad(File* stream) +{ + return _roll_reset_(); +} + +// Rolls d% against [difficulty]. +// +// 0x4A3000 +int randomRoll(int difficulty, int criticalSuccessModifier, int* howMuchPtr) +{ + int delta = difficulty - randomBetween(1, 100); + int result = randomTranslateRoll(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 randomTranslateRoll(int delta, int criticalSuccessModifier) +{ + int gameTime = gameTimeGetTime(); + + int roll; + if (delta < 0) { + roll = ROLL_FAILURE; + + if ((gameTime / GAME_TIME_TICKS_PER_DAY) >= 1) { + // 10% to become critical failure. + if (randomBetween(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 (randomBetween(1, 100) <= delta / 10 + criticalSuccessModifier) { + roll = ROLL_CRITICAL_SUCCESS; + } + } + } + + return roll; +} + +// 0x4A30C0 +int randomBetween(int min, int max) +{ + int result; + + if (min <= max) { + result = min + getRandom(max - min + 1); + } else { + result = max + getRandom(min - max + 1); + } + + if (result < min || result > max) { + debugPrint("Random number %d is not in range %d to %d", result, min, max); + result = min; + } + + return result; +} + +// 0x4A30FC +int getRandom(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; +} + +// 0x4A31A0 +void randomSeedPrerandom(int seed) +{ + if (seed == -1) { + // NOTE: Uninline. + seed = randomInt32(); + } + + randomSeedPrerandomInternal(seed); +} + +// 0x4A31C4 +int randomInt32() +{ + int high = rand() << 16; + int low = rand(); + + return (high + low) & INT_MAX; +} + +// 0x4A31E0 +void randomSeedPrerandomInternal(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 +unsigned int randomGetSeed() +{ + return timeGetTime(); +} + +// 0x4A3264 +void randomValidatePrerandom() +{ + int results[25]; + + for (int index = 0; index < 25; index++) { + results[index] = 0; + } + + for (int attempt = 0; attempt < 100000; attempt++) { + int value = randomBetween(1, 25); + if (value - 1 < 0) { + debugPrint("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] - dbl_50D4C2) * ((double)results[index] - dbl_50D4C2) / dbl_50D4C2; + v1 += v2; + } + + debugPrint("Chi squared is %f, P = %f at 0.05\n", v1, dbl_50D4C2); + + if (v1 < dbl_50D4BA) { + debugPrint("Sequence is random, 95%% confidence.\n"); + } else { + debugPrint("Warning! Sequence is not random, 95%% confidence.\n"); + } +} diff --git a/src/random.h b/src/random.h new file mode 100644 index 0000000..a514ac1 --- /dev/null +++ b/src/random.h @@ -0,0 +1,37 @@ +#ifndef RANDOM_H +#define RANDOM_H + +#include "db.h" + +typedef enum Roll { + ROLL_CRITICAL_FAILURE, + ROLL_FAILURE, + ROLL_SUCCESS, + ROLL_CRITICAL_SUCCESS, +} Roll; + +extern const double dbl_50D4BA; +extern const double dbl_50D4C2; + +extern int _iy; + +extern int _iv[32]; +extern int _idum; + +void randomInit(); +int _roll_reset_(); +void randomReset(); +void randomExit(); +int randomSave(File* stream); +int randomLoad(File* stream); +int randomRoll(int difficulty, int criticalSuccessModifier, int* howMuchPtr); +int randomTranslateRoll(int delta, int criticalSuccessModifier); +int randomBetween(int min, int max); +int getRandom(int max); +void randomSeedPrerandom(int seed); +int randomInt32(); +void randomSeedPrerandomInternal(int seed); +unsigned int randomGetSeed(); +void randomValidatePrerandom(); + +#endif /* RANDOM_H */ diff --git a/src/reaction.c b/src/reaction.c new file mode 100644 index 0000000..169fc46 --- /dev/null +++ b/src/reaction.c @@ -0,0 +1,46 @@ +#include "reaction.h" + +#include "scripts.h" + +// 0x4A29D0 +int reactionSetValue(Object* critter, int value) +{ + scriptSetLocalVar(critter->sid, 0, value); + return 0; +} + +// 0x4A29E8 +int reactionTranslateValue(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; + } +} + +// 0x4A29F0 +int _reaction_influence_() +{ + return 0; +} + +// 0x4A2B28 +int reactionGetValue(Object* critter) +{ + int reactionValue; + + if (scriptGetLocalVar(critter->sid, 0, &reactionValue) == -1) { + return -1; + } + + return reactionValue; +} diff --git a/src/reaction.h b/src/reaction.h new file mode 100644 index 0000000..6dd3961 --- /dev/null +++ b/src/reaction.h @@ -0,0 +1,17 @@ +#ifndef REACTION_H +#define REACTION_H + +#include "obj_types.h" + +typedef enum NpcReaction { + NPC_REACTION_BAD, + NPC_REACTION_NEUTRAL, + NPC_REACTION_GOOD, +} NpcReaction; + +int reactionSetValue(Object* critter, int a2); +int reactionTranslateValue(int a1); +int _reaction_influence_(); +int reactionGetValue(Object* critter); + +#endif /* REACTION_H */ diff --git a/src/region.c b/src/region.c new file mode 100644 index 0000000..e1e789c --- /dev/null +++ b/src/region.c @@ -0,0 +1,193 @@ +#include "region.h" + +#include "debug.h" +#include "memory_manager.h" + +#include +#include + +static_assert(sizeof(Region) == 140, "wrong size"); + +char _aNull[] = ""; + +// Probably recalculates bounding box of the region. +// +// 0x4A2B50 +void _regionSetBound(Region* region) +{ + int v1 = INT_MAX; + int v2 = INT_MIN; + int v3 = INT_MAX; + int v4 = INT_MIN; + int v5 = 0; + int v6 = 0; + int v7 = 0; + + for (int index = 0; index < region->pointsLength; index++) { + Point* point = &(region->points[index]); + if (v1 >= point->x) v1 = point->x; + if (v3 >= point->y) v3 = point->y; + if (v2 <= point->x) v2 = point->x; + if (v4 <= point->y) v4 = point->y; + v6 += point->x; + v7 += point->y; + v5++; + } + + region->field_28 = v3; + region->field_2C = v2; + region->field_30 = v4; + region->field_24 = v1; + + if (v5 != 0) { + region->field_34 = v6 / v5; + region->field_38 = v7 / v5; + } +} + +// 0x4A2D78 +Region* regionCreate(int initialCapacity) +{ + Region* region = internal_malloc_safe(sizeof(*region), __FILE__, __LINE__); // "..\int\REGION.C", 142 + memset(region, 0, sizeof(*region)); + + if (initialCapacity != 0) { + region->points = internal_malloc_safe(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->field_74 = 0; + region->field_28 = INT_MIN; + region->field_30 = INT_MAX; + region->field_54 = 0; + region->field_5C = 0; + region->field_64 = 0; + region->field_68 = 0; + region->field_6C = 0; + region->field_70 = 0; + region->field_60 = 0; + region->field_78 = 0; + region->field_7C = 0; + region->field_80 = 0; + region->field_84 = 0; + region->pointsLength = 0; + region->field_24 = 0; + region->field_2C = 0; + region->field_50 = 0; + region->field_4C = 0; + region->field_48 = 0; + region->field_58 = 0; + + return region; +} + +// regionAddPoint +// 0x4A2E68 +void regionAddPoint(Region* region, int x, int y) +{ + if (region == NULL) { + debugPrint("regionAddPoint(): null region ptr\n"); + return; + } + + if (region->points != NULL) { + if (region->pointsCapacity - 1 == region->pointsLength) { + region->points = internal_realloc_safe(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 = internal_malloc_safe(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; +} + +// regionDelete +// 0x4A2F0C +void regionDelete(Region* region) +{ + if (region == NULL) { + debugPrint("regionDelete(): null region ptr\n"); + return; + } + + if (region->points != NULL) { + internal_free_safe(region->points, __FILE__, __LINE__); // "..\int\REGION.C", 206 + } + + internal_free_safe(region, __FILE__, __LINE__); // "..\int\REGION.C", 207 +} + +// regionAddName +// 0x4A2F54 +void regionSetName(Region* region, const char* name) +{ + if (region == NULL) { + debugPrint("regionAddName(): null region ptr\n"); + return; + } + + if (name == NULL) { + region->name[0] = '\0'; + return; + } + + strncpy(region->name, name, REGION_NAME_LENGTH - 1); +} + +// regionGetName +// 0x4A2F80 +char* regionGetName(Region* region) +{ + if (region == NULL) { + debugPrint("regionGetName(): null region ptr\n"); + return _aNull; + } + + return region->name; +} + +// regionGetUserData +// 0x4A2F98 +void* regionGetUserData(Region* region) +{ + if (region == NULL) { + debugPrint("regionGetUserData(): null region ptr\n"); + return NULL; + } + + return region->userData; +} + +// regionSetUserData +// 0x4A2FB4 +void regionSetUserData(Region* region, void* data) +{ + if (region == NULL) { + debugPrint("regionSetUserData(): null region ptr\n"); + return; + } + + region->userData = data; +} + +// 0x4A2FD0 +void regionAddFlag(Region* region, int value) +{ + region->field_74 |= value; +} diff --git a/src/region.h b/src/region.h new file mode 100644 index 0000000..516ab3e --- /dev/null +++ b/src/region.h @@ -0,0 +1,52 @@ +#ifndef REGION_H +#define REGION_H + +#include "geometry.h" +#include "interpreter.h" + +#define REGION_NAME_LENGTH (32) + +typedef struct Region { + char name[REGION_NAME_LENGTH]; + Point* points; + int field_24; + int field_28; + int field_2C; + int field_30; + int field_34; + int field_38; + int pointsLength; + int pointsCapacity; + Program* program; + int field_48; + int field_4C; + int field_50; + int field_54; + int field_58; + int field_5C; + int field_60; + int field_64; + int field_68; + int field_6C; + int field_70; + int field_74; + int field_78; + int field_7C; + int field_80; + int field_84; + void* userData; +} Region; + +extern char _aNull[]; + +void _regionSetBound(Region* region); +Region* regionCreate(int a1); +void regionAddPoint(Region* region, int x, int y); +void regionDelete(Region* region); +void regionSetName(Region* region, const char* src); +char* regionGetName(Region* region); +void* regionGetUserData(Region* region); +void regionSetUserData(Region* region, void* data); +void regionAddFlag(Region* region, int value); + +#endif /* REGION_H */ diff --git a/src/scripts.c b/src/scripts.c new file mode 100644 index 0000000..849c15d --- /dev/null +++ b/src/scripts.c @@ -0,0 +1,2847 @@ +#include "scripts.h" + +#include "actions.h" +#include "automap.h" +#include "combat.h" +#include "core.h" +#include "critter.h" +#include "debug.h" +#include "dialog.h" +#include "elevator.h" +#include "endgame.h" +#include "export.h" +#include "game.h" +#include "game_dialog.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "memory.h" +#include "object.h" +#include "proto.h" +#include "proto_instance.h" +#include "queue.h" +#include "tile.h" +#include "window_manager.h" +#include "window_manager_private.h" +#include "world_map.h" + +#include +#include + +// 0x50D6B8 +char _Error_2[] = "Error"; + +// 0x50D6C0 +char byte_50D6C0[] = ""; + +// Number of lines in scripts.lst +// +// 0x51C6AC +int _num_script_indexes = 0; + +// 0x51C6B0 +int gScriptsEnumerationScriptIndex = 0; + +// 0x51C6B4 +ScriptListExtent* gScriptsEnumerationScriptListExtent = NULL; + +// 0x51C6B8 +int gScriptsEnumerationElevation = 0; + +// 0x51C6BC +bool _scr_SpatialsEnabled = true; + +// 0x51C6C0 +ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; + +// 0x51C710 +const char* gScriptsBasePath = "scripts\\"; + +// 0x51C714 +bool gScriptsEnabled = false; + +// 0x51C718 +int _script_engine_run_critters = 0; + +// 0x51C71C +int _script_engine_game_mode = 0; + +// Game time in ticks (1/10 second). +// +// 0x51C720 +int gGameTime = 302400; + +// 0x51C724 +const int gGameTimeDaysPerMonth[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 +const char* gScriptProcNames[28] = { + "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 +ScriptsListEntry* gScriptsListEntries = NULL; + +// 0x51C7CC +int gScriptsListEntriesLength = 0; + +// 0x51C7D4 +int _cur_id = 4; + +// 0x51C7DC +int _count_ = 0; + +// 0x51C7E0 +int _last_time__ = 0; + +// 0x51C7E4 +int _last_light_time = 0; + +// 0x51C7E8 +Object* _scrQueueTestObj = NULL; + +// 0x51C7EC +int _scrQueueTestValue = 0; + +// 0x51C7F0 +char* _err_str = _Error_2; + +// 0x51C7F4 +char* _blank_str = byte_50D6C0; + +// 0x664954 +ScriptRequests gScriptsRequests; + +// 0x664958 +STRUCT_664980 stru_664958; + +// 0x664980 +STRUCT_664980 stru_664980; + +// 0x6649A8 +int gScriptsRequestedElevatorType; + +// 0x6649AC +int gScriptsRequestedElevatorLevel; + +// 0x6649B0 +int gScriptsRequestedExplosionTile; + +// 0x6649B4 +int gScriptsRequestedExplosionElevation; + +// 0x6649B8 +int gScriptsRequestedExplosionMinDamage; + +// 0x6649BC +int gScriptsRequestedExplosionMaxDamage; + +// 0x6649C0 +Object* gScriptsRequestedDialogWith; + +// 0x6649C4 +Object* gScriptsRequestedLootingBy; + +// 0x6649C8 +Object* gScriptsRequestedLootingFrom; + +// 0x6649CC +Object* gScriptsRequestedStealingBy; + +// 0x6649D0 +Object* gScriptsRequestedStealingFrom; + +// 0x6649D4 +MessageList _script_dialog_msgs[1450]; + +// scr.msg +// +// 0x667724 +MessageList gScrMessageList; + +// time string (h:ss) +// +// 0x66772C +char _hour_str[7]; + +// 0x667748 +int _lasttime; + +// 0x66774C +bool _set; + +// 0x667750 +char _tempStr1[20]; + +// TODO: Make unsigned. +// +// Returns game time in ticks (1/10 second). +// +// 0x4A3330 +int gameTimeGetTime() +{ + return gGameTime; +} + +// 0x4A3338 +void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr) +{ + int year = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) / 365 + 2241; + int month = 6; + int day = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) % 365; + + while (1) { + int daysInMonth = gGameTimeDaysPerMonth[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 gameTimeGetHour() +{ + return 100 * ((gGameTime / 600) / 60 % 24) + (gGameTime / 600) % 60; +} + +// Returns time string (h:mm) +// +// 0x4A3420 +char* gameTimeGetTimeString() +{ + sprintf(_hour_str, "%d:%02d", (gGameTime / 600) / 60 % 24, (gGameTime / 600) % 60); + return _hour_str; +} + +// TODO: Make unsigned. +// +// 0x4A347C +void gameTimeSetTime(int time) +{ + if (time == 0) { + time = 1; + } + + gGameTime = time; +} + +// 0x4A34CC +void gameTimeAddTicks(int ticks) +{ + gGameTime += ticks; + + int v1 = 0; + + unsigned int year = gGameTime / 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) { + gameTimeEventProcess(NULL, NULL); + } +} + +// 0x4A3518 +void gameTimeAddSeconds(int seconds) +{ + // NOTE: Uninline. + gameTimeAddTicks(seconds * 10); +} + +// 0x4A3570 +int gameTimeScheduleUpdateEvent() +{ + int v1 = 10 * (60 * (60 - (gGameTime / 600) % 60 - 1) + 3600 * (24 - (gGameTime / 600) / 60 % 24 - 1) + 60); + if (queueAddEvent(v1, NULL, NULL, EVENT_TYPE_GAME_TIME) == -1) { + return -1; + } + + if (gMapHeader.name[0] != '\0') { + if (queueAddEvent(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) == -1) { + return -1; + } + } + + return 0; +} + +// 0x4A3620 +int gameTimeEventProcess(Object* obj, void* data) +{ + int movie_index; + int v4; + + movie_index = -1; + + debugPrint("\nQUEUE PROCESS: Midnight!"); + + if (gameMovieIsPlaying()) { + return 0; + } + + objectUnjamAll(); + + if (!_gdialogActive()) { + _scriptsCheckGameEvents(&movie_index, -1); + } + + v4 = _critter_check_rads(gDude); + + _queue_clear_type(4, 0); + + gameTimeScheduleUpdateEvent(); + + 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 = gGameTime / GAME_TIME_TICKS_PER_DAY; + + if (gameGetGlobalVar(GVAR_ENEMY_ARROYO)) { + movie = MOVIE_AFAILED; + movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_STOP_MUSIC; + endgame = true; + } else { + if (day >= 360 || gameGetGlobalVar(GVAR_FALLOUT_2) >= 3) { + movie = MOVIE_ARTIMER4; + if (!gameMovieIsSeen(MOVIE_ARTIMER4)) { + adjustRep = true; + _wmAreaSetVisibleState(CITY_ARROYO, 0, 1); + _wmAreaSetVisibleState(CITY_DESTROYED_ARROYO, 1, 1); + _wmAreaMarkVisitedState(CITY_DESTROYED_ARROYO, 2); + } + } else if (day >= 270 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + adjustRep = true; + movie = MOVIE_ARTIMER3; + } else if (day >= 180 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + adjustRep = true; + movie = MOVIE_ARTIMER2; + } else if (day >= 90 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + adjustRep = true; + movie = MOVIE_ARTIMER1; + } + } + + if (movie != -1) { + if (gameMovieIsSeen(movie)) { + movie = -1; + } else { + if (window != -1) { + windowHide(window); + } + + gameMoviePlay(movie, movieFlags); + + if (window != -1) { + windowUnhide(window); + } + + if (adjustRep) { + int rep = gameGetGlobalVar(GVAR_TOWN_REP_ARROYO); + gameSetGlobalVar(GVAR_TOWN_REP_ARROYO, rep - 15); + } + } + } + + if (endgame) { + _game_user_wants_to_quit = 2; + } else { + tileWindowRefresh(); + } + + if (moviePtr != NULL) { + *moviePtr = movie; + } + + return 0; +} + +// 0x4A382C +int mapUpdateEventProcess(Object* obj, void* data) +{ + scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); + + _queue_clear_type(EVENT_TYPE_MAP_UPDATE_EVENT, NULL); + + if (gMapHeader.name[0] == '\0') { + return 0; + } + + if (queueAddEvent(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) != -1) { + return 0; + } + + return -1; +} + +// new_obj_id +// 0x4A386C +int scriptsNewObjectId() +{ + Object* ptr; + + do { + _cur_id++; + ptr = objectFindFirst(); + + while (ptr) { + if (_cur_id == ptr->id) { + break; + } + + ptr = objectFindNext(); + } + } while (ptr); + + if (_cur_id >= 18000) { + debugPrint("\n ERROR: new_obj_id() !!!! Picked PLAYER ID!!!!"); + } + + _cur_id++; + + return _cur_id; +} + +// 0x4A390C +int scriptGetSid(Program* program) +{ + for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) { + ScriptListExtent* extent = gScriptLists[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* scriptGetSelf(Program* program) +{ + int sid = scriptGetSid(program); + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return NULL; + } + + if (script->owner != NULL) { + return script->owner; + } + + if ((sid >> 24) != SCRIPT_TYPE_SPATIAL) { + return NULL; + } + + Object* object; + int fid = buildFid(6, 3, 0, 0, 0); + objectCreateWithFidPid(&object, fid, -1); + objectHide(object, NULL); + _obj_toggle_flat(object, NULL); + object->sid = sid; + + // NOTE: Redundant, we've already obtained script earlier. Probably + // inlining. + Script* v1; + if (scriptGetScript(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 = scriptsNewObjectId(); + v1->field_1C = object->id; + v1->owner = object; + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + Script* spatialScript = scriptGetFirstSpatialScript(elevation); + while (spatialScript != NULL) { + if (spatialScript == script) { + objectSetLocation(object, script->sp.built_tile & 0x3FFFFFF, elevation, NULL); + return object; + } + spatialScript = scriptGetNextSpatialScript(); + } + } + + return object; +} + +// 0x4A3B0C +int scriptSetObjects(int sid, Object* source, Object* target) +{ + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return -1; + } + + script->source = source; + script->target = target; + + return 0; +} + +// 0x4A3B34 +void scriptSetFixedParam(int sid, int value) +{ + Script* script; + if (scriptGetScript(sid, &script) != -1) { + script->fixedParam = value; + } +} + +// 0x4A3B54 +int scriptSetActionBeingUsed(int sid, int value) +{ + Script* scr; + + if (scriptGetScript(sid, &scr) == -1) { + return -1; + } + + scr->actionBeingUsed = value; + + return 0; +} + +// 0x4A3B74 +Program* scriptsCreateProgramByName(const char* name) +{ + char path[MAX_PATH]; + + strcpy(path, _cd_path_base); + strcat(path, gScriptsBasePath); + strcat(path, name); + strcat(path, ".int"); + + return programCreateByPath(path); +} + +// 0x4A3C2C +void _doBkProcesses() +{ + if (!_set) { + _lasttime = _get_bk_time(); + _set = 1; + } + + int v0 = _get_bk_time(); + if (gScriptsEnabled) { + _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 (gScriptsEnabled && _script_engine_run_critters) { + if (!_gdialogActive()) { + _script_chk_critters(); + _script_chk_timed_events(); + } + } +} + +// 0x4A3CA0 +void _script_chk_critters() +{ + if (!_gdialogActive() && !isInCombat()) { + ScriptList* scriptList; + ScriptListExtent* scriptListExtent; + + int scriptsCount = 0; + + scriptList = &(gScriptLists[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 = &(gScriptLists[SCRIPT_TYPE_CRITTER]); + scriptListExtent = scriptList->head; + while (scriptListExtent != NULL && extentIndex != 0) { + extentIndex -= 1; + scriptListExtent = scriptListExtent->next; + } + + if (scriptListExtent != NULL) { + Script* script = &(scriptListExtent->scripts[scriptIndex]); + scriptExecProc(script->sid, proc); + } + } + } +} + +// TODO: Check. +// +// 0x4A3D84 +void _script_chk_timed_events() +{ + int v0 = _get_bk_time(); + + int v1 = false; + if (!isInCombat()) { + v1 = true; + } + + if (_game_state() != 4) { + if (getTicksBetween(v0, _last_light_time) >= 30000) { + _last_light_time = v0; + scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); + } + } else { + v1 = false; + } + + if (getTicksBetween(v0, _last_time__) >= 100) { + _last_time__ = v0; + if (!isInCombat()) { + gGameTime += 1; + } + v1 = true; + } + + if (v1) { + while (!queueIsEmpty()) { + int time = gameTimeGetTime(); + int v2 = queueGetNextEventTime(); + if (time < v2) { + break; + } + + queueProcessEvents(); + } + } +} + +// 0x4A3E30 +void _scrSetQueueTestVals(Object* a1, int a2) +{ + _scrQueueTestObj = a1; + _scrQueueTestValue = a2; +} + +// 0x4A3E3C +int _scrQueueRemoveFixed(Object* obj, void* data) +{ + ScriptEvent* scriptEvent = data; + return obj == _scrQueueTestObj && scriptEvent->field_4 == _scrQueueTestValue; +} + +// 0x4A3E60 +int scriptAddTimerEvent(int sid, int delay, int param) +{ + ScriptEvent* scriptEvent = internal_malloc(sizeof(*scriptEvent)); + if (scriptEvent == NULL) { + return -1; + } + + scriptEvent->sid = sid; + scriptEvent->field_4 = param; + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + internal_free(scriptEvent); + return -1; + } + + if (queueAddEvent(delay, script->owner, scriptEvent, EVENT_TYPE_SCRIPT) == -1) { + internal_free(scriptEvent); + return -1; + } + + return 0; +} + +// 0x4A3EDC +int scriptEventWrite(File* stream, void* data) +{ + ScriptEvent* scriptEvent = data; + + if (fileWriteInt32(stream, scriptEvent->sid) == -1) return -1; + if (fileWriteInt32(stream, scriptEvent->field_4) == -1) return -1; + + return 0; +} + +// 0x4A3F04 +int scriptEventRead(File* stream, void** dataPtr) +{ + ScriptEvent* scriptEvent = internal_malloc(sizeof(*scriptEvent)); + if (scriptEvent == NULL) { + return -1; + } + + if (fileReadInt32(stream, &(scriptEvent->sid)) == -1) goto err; + if (fileReadInt32(stream, &(scriptEvent->field_4)) == -1) goto err; + + *dataPtr = scriptEvent; + + return 0; + +err: + + // there is a memory leak in original code, free is not called + internal_free(scriptEvent); + + return -1; +} + +// 0x4A3F4C +int scriptEventProcess(Object* obj, void* data) +{ + ScriptEvent* scriptEvent = data; + + Script* script; + if (scriptGetScript(scriptEvent->sid, &script) == -1) { + return 0; + } + + script->fixedParam = scriptEvent->field_4; + + scriptExecProc(scriptEvent->sid, SCRIPT_PROC_TIMED); + + return 0; +} + +// 0x4A3F80 +int scriptsClearPendingRequests() +{ + gScriptsRequests = 0; + return 0; +} + +// NOTE: Inlined. +// +// 0x4A3F90 +int _scripts_clear_combat_requests(Script* script) +{ + if ((gScriptsRequests & SCRIPT_REQUEST_COMBAT) != 0 && stru_664958.attacker == script->owner) { + gScriptsRequests &= ~(SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); + } + return 0; +} + +// 0x4A3FB4 +int scriptsHandleRequests() +{ + if (gScriptsRequests == 0) { + return 0; + } + + if ((gScriptsRequests & SCRIPT_REQUEST_COMBAT) != 0) { + if (!_action_explode_running()) { + // entering combat + gScriptsRequests &= ~(SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); + memcpy(&stru_664980, &stru_664958, sizeof(stru_664980)); + + if ((gScriptsRequests & SCRIPT_REQUEST_0x40) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_0x40; + _combat(NULL); + } else { + _combat(&stru_664980); + memset(&stru_664980, 0, sizeof(stru_664980)); + } + } + } + + if ((gScriptsRequests & SCRIPT_REQUEST_0x02) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_0x02; + _wmTownMap(); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_WORLD_MAP) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_WORLD_MAP; + _wmWorldMap(); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_ELEVATOR) != 0) { + int map = gMapHeader.field_34; + int elevation = gScriptsRequestedElevatorLevel; + int tile = -1; + + gScriptsRequests &= ~SCRIPT_REQUEST_ELEVATOR; + + if (elevatorSelectLevel(gScriptsRequestedElevatorType, &map, &elevation, &tile) != -1) { + automapSaveCurrent(); + + if (map == gMapHeader.field_34) { + if (elevation == gElevation) { + reg_anim_clear(gDude); + objectSetRotation(gDude, ROTATION_SE, 0); + _obj_attempt_placement(gDude, tile, elevation, 0); + } else { + Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); + while (elevatorDoors != NULL) { + int pid = elevatorDoors->pid; + if ((pid >> 24) == OBJ_TYPE_SCENERY + && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) + && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { + break; + } + elevatorDoors = objectFindNextAtElevation(); + } + + reg_anim_clear(gDude); + objectSetRotation(gDude, ROTATION_SE, 0); + _obj_attempt_placement(gDude, tile, elevation, 0); + + if (elevatorDoors != NULL) { + objectSetFrame(elevatorDoors, 0, NULL); + objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); + elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; + elevatorDoors->data.scenery.door.openFlags &= ~0x01; + _obj_rebuild_all_light(); + } else { + debugPrint("\nWarning: Elevator: Couldn't find old elevator doors!"); + } + } + } else { + Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); + while (elevatorDoors != NULL) { + int pid = elevatorDoors->pid; + if ((pid >> 24) == OBJ_TYPE_SCENERY + && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) + && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { + break; + } + elevatorDoors = objectFindNextAtElevation(); + } + + if (elevatorDoors != NULL) { + objectSetFrame(elevatorDoors, 0, NULL); + objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); + elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; + elevatorDoors->data.scenery.door.openFlags &= ~0x01; + _obj_rebuild_all_light(); + } else { + debugPrint("\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; + + mapSetTransition(&transition); + } + } + } + + if ((gScriptsRequests & SCRIPT_REQUEST_EXPLOSION) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_EXPLOSION; + actionExplode(gScriptsRequestedExplosionTile, gScriptsRequestedExplosionElevation, gScriptsRequestedExplosionMinDamage, gScriptsRequestedExplosionMaxDamage, NULL, 1); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_DIALOG) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_DIALOG; + gameDialogEnter(gScriptsRequestedDialogWith, 0); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_ENDGAME) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_ENDGAME; + endgamePlaySlideshow(); + endgamePlayMovie(); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_LOOTING) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_LOOTING; + inventoryOpenLooting(gScriptsRequestedLootingBy, gScriptsRequestedLootingFrom); + } + + if ((gScriptsRequests & SCRIPT_REQUEST_STEALING) != 0) { + gScriptsRequests &= ~SCRIPT_REQUEST_STEALING; + inventoryOpenStealing(gScriptsRequestedStealingBy, gScriptsRequestedStealingFrom); + } + + return 0; +} + +// 0x4A43A0 +int _scripts_check_state_in_combat() +{ + if ((gScriptsRequests & SCRIPT_REQUEST_ELEVATOR) != 0) { + int map = gMapHeader.field_34; + int elevation = gScriptsRequestedElevatorLevel; + int tile = -1; + + if (elevatorSelectLevel(gScriptsRequestedElevatorType, &map, &elevation, &tile) != -1) { + automapSaveCurrent(); + + if (map == gMapHeader.field_34) { + if (elevation == gElevation) { + reg_anim_clear(gDude); + objectSetRotation(gDude, ROTATION_SE, 0); + _obj_attempt_placement(gDude, tile, elevation, 0); + } else { + Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); + while (elevatorDoors != NULL) { + int pid = elevatorDoors->pid; + if ((pid >> 24) == OBJ_TYPE_SCENERY + && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) + && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { + break; + } + elevatorDoors = objectFindNextAtElevation(); + } + + reg_anim_clear(gDude); + objectSetRotation(gDude, ROTATION_SE, 0); + _obj_attempt_placement(gDude, tile, elevation, 0); + + if (elevatorDoors != NULL) { + objectSetFrame(elevatorDoors, 0, NULL); + objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); + elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; + elevatorDoors->data.scenery.door.openFlags &= ~0x01; + _obj_rebuild_all_light(); + } else { + debugPrint("\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; + + mapSetTransition(&transition); + } + } + } + + if ((gScriptsRequests & SCRIPT_REQUEST_LOOTING) != 0) { + inventoryOpenLooting(gScriptsRequestedLootingBy, gScriptsRequestedLootingFrom); + } + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + return 0; +} + +// 0x4A457C +int scriptsRequestCombat(STRUCT_664980* a1) +{ + if ((gScriptsRequests & SCRIPT_REQUEST_0x0400) != 0) { + return -1; + } + + if (a1) { + static_assert(sizeof(stru_664958) == sizeof(*a1), "wrong size"); + memcpy(&stru_664958, a1, sizeof(stru_664958)); + } else { + gScriptsRequests |= SCRIPT_REQUEST_0x40; + } + + gScriptsRequests |= SCRIPT_REQUEST_COMBAT; + + return 0; +} + +// Likely related to random encounter, ala scriptsRequestRandomEncounter RELEASE +// +// 0x4A45D4 +void _scripts_request_combat_locked(STRUCT_664980* a1) +{ + if (a1 != NULL) { + memcpy(&stru_664958, a1, sizeof(stru_664958)); + } else { + gScriptsRequests |= SCRIPT_REQUEST_0x40; + } + + gScriptsRequests |= (SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); +} + +// request_world_map() +// 0x4A4644 +void scriptsRequestWorldMap() +{ + if (isInCombat()) { + _game_user_wants_to_quit = 1; + } + + gScriptsRequests |= SCRIPT_REQUEST_WORLD_MAP; +} + +// scripts_request_elevator +// 0x4A466C +int scriptsRequestElevator(Object* a1, int a2) +{ + int elevatorType = a2; + int elevatorLevel = gElevation; + + int tile = a1->tile; + if (tile == -1) { + debugPrint("\nError: scripts_request_elevator! Bad tile num"); + return -1; + } + + // TODO: What the hell is this? + tile -= 1005; + + Object* obj; + for (int y = -5; y < 5; y++) { + for (int x = -5; x < 5; x++) { + obj = objectFindFirstAtElevation(a1->elevation); + while (obj != NULL) { + if (tile == obj->tile && obj->pid == PROTO_ID_0x200050D) { + break; + } + + obj = objectFindNextAtElevation(); + } + + if (obj != NULL) { + break; + } + + tile += 1; + } + + if (obj != NULL) { + break; + } + + tile += 190; + } + + if (obj != NULL) { + elevatorType = obj->data.scenery.elevator.field_0; + elevatorLevel = obj->data.scenery.elevator.field_4; + } + + if (elevatorType == -1) { + return -1; + } + + gScriptsRequests |= SCRIPT_REQUEST_ELEVATOR; + gScriptsRequestedElevatorType = elevatorType; + gScriptsRequestedElevatorLevel = elevatorLevel; + + return 0; +} + +// 0x4A4730 +int scriptsRequestExplosion(int tile, int elevation, int minDamage, int maxDamage) +{ + gScriptsRequests |= SCRIPT_REQUEST_EXPLOSION; + gScriptsRequestedExplosionTile = tile; + gScriptsRequestedExplosionElevation = elevation; + gScriptsRequestedExplosionMinDamage = minDamage; + gScriptsRequestedExplosionMaxDamage = maxDamage; + return 0; +} + +// 0x4A4754 +void scriptsRequestDialog(Object* obj) +{ + gScriptsRequestedDialogWith = obj; + gScriptsRequests |= SCRIPT_REQUEST_DIALOG; +} + +// 0x4A4770 +void scriptsRequestEndgame() +{ + gScriptsRequests |= SCRIPT_REQUEST_ENDGAME; +} + +// 0x4A477C +int scriptsRequestLooting(Object* a1, Object* a2) +{ + gScriptsRequestedLootingBy = a1; + gScriptsRequestedLootingFrom = a2; + gScriptsRequests |= SCRIPT_REQUEST_LOOTING; + return 0; +} + +// 0x4A479C +int scriptsRequestStealing(Object* a1, Object* a2) +{ + gScriptsRequestedStealingBy = a1; + gScriptsRequestedStealingFrom = a2; + gScriptsRequests |= SCRIPT_REQUEST_STEALING; + return 0; +} + +// exec_script_proc +// 0x4A4810 +int scriptExecProc(int sid, int proc) +{ + if (!gScriptsEnabled) { + return -1; + } + + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return -1; + } + + script->scriptOverrides = 0; + + bool programLoaded = false; + if ((script->flags & SCRIPT_FLAG_0x01) == 0) { + clock(); + + char name[16]; + if (scriptsGetFileName(script->field_14 & 0xFFFFFF, name) == -1) { + return -1; + } + + char* pch = strchr(name, '.'); + if (pch != NULL) { + *pch = '\0'; + } + + script->program = scriptsCreateProgramByName(name); + if (script->program == NULL) { + debugPrint("\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) { + scriptLocateProcs(script); + + v9 = script->procs[proc]; + if (v9 == 0) { + v9 = 1; + } + + script->action = 0; + programListNodeCreate(program); + _interpret(program, -1); + } + + script->action = proc; + + _executeProcedure(program, v9); + + script->source = NULL; + + return 0; +} + +// Locate built-in procs for given script. +// +// 0x4A49D0 +int scriptLocateProcs(Script* script) +{ + for (int proc = 0; proc < SCRIPT_PROC_COUNT; proc++) { + int index = programFindProcedure(script->program, gScriptProcNames[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 (scriptGetScript(sid, &scr) == -1) { + return 0; + } + + return scr->procs[proc] != SCRIPT_PROC_NO_PROC; +} + +// 0x4A4D50 +int scriptsLoadScriptsList() +{ + char path[MAX_PATH]; + sprintf(path, "%s%s", "scripts\\", "scripts.lst"); + + File* stream = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + char string[260]; + while (fileReadString(string, 260, stream)) { + gScriptsListEntriesLength++; + + ScriptsListEntry* entries = internal_realloc(gScriptsListEntries, sizeof(*entries) * gScriptsListEntriesLength); + if (entries == NULL) { + return -1; + } + + gScriptsListEntries = entries; + + ScriptsListEntry* entry = &(entries[gScriptsListEntriesLength - 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); + } + } + } + + fileClose(stream); + + return 0; +} + +// NOTE: Inlined. +// +// 0x4A4EFC +int scriptsFreeScriptsList() +{ + if (gScriptsListEntries != NULL) { + internal_free(gScriptsListEntries); + gScriptsListEntries = NULL; + } + + gScriptsListEntriesLength = 0; + + return 0; +} + +// 0x4A4F28 +int _scr_find_str_run_info(int scriptIndex, int* a2, int sid) +{ + Script* script; + if (scriptGetScript(sid, &script) == -1) { + return -1; + } + + script->localVarsCount = gScriptsListEntries[scriptIndex].local_vars_num; + + return 0; +} + +// 0x4A4F68 +int scriptsGetFileName(int scriptIndex, char* name) +{ + sprintf(name, "%s.int", gScriptsListEntries[scriptIndex].name); + return 0; +} + +// scr_set_dude_script +// 0x4A4F90 +int scriptsSetDudeScript() +{ + if (scriptsClearDudeScript() == -1) { + return -1; + } + + if (gDude == NULL) { + debugPrint("Error in scr_set_dude_script: obj_dude uninitialized!"); + return -1; + } + + Proto* proto; + if (protoGetProto(0x1000000, &proto) == -1) { + debugPrint("Error in scr_set_dude_script: can't find obj_dude proto!"); + return -1; + } + + proto->critter.sid = 0x4000000; + + _obj_new_sid(gDude, &(gDude->sid)); + + Script* script; + if (scriptGetScript(gDude->sid, &script) == -1) { + debugPrint("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 scriptsClearDudeScript() +{ + if (gDude == NULL) { + debugPrint("\nError in scr_clear_dude_script: obj_dude uninitialized!"); + return -1; + } + + if (gDude->sid != -1) { + Script* script; + if (scriptGetScript(gDude->sid, &script) != -1) { + script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); + } + + scriptRemove(gDude->sid); + + gDude->sid = -1; + } + + return 0; +} + +// scr_init +// 0x4A50A8 +int scriptsInit() +{ + if (!messageListInit(&gScrMessageList)) { + return -1; + } + + for (int index = 0; index < 1450; index++) { + if (!messageListInit(&(_script_dialog_msgs[index]))) { + return -1; + } + } + + _scr_remove_all(); + _interpretOutputFunc(_win_debug); + interpreterRegisterOpcodeHandlers(); + _scr_header_load(); + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + _partyMemberClear(); + + if (scriptsLoadScriptsList() == -1) { + return -1; + } + + return 0; +} + +// 0x4A5120 +int _scr_reset() +{ + _scr_remove_all(); + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + _partyMemberClear(); + + return 0; +} + +// 0x4A5138 +int _scr_game_init() +{ + int i; + char path[MAX_PATH]; + + if (!messageListInit(&gScrMessageList)) { + debugPrint("\nError initing script message file!"); + return -1; + } + + for (i = 0; i < 1450; i++) { + if (!messageListInit(&(_script_dialog_msgs[i]))) { + debugPrint("\nERROR IN SCRIPT_DIALOG_MSGS!"); + return -1; + } + } + + sprintf(path, "%s%s", asc_5186C8, "script.msg"); + if (!messageListLoad(&gScrMessageList, path)) { + debugPrint("\nError loading script message file!"); + return -1; + } + + gScriptsEnabled = true; + _script_engine_game_mode = 1; + gGameTime = 1; + gameTimeSetTime(302400); + tickersAdd(_doBkProcesses); + + if (scriptsSetDudeScript() == -1) { + return -1; + } + + _scr_SpatialsEnabled = true; + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + return 0; +} + +// 0x4A5240 +int scriptsReset() +{ + debugPrint("\nScripts: [Game Reset]"); + _scr_game_exit(); + _scr_game_init(); + _partyMemberClear(); + _scr_remove_all_force(); + return scriptsSetDudeScript(); +} + +// 0x4A5274 +int scriptsExit() +{ + gScriptsEnabled = false; + _script_engine_run_critters = 0; + if (!messageListFree(&gScrMessageList)) { + debugPrint("\nError exiting script message file!"); + return -1; + } + + _scr_remove_all(); + _scr_remove_all_force(); + _interpretClose(); + programListFree(); + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + // NOTE: Uninline. + scriptsFreeScriptsList(); + + 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 (!messageListFree(messageList)) { + debugPrint("\nERROR in scr_message_free!"); + return -1; + } + + if (!messageListInit(messageList)) { + debugPrint("\nERROR in scr_message_free!"); + return -1; + } + } + } + + return 0; +} + +// 0x4A535C +int _scr_game_exit() +{ + _script_engine_game_mode = 0; + gScriptsEnabled = false; + _script_engine_run_critters = 0; + _scr_message_free(); + _scr_remove_all(); + programListFree(); + tickersRemove(_doBkProcesses); + messageListFree(&gScrMessageList); + if (scriptsClearDudeScript() == -1) { + return -1; + } + + // NOTE: Uninline. + scriptsClearPendingRequests(); + + return 0; +} + +// scr_enable +// 0x4A53A8 +int scriptsEnable() +{ + if (!_script_engine_game_mode) { + return -1; + } + + _script_engine_run_critters = 1; + gScriptsEnabled = true; + return 0; +} + +// scr_disable +// 0x4A53D0 +int scriptsDisable() +{ + gScriptsEnabled = 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 scriptsSaveGameGlobalVars(File* stream) +{ + return fileWriteInt32List(stream, gGameGlobalVars, gGameGlobalVarsLength); +} + +// 0x4A5424 +int scriptsLoadGameGlobalVars(File* stream) +{ + return fileReadInt32List(stream, gGameGlobalVars, gGameGlobalVarsLength); +} + +// NOTE: For unknown reason save game files contains two identical sets of game +// global variables (saved with [scriptsSaveGameGlobalVars]). The first set is +// read with [scriptsLoadGameGlobalVars], the second set is simply thrown away +// using this function. +// +// 0x4A5448 +int scriptsSkipGameGlobalVars(File* stream) +{ + int* vars = internal_malloc(sizeof(*vars) * gGameGlobalVarsLength); + if (vars == NULL) { + return -1; + } + + if (fileReadInt32List(stream, vars, gGameGlobalVarsLength) == -1) { + // FIXME: Leaks vars. + return -1; + } + + internal_free(vars); + + return 0; +} + +// 0x4A5490 +int _scr_header_load() +{ + _num_script_indexes = 0; + + char path[MAX_PATH]; + strcpy(path, _cd_path_base); + strcat(path, gScriptsBasePath); + strcat(path, "scripts.lst"); + + File* stream = fileOpen(path, "rt"); + if (stream == NULL) { + return -1; + } + + while (1) { + char ch = fileReadChar(stream); + if (ch == -1) { + break; + } + + if (ch == '\n') { + _num_script_indexes++; + } + } + + _num_script_indexes++; + + fileClose(stream); + + for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { + ScriptList* scriptList = &(gScriptLists[scriptType]); + scriptList->head = NULL; + scriptList->tail = NULL; + scriptList->length = 0; + scriptList->nextScriptId = 0; + } + + return 0; +} + +// 0x4A5590 +int scriptWrite(Script* scr, File* stream) +{ + if (fileWriteInt32(stream, scr->sid) == -1) return -1; + if (fileWriteInt32(stream, scr->field_4) == -1) return -1; + + switch (scr->sid >> 24) { + case SCRIPT_TYPE_SPATIAL: + if (fileWriteInt32(stream, scr->sp.built_tile) == -1) return -1; + if (fileWriteInt32(stream, scr->sp.radius) == -1) return -1; + break; + case SCRIPT_TYPE_TIMED: + if (fileWriteInt32(stream, scr->tm.time) == -1) return -1; + break; + } + + if (fileWriteInt32(stream, scr->flags) == -1) return -1; + if (fileWriteInt32(stream, scr->field_14) == -1) return -1; + if (fileWriteInt32(stream, (int)scr->program) == -1) return -1; // FIXME: writing pointer to file + if (fileWriteInt32(stream, scr->field_1C) == -1) return -1; + if (fileWriteInt32(stream, scr->localVarsOffset) == -1) return -1; + if (fileWriteInt32(stream, scr->localVarsCount) == -1) return -1; + if (fileWriteInt32(stream, scr->field_28) == -1) return -1; + if (fileWriteInt32(stream, scr->action) == -1) return -1; + if (fileWriteInt32(stream, scr->fixedParam) == -1) return -1; + if (fileWriteInt32(stream, scr->actionBeingUsed) == -1) return -1; + if (fileWriteInt32(stream, scr->scriptOverrides) == -1) return -1; + if (fileWriteInt32(stream, scr->field_48) == -1) return -1; + if (fileWriteInt32(stream, scr->howMuch) == -1) return -1; + if (fileWriteInt32(stream, scr->field_50) == -1) return -1; + + return 0; +} + +// 0x4A5704 +int scriptListExtentWrite(ScriptListExtent* a1, File* stream) +{ + for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { + Script* script = &(a1->scripts[index]); + if (scriptWrite(script, stream) != 0) { + return -1; + } + } + + if (fileWriteInt32(stream, a1->length) != 0) { + return -1; + } + + if (fileWriteInt32(stream, (int)a1->next) != 0) { + // FIXME: writing pointer to file + return -1; + } + + return 0; +} + +// 0x4A5768 +int scriptSaveAll(File* stream) +{ + for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { + ScriptList* scriptList = &(gScriptLists[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]); + + if ((script->flags & SCRIPT_FLAG_0x08) != 0) { + scriptCount--; + + lastScriptExtent = scriptList->tail; + int backwardsIndex = lastScriptExtent->length - 1; + while (lastScriptExtent != scriptExtent || backwardsIndex > index) { + if (lastScriptExtent == scriptExtent) { + if (backwardsIndex >= index) { + break; + } + } + + 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 (fileWriteInt32(stream, scriptCount) == -1) { + return -1; + } + + if (scriptCount > 0) { + ScriptListExtent* scriptExtent = scriptList->head; + while (scriptExtent != lastScriptExtent) { + if (scriptListExtentWrite(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 (scriptListExtentWrite(lastScriptExtent, stream) == -1) { + return -1; + } + lastScriptExtent->length = length; + } + } + } + } + + return 0; +} + +// 0x4A5A1C +int scriptRead(Script* scr, File* stream) +{ + int prg; + + if (fileReadInt32(stream, &(scr->sid)) == -1) return -1; + if (fileReadInt32(stream, &(scr->field_4)) == -1) return -1; + + switch (scr->sid >> 24) { + case SCRIPT_TYPE_SPATIAL: + if (fileReadInt32(stream, &(scr->sp.built_tile)) == -1) return -1; + if (fileReadInt32(stream, &(scr->sp.radius)) == -1) return -1; + break; + case SCRIPT_TYPE_TIMED: + if (fileReadInt32(stream, &(scr->tm.time)) == -1) return -1; + break; + } + + if (fileReadInt32(stream, &(scr->flags)) == -1) return -1; + if (fileReadInt32(stream, &(scr->field_14)) == -1) return -1; + if (fileReadInt32(stream, &(prg)) == -1) return -1; + if (fileReadInt32(stream, &(scr->field_1C)) == -1) return -1; + if (fileReadInt32(stream, &(scr->localVarsOffset)) == -1) return -1; + if (fileReadInt32(stream, &(scr->localVarsCount)) == -1) return -1; + if (fileReadInt32(stream, &(scr->field_28)) == -1) return -1; + if (fileReadInt32(stream, &(scr->action)) == -1) return -1; + if (fileReadInt32(stream, &(scr->fixedParam)) == -1) return -1; + if (fileReadInt32(stream, &(scr->actionBeingUsed)) == -1) return -1; + if (fileReadInt32(stream, &(scr->scriptOverrides)) == -1) return -1; + if (fileReadInt32(stream, &(scr->field_48)) == -1) return -1; + if (fileReadInt32(stream, &(scr->howMuch)) == -1) return -1; + if (fileReadInt32(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 (!(gMapHeader.flags & 1)) { + scr->localVarsCount = 0; + } + + return 0; +} + +// 0x4A5BE8 +int scriptListExtentRead(ScriptListExtent* scriptExtent, File* stream) +{ + for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { + Script* scr = &(scriptExtent->scripts[index]); + if (scriptRead(scr, stream) != 0) { + return -1; + } + } + + if (fileReadInt32(stream, &(scriptExtent->length)) != 0) { + return -1; + } + + int next; + if (fileReadInt32(stream, &(next)) != 0) { + return -1; + } + + return 0; +} + +// 0x4A5C50 +int scriptLoadAll(File* stream) +{ + for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { + ScriptList* scriptList = &(gScriptLists[index]); + + int scriptsCount = 0; + if (fileReadInt32(stream, &scriptsCount) == -1) { + return -1; + } + + if (scriptsCount != 0) { + scriptList->length = scriptsCount / 16; + + if (scriptsCount % 16 != 0) { + scriptList->length++; + } + + ScriptListExtent* extent = internal_malloc(sizeof(*extent)); + scriptList->head = extent; + scriptList->tail = extent; + if (extent == NULL) { + return -1; + } + + if (scriptListExtentRead(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 = internal_malloc(sizeof(*extent)); + if (extent == NULL) { + return -1; + } + + if (scriptListExtentRead(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 scriptGetScript(int sid, Script** scriptPtr) +{ + *scriptPtr = NULL; + + if (sid == -1) { + return -1; + } + + if (sid == 0xCCCCCCCC) { + debugPrint("\nERROR: scr_ptr called with UN-SET id #!!!!"); + return -1; + } + + ScriptList* scriptList = &(gScriptLists[sid >> 24]); + 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 +int scriptGetNewId(int scriptType) +{ + int scriptId = gScriptLists[scriptType].nextScriptId++; + int v1 = scriptType << 24; + + while (scriptId < 32000) { + Script* script; + if (scriptGetScript(v1 | scriptId, &script) == -1) { + break; + } + scriptId++; + } + + return scriptId; +} + +// 0x4A5F28 +int scriptAdd(int* sidPtr, int scriptType) +{ + ScriptList* scriptList = &(gScriptLists[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 = internal_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 = internal_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 = scriptGetNewId(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 scriptsRemoveLocalVars(Script* script) +{ + if (script == NULL) { + return -1; + } + + if (script->localVarsCount != 0) { + int oldMapLocalVarsCount = gMapLocalVarsLength; + if (oldMapLocalVarsCount > 0 && script->localVarsOffset >= 0) { + gMapLocalVarsLength -= script->localVarsCount; + + if (oldMapLocalVarsCount - script->localVarsCount != script->localVarsOffset && script->localVarsOffset != -1) { + memmove(gMapLocalVars + script->localVarsOffset, + gMapLocalVars + (script->localVarsOffset + script->localVarsCount), + sizeof(*gMapLocalVars) * (oldMapLocalVarsCount - script->localVarsCount - script->localVarsOffset)); + + gMapLocalVars = internal_realloc(gMapLocalVars, sizeof(*gMapLocalVars) * gMapLocalVarsLength); + if (gMapLocalVars == NULL) { + debugPrint("\nError in mem_realloc in scr_remove_local_vars!\n"); + } + + for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { + ScriptList* scriptList = &(gScriptLists[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 scriptRemove(int sid) +{ + if (sid == -1) { + return -1; + } + + ScriptList* scriptList = &(gScriptLists[sid >> 24]); + + 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 (scriptsRemoveLocalVars(script) == -1) { + debugPrint("\nERROR Removing local vars on scr_remove!!\n"); + } + + if (queueRemoveEventsByType(script->owner, EVENT_TYPE_SCRIPT) == -1) { + debugPrint("\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--; + internal_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; + + internal_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 = &(gScriptLists[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) { + scriptRemove(script->sid); + } else { + next = scriptListExtent->next; + scriptRemove(script->sid); + } + } + } + + scriptListExtent = next; + } + } + + gScriptsEnumerationScriptIndex = 0; + gScriptsEnumerationScriptListExtent = NULL; + gScriptsEnumerationElevation = 0; + gMapSid = -1; + + programListFree(); + _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 = &(gScriptLists[type]); + ScriptListExtent* extent = scriptList->head; + while (extent != NULL) { + ScriptListExtent* next = extent->next; + internal_free(extent); + extent = next; + } + + scriptList->head = NULL; + scriptList->tail = NULL; + scriptList->length = 0; + } + + gScriptsEnumerationScriptIndex = 0; + gScriptsEnumerationScriptListExtent = 0; + gScriptsEnumerationElevation = 0; + gMapSid = -1; + programListFree(); + _exportClearAllVariables(); + + return 0; +} + +// 0x4A6524 +Script* scriptGetFirstSpatialScript(int elevation) +{ + gScriptsEnumerationElevation = elevation; + gScriptsEnumerationScriptIndex = 0; + gScriptsEnumerationScriptListExtent = gScriptLists[SCRIPT_TYPE_SPATIAL].head; + + if (gScriptsEnumerationScriptListExtent == NULL) { + return NULL; + } + + Script* script = &(gScriptsEnumerationScriptListExtent->scripts[0]); + if ((script->flags & SCRIPT_FLAG_0x02) != 0 || ((script->sp.built_tile & 0xE0000000) >> 29) != elevation) { + script = scriptGetNextSpatialScript(); + } + + return script; +} + +// 0x4A6564 +Script* scriptGetNextSpatialScript() +{ + ScriptListExtent* scriptListExtent = gScriptsEnumerationScriptListExtent; + int scriptIndex = gScriptsEnumerationScriptIndex; + + 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 && ((script->sp.built_tile & 0xE0000000) >> 29) == gScriptsEnumerationElevation) { + break; + } + } + + Script* script; + if (scriptListExtent != NULL) { + script = &(scriptListExtent->scripts[scriptIndex]); + } else { + script = NULL; + } + + gScriptsEnumerationScriptIndex = scriptIndex; + gScriptsEnumerationScriptListExtent = scriptListExtent; + + return script; +} + +// 0x4A65F0 +void _scr_spatials_enable() +{ + _scr_SpatialsEnabled = true; +} + +// 0x4A6600 +void _scr_spatials_disable() +{ + _scr_SpatialsEnabled = false; +} + +// 0x4A6610 +bool scriptsExecSpatialProc(Object* object, int tile, int elevation) +{ + if (object == gGameMouseBouncingCursor) { + return false; + } + + if (object == gGameMouseHexCursor) { + return false; + } + + if ((object->flags & OBJECT_HIDDEN) != 0 || (object->flags & OBJECT_FLAG_0x08) != 0) { + return false; + } + + if (tile < 10) { + return false; + } + + if (!_scr_SpatialsEnabled) { + return false; + } + + _scr_SpatialsEnabled = false; + + int builtTile = ((elevation << 29) & 0xE0000000) | tile; + + for (Script* script = scriptGetFirstSpatialScript(elevation); script != NULL; script = scriptGetNextSpatialScript()) { + if (builtTile == script->sp.built_tile) { + // NOTE: Uninline. + scriptSetObjects(script->sid, object, NULL); + } else { + if (script->sp.radius == 0) { + continue; + } + + int distance = tileDistanceBetween(script->sp.built_tile & 0x3FFFFFF, tile); + if (distance > script->sp.radius) { + continue; + } + + // NOTE: Uninline. + scriptSetObjects(script->sid, object, NULL); + } + + scriptExecProc(script->sid, SCRIPT_PROC_SPATIAL); + } + + _scr_SpatialsEnabled = true; + + return true; +} + +// scr_load_all_scripts +// 0x4A677C +int scriptsExecStartProc() +{ + for (int scriptListIndex = 0; scriptListIndex < SCRIPT_TYPE_COUNT; scriptListIndex++) { + ScriptList* scriptList = &(gScriptLists[scriptListIndex]); + ScriptListExtent* extent = scriptList->head; + while (extent != NULL) { + for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { + Script* script = &(extent->scripts[scriptIndex]); + scriptExecProc(script->sid, SCRIPT_PROC_START); + } + extent = extent->next; + } + } + + return 0; +} + +// 0x4A67DC +void scriptsExecMapEnterProc() +{ + scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_ENTER); +} + +// 0x4A67E4 +void scriptsExecMapUpdateProc() +{ + scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); +} + +// scr_exec_map_update_scripts +// 0x4A67EC +void scriptsExecMapUpdateScripts(int proc) +{ + _scr_SpatialsEnabled = false; + + int fixedParam = 0; + if (proc == SCRIPT_PROC_MAP_ENTER) { + fixedParam = (gMapHeader.flags & 1) == 0; + } else { + scriptExecProc(gMapSid, proc); + } + + int sidListCapacity = 0; + for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { + ScriptList* scriptList = &(gScriptLists[scriptType]); + ScriptListExtent* scriptListExtent = scriptList->head; + while (scriptListExtent != NULL) { + sidListCapacity += scriptListExtent->length; + scriptListExtent = scriptListExtent->next; + } + } + + if (sidListCapacity == 0) { + return; + } + + int* sidList = internal_malloc(sizeof(*sidList) * sidListCapacity); + if (sidList == NULL) { + debugPrint("\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 = &(gScriptLists[scriptType]); + ScriptListExtent* scriptListExtent = scriptList->head; + while (scriptListExtent != NULL) { + for (int scriptIndex = 0; scriptIndex < scriptListExtent->length; scriptIndex++) { + Script* script = &(scriptListExtent->scripts[scriptIndex]); + if (script->sid != gMapSid && 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 (scriptGetScript(sidList[index], &script) != -1) { + script->fixedParam = fixedParam; + } + + scriptExecProc(sidList[index], proc); + } + } else { + for (int index = 0; index < sidListLength; index++) { + scriptExecProc(sidList[index], proc); + } + } + + internal_free(sidList); + + _scr_SpatialsEnabled = true; +} + +// 0x4A69A0 +void scriptsExecMapExitProc() +{ + scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_EXIT); +} + +// 0x4A6B64 +int scriptsGetMessageList(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'; + scriptsGetFileName(messageListIndex & 0xFFFFFF, scriptName); + + char* pch = strrchr(scriptName, '.'); + if (pch != NULL) { + *pch = '\0'; + } + + char path[MAX_PATH]; + sprintf(path, "dialog\\%s.msg", scriptName); + + if (!messageListLoad(messageList, path)) { + debugPrint("\nError loading script dialog message file!"); + return -1; + } + + if (!messageListFilterBadwords(messageList)) { + debugPrint("\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); +} + +// message_str +// 0x4A6C5C +char* _scr_get_msg_str_speech(int messageListId, int messageId, int a3) +{ + 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(&gProtoMessageList, &messageListItem, 650); + } + + MessageList* messageList; + if (scriptsGetMessageList(messageListId, &messageList) == -1) { + debugPrint("\nERROR: message_str: can't find message file: List: %d!", messageListId); + return NULL; + } + + if (((gGameDialogHeadFid & 0xF000000) >> 24) != 8) { + a3 = 0; + } + + MessageListItem messageListItem; + messageListItem.num = messageId; + if (!messageListGetItem(messageList, &messageListItem)) { + debugPrint("\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) { + gameDialogStartLips(NULL); + } else { + gameDialogStartLips(messageListItem.audio); + } + } else { + debugPrint("Missing speech name: %d\n", messageListItem.num); + } + } + } + + return messageListItem.text; +} + +// 0x4A6D64 +int scriptGetLocalVar(int sid, int variable, int* value) +{ + if (sid >> 24 == SCRIPT_TYPE_SYSTEM) { + debugPrint("\nError! System scripts/Map scripts not allowed local_vars! "); + + _tempStr1[0] = '\0'; + scriptsGetFileName(sid & 0xFFFFFF, _tempStr1); + + debugPrint(":%s\n", _tempStr1); + + *value = -1; + return -1; + } + + Script* script; + if (scriptGetScript(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 = mapGetLocalVar(script->localVarsOffset + variable); + } + + return 0; +} + +// 0x4A6E58 +int scriptSetLocalVar(int sid, int variable, int value) +{ + Script* script; + if (scriptGetScript(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); + } + + mapSetLocalVar(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 (gMapSid == 0 || gMapSid == -1) { + return false; + } + + int team = _combat_player_knocked_out_by(); + if (team == -1) { + return false; + } + + Script* before; + if (scriptGetScript(gMapSid, &before) != -1) { + before->fixedParam = team; + } + + scriptExecProc(gMapSid, SCRIPT_PROC_COMBAT); + + bool success = false; + + Script* after; + if (scriptGetScript(gMapSid, &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 = gScriptLists[SCRIPT_TYPE_SPATIAL].length + gScriptLists[SCRIPT_TYPE_ITEM].length; + if (scriptExtentsCount == 0) { + return 0; + } + + int* scriptIds = internal_malloc(sizeof(*scriptIds) * scriptExtentsCount * SCRIPT_LIST_EXTENT_SIZE); + if (scriptIds == NULL) { + return -1; + } + + ScriptListExtent* extent; + int scriptsCount = 0; + + _scr_SpatialsEnabled = false; + + extent = gScriptLists[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) { + scriptExecProc(script->sid, SCRIPT_PROC_START); + } + + if (script->procs[SCRIPT_PROC_DAMAGE] > 0) { + Object* self = script->owner; + if (self != NULL) { + if (self->elevation == elevation && tileDistanceBetween(self->tile, tile) <= radius) { + scriptIds[scriptsCount] = script->sid; + scriptsCount += 1; + } + } + } + } + extent = extent->next; + } + + extent = gScriptLists[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) { + scriptExecProc(script->sid, SCRIPT_PROC_START); + } + + if (script->procs[SCRIPT_PROC_DAMAGE] > 0 + && (script->sp.built_tile & 0xE0000000) >> 29 == elevation + && tileDistanceBetween(script->sp.built_tile & 0x3FFFFFF, 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 (scriptGetScript(sid, &script) != -1) { + script->fixedParam = 20; + } + + // TODO: Obtaining script twice, probably some inlining. + if (scriptGetScript(sid, &script) != -1) { + script->source = NULL; + script->target = a1; + } + + scriptExecProc(sid, SCRIPT_PROC_DAMAGE); + } + + // TODO: Redundant, we already know `scriptIds` is not NULL. + if (scriptIds != NULL) { + internal_free(scriptIds); + } + + _scr_SpatialsEnabled = true; + + return 0; +} diff --git a/src/scripts.h b/src/scripts.h new file mode 100644 index 0000000..881636c --- /dev/null +++ b/src/scripts.h @@ -0,0 +1,319 @@ +#ifndef SCRIPTS_H +#define SCRIPTS_H + +#include "combat_defs.h" +#include "db.h" +#include "interpreter.h" +#include "message.h" +#include "obj_types.h" + +#include + +#define SCRIPT_LIST_EXTENT_SIZE 16 + +#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) + +// 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_0x02 = 0x02, + SCRIPT_REQUEST_WORLD_MAP = 0x04, + SCRIPT_REQUEST_ELEVATOR = 0x08, + SCRIPT_REQUEST_EXPLOSION = 0x10, + SCRIPT_REQUEST_DIALOG = 0x20, + SCRIPT_REQUEST_0x40 = 0x40, + SCRIPT_REQUEST_ENDGAME = 0x80, + SCRIPT_REQUEST_LOOTING = 0x100, + SCRIPT_REQUEST_STEALING = 0x200, + SCRIPT_REQUEST_0x0400 = 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 [opGetScriptAction]. + 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"); + +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"); + +extern char _Error_2[]; +extern char byte_50D6C0[]; + +extern int _num_script_indexes; +extern int gScriptsEnumerationScriptIndex; +extern ScriptListExtent* gScriptsEnumerationScriptListExtent; +extern int gScriptsEnumerationElevation; +extern bool _scr_SpatialsEnabled; +extern ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; +extern const char* gScriptsBasePath; +extern bool gScriptsEnabled; +extern int _script_engine_run_critters; +extern int _script_engine_game_mode; +extern int gGameTime; +extern const int gGameTimeDaysPerMonth[12]; +extern const char* gScriptProcNames[SCRIPT_PROC_COUNT]; +extern ScriptsListEntry* gScriptsListEntries; +extern int gScriptsListEntriesLength; +extern int _cur_id; +extern int _count_; +extern int _last_time__; +extern int _last_light_time; +extern Object* _scrQueueTestObj; +extern int _scrQueueTestValue; +extern char* _err_str; +extern char* _blank_str; + +extern ScriptRequests gScriptsRequests; +extern STRUCT_664980 stru_664958; +extern STRUCT_664980 stru_664980; +extern int gScriptsRequestedElevatorType; +extern int gScriptsRequestedElevatorLevel; +extern int gScriptsRequestedExplosionTile; +extern int gScriptsRequestedExplosionElevation; +extern int gScriptsRequestedExplosionMinDamage; +extern int gScriptsRequestedExplosionMaxDamage; +extern Object* gScriptsRequestedDialogWith; +extern Object* gScriptsRequestedLootingBy; +extern Object* gScriptsRequestedLootingFrom; +extern Object* gScriptsRequestedStealingBy; +extern Object* gScriptsRequestedStealingFrom; +extern MessageList _script_dialog_msgs[1450]; +extern MessageList gScrMessageList; +extern char _hour_str[7]; +extern int _lasttime; +extern bool _set; +extern char _tempStr1[20]; + +int gameTimeGetTime(); +void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr); +int gameTimeGetHour(); +char* gameTimeGetTimeString(); +void gameTimeAddTicks(int a1); +void gameTimeAddSeconds(int a1); +void gameTimeSetTime(int time); +int gameTimeScheduleUpdateEvent(); +int gameTimeEventProcess(Object* obj, void* data); +int _scriptsCheckGameEvents(int* moviePtr, int window); +int mapUpdateEventProcess(Object* obj, void* data); +int scriptsNewObjectId(); +int scriptGetSid(Program* a1); +Object* scriptGetSelf(Program* s); +int scriptSetObjects(int sid, Object* source, Object* target); +void scriptSetFixedParam(int a1, int a2); +int scriptSetActionBeingUsed(int sid, int a2); +Program* scriptsCreateProgramByName(const char* name); +void _doBkProcesses(); +void _script_chk_critters(); +void _script_chk_timed_events(); +void _scrSetQueueTestVals(Object* a1, int a2); +int _scrQueueRemoveFixed(Object* obj, void* data); +int scriptAddTimerEvent(int sid, int delay, int param); +int scriptEventWrite(File* stream, void* data); +int scriptEventRead(File* stream, void** dataPtr); +int scriptEventProcess(Object* obj, void* data); +int scriptsClearPendingRequests(); +int _scripts_clear_combat_requests(Script* script); +int scriptsHandleRequests(); +int _scripts_check_state_in_combat(); +int scriptsRequestCombat(STRUCT_664980* a1); +void _scripts_request_combat_locked(STRUCT_664980* ptr); +void scriptsRequestWorldMap(); +int scriptsRequestElevator(Object* a1, int a2); +int scriptsRequestExplosion(int tile, int elevation, int minDamage, int maxDamage); +void scriptsRequestDialog(Object* a1); +void scriptsRequestEndgame(); +int scriptsRequestLooting(Object* a1, Object* a2); +int scriptsRequestStealing(Object* a1, Object* a2); +int scriptExecProc(int sid, int proc); +int scriptLocateProcs(Script* scr); +bool scriptHasProc(int sid, int proc); +int scriptsLoadScriptsList(); +int scriptsFreeScriptsList(); +int _scr_find_str_run_info(int a1, int* a2, int sid); +int scriptsGetFileName(int scriptIndex, char* name); +int scriptsSetDudeScript(); +int scriptsClearDudeScript(); +int scriptsInit(); +int _scr_reset(); +int _scr_game_init(); +int scriptsReset(); +int scriptsExit(); +int _scr_message_free(); +int _scr_game_exit(); +int scriptsEnable(); +int scriptsDisable(); +void _scr_enable_critters(); +void _scr_disable_critters(); +int scriptsSaveGameGlobalVars(File* stream); +int scriptsLoadGameGlobalVars(File* stream); +int scriptsSkipGameGlobalVars(File* stream); +int _scr_header_load(); +int scriptWrite(Script* scr, File* stream); +int scriptListExtentWrite(ScriptListExtent* a1, File* stream); +int scriptSaveAll(File* stream); +int scriptRead(Script* scr, File* stream); +int scriptListExtentRead(ScriptListExtent* a1, File* stream); +int scriptLoadAll(File* stream); +int scriptGetScript(int sid, Script** script); +int scriptGetNewId(int scriptType); +int scriptAdd(int* sidPtr, int scriptType); +int scriptsRemoveLocalVars(Script* script); +int scriptRemove(int index); +int _scr_remove_all(); +int _scr_remove_all_force(); +Script* scriptGetFirstSpatialScript(int a1); +Script* scriptGetNextSpatialScript(); +void _scr_spatials_enable(); +void _scr_spatials_disable(); +bool scriptsExecSpatialProc(Object* obj, int tile, int elevation); +int scriptsExecStartProc(); +void scriptsExecMapEnterProc(); +void scriptsExecMapUpdateProc(); +void scriptsExecMapUpdateScripts(int a1); +void scriptsExecMapExitProc(); +int scriptsGetMessageList(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 scriptGetLocalVar(int a1, int a2, int* a3); +int scriptSetLocalVar(int a1, int a2, int a3); +bool _scr_end_combat(); +int _scr_explode_scenery(Object* a1, int tile, int radius, int elevation); + +#endif /* SCRIPTS_H */ diff --git a/src/select_file_list.c b/src/select_file_list.c new file mode 100644 index 0000000..1fac1ef --- /dev/null +++ b/src/select_file_list.c @@ -0,0 +1,35 @@ +#include "select_file_list.h" + +#include "db.h" + +#include +#include + +// 0x4AA250 +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 = fileNameListInit(pattern, &fileNameList, 0, 0); + *fileNameListLengthPtr = fileNameListLength; + if (fileNameListLength == 0) { + return NULL; + } + + qsort(fileNameList, fileNameListLength, sizeof(*fileNameList), _compare); + + return fileNameList; +} + +// 0x4AA2DC +void _freeFileList(char** fileList) +{ + fileNameListFree(&fileList, 0); +} diff --git a/src/select_file_list.h b/src/select_file_list.h new file mode 100644 index 0000000..31849d9 --- /dev/null +++ b/src/select_file_list.h @@ -0,0 +1,8 @@ +#ifndef SELECT_FILE_LIST_H +#define SELECT_FILE_LIST_H + +int _compare(const void* a1, const void* a2); +char** _getFileList(const char* pattern, int* fileNameListLengthPtr); +void _freeFileList(char** fileList); + +#endif /* SELECT_FILE_LIST_H */ diff --git a/src/selfrun.c b/src/selfrun.c new file mode 100644 index 0000000..5e00740 --- /dev/null +++ b/src/selfrun.c @@ -0,0 +1,44 @@ +#include "selfrun.h" + +#include "db.h" +#include "game.h" + +#include + +// 0x51C8D8 +int _selfrun_state = 0; + +// 0x4A8BE0 +int _selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr) +{ + if (fileListPtr == NULL) { + return -1; + } + + if (fileListLengthPtr == NULL) { + return -1; + } + + *fileListLengthPtr = fileNameListInit("selfrun\\*.sdf", fileListPtr, 0, 0); + + return 0; +} + +// 0x4A8C10 +int _selfrun_free_list(char*** fileListPtr) +{ + if (fileListPtr == NULL) { + return -1; + } + + fileNameListFree(fileListPtr, 0); + + return 0; +} + +// 0x4A8E74 +void _selfrun_playback_callback() +{ + _game_user_wants_to_quit = 2; + _selfrun_state = 0; +} diff --git a/src/selfrun.h b/src/selfrun.h new file mode 100644 index 0000000..690ad94 --- /dev/null +++ b/src/selfrun.h @@ -0,0 +1,10 @@ +#ifndef SELFRUN_H +#define SELFRUN_H + +extern int _selfrun_state; + +int _selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr); +int _selfrun_free_list(char*** fileListPtr); +void _selfrun_playback_callback(); + +#endif /* SELFRUN_H */ diff --git a/src/skill.c b/src/skill.c new file mode 100644 index 0000000..2b21bf9 --- /dev/null +++ b/src/skill.c @@ -0,0 +1,1182 @@ +#include "skill.h" + +#include "actions.h" +#include "color.h" +#include "combat.h" +#include "critter.h" +#include "debug.h" +#include "display_monitor.h" +#include "game.h" +#include "game_config.h" +#include "interface.h" +#include "item.h" +#include "object.h" +#include "palette.h" +#include "party_member.h" +#include "perk.h" +#include "pipboy.h" +#include "proto.h" +#include "random.h" +#include "scripts.h" +#include "stat.h" +#include "trait.h" + +#include + +// Damage flags which can be repaired using "Repair" skill. +// +// 0x4AA2F0 +const int gRepairableDamageFlags[REPAIRABLE_DAMAGE_FLAGS_LENGTH] = { + DAM_BLIND, + DAM_CRIP_ARM_LEFT, + DAM_CRIP_ARM_RIGHT, + DAM_CRIP_LEG_RIGHT, + DAM_CRIP_LEG_LEFT, +}; + +// Damage flags which can be healed using "Doctor" skill. +// +// 0x4AA304 +const int gHealableDamageFlags[HEALABLE_DAMAGE_FLAGS_LENGTH] = { + DAM_BLIND, + DAM_CRIP_ARM_LEFT, + DAM_CRIP_ARM_RIGHT, + DAM_CRIP_LEG_RIGHT, + DAM_CRIP_LEG_LEFT, +}; + +// 0x51D118 +SkillDescription gSkillDescriptions[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; + +// Something about stealing, base value? +// +// 0x51D434 +int _gStealCount = 0; + +// 0x51D438 +int _gStealSize = 0; + +// 0x667F98 +int _timesSkillUsed[SKILL_COUNT][SKILLS_MAX_USES_PER_DAY]; + +// 0x668070 +int gTaggedSkills[NUM_TAGGED_SKILLS]; + +// skill.msg +// +// 0x668080 +MessageList gSkillsMessageList; + +// 0x4AA318 +int skillsInit() +{ + if (!messageListInit(&gSkillsMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "skill.msg"); + + if (!messageListLoad(&gSkillsMessageList, path)) { + return -1; + } + + for (int skill = 0; skill < SKILL_COUNT; skill++) { + MessageListItem messageListItem; + + messageListItem.num = 100 + skill; + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + gSkillDescriptions[skill].name = messageListItem.text; + } + + messageListItem.num = 200 + skill; + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + gSkillDescriptions[skill].description = messageListItem.text; + } + + messageListItem.num = 300 + skill; + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + gSkillDescriptions[skill].attributes = messageListItem.text; + } + } + + for (int index = 0; index < NUM_TAGGED_SKILLS; index++) { + gTaggedSkills[index] = -1; + } + + memset(_timesSkillUsed, 0, sizeof(_timesSkillUsed)); + + return 0; +} + +// 0x4AA448 +void skillsReset() +{ + for (int index = 0; index < NUM_TAGGED_SKILLS; index++) { + gTaggedSkills[index] = -1; + } + + memset(_timesSkillUsed, 0, sizeof(_timesSkillUsed)); +} + +// 0x4AA478 +void skillsExit() +{ + messageListFree(&gSkillsMessageList); +} + +// 0x4AA488 +int skillsLoad(File* stream) +{ + return fileReadInt32List(stream, gTaggedSkills, NUM_TAGGED_SKILLS); +} + +// 0x4AA4A8 +int skillsSave(File* stream) +{ + return fileWriteInt32List(stream, gTaggedSkills, NUM_TAGGED_SKILLS); +} + +// 0x4AA4C8 +void protoCritterDataResetSkills(CritterProtoData* data) +{ + for (int skill = 0; skill < SKILL_COUNT; skill++) { + data->skills[skill] = 0; + } +} + +// 0x4AA4E4 +void skillsSetTagged(int* skills, int count) +{ + for (int index = 0; index < count; index++) { + gTaggedSkills[index] = skills[index]; + } +} + +// 0x4AA508 +void skillsGetTagged(int* skills, int count) +{ + for (int index = 0; index < count; index++) { + skills[index] = gTaggedSkills[index]; + } +} + +// 0x4AA52C +bool skillIsTagged(int skill) +{ + return skill == gTaggedSkills[0] + || skill == gTaggedSkills[1] + || skill == gTaggedSkills[2] + || skill == gTaggedSkills[3]; +} + +// 0x4AA558 +int skillGetValue(Object* critter, int skill) +{ + if (!skillIsValid(skill)) { + return -5; + } + + int baseValue = skillGetBaseValue(critter, skill); + if (baseValue < 0) { + return baseValue; + } + + SkillDescription* skillDescription = &(gSkillDescriptions[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 == gDude) { + if (skillIsTagged(skill)) { + value += baseValue * skillDescription->field_20; + + if (!perkGetRank(critter, PERK_TAG) || skill != gTaggedSkills[3]) { + value += 20; + } + } + + value += traitGetSkillModifier(skill); + value += perkGetSkillModifier(critter, skill); + value += skillGetGameDifficultyModifier(skill); + } + + if (value > 300) { + value = 300; + } + + return value; +} + +// 0x4AA654 +int skillGetDefaultValue(int skill) +{ + return skillIsValid(skill) ? gSkillDescriptions[skill].defaultValue : -5; +} + +// 0x4AA680 +int skillGetBaseValue(Object* obj, int skill) +{ + if (!skillIsValid(skill)) { + return 0; + } + + Proto* proto; + protoGetProto(obj->pid, &proto); + + return proto->critter.data.skills[skill]; +} + +// 0x4AA6BC +int skillAdd(Object* obj, int skill) +{ + if (obj != gDude) { + return -5; + } + + if (!skillIsValid(skill)) { + return -5; + } + + Proto* proto; + protoGetProto(obj->pid, &proto); + + int unspentSp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + if (unspentSp <= 0) { + return -4; + } + + int skillValue = skillGetValue(obj, skill); + if (skillValue >= 300) { + return -3; + } + + // NOTE: Uninline. + int requiredSp = skillsGetCost(skillValue); + + if (unspentSp < requiredSp) { + return -4; + } + + int rc = pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, unspentSp - requiredSp); + if (rc == 0) { + proto->critter.data.skills[skill] += 1; + } + + return rc; +} + +// 0x4AA7F8 +int skillAddForce(Object* obj, int skill) +{ + if (obj != gDude) { + return -5; + } + + if (!skillIsValid(skill)) { + return -5; + } + + Proto* proto; + protoGetProto(obj->pid, &proto); + + if (skillGetValue(obj, skill) >= 300) { + return -3; + } + + proto->critter.data.skills[skill] += 1; + + return 0; +} + +// Returns the cost of raising skill value in skill points. +// +// 0x4AA87C +int skillsGetCost(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 skillSub(Object* critter, int skill) +{ + if (critter != gDude) { + return -5; + } + + if (!skillIsValid(skill)) { + return -5; + } + + Proto* proto; + protoGetProto(critter->pid, &proto); + + if (proto->critter.data.skills[skill] <= 0) { + return -2; + } + + int unspentSp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS); + int skillValue = skillGetValue(critter, skill) - 1; + + // NOTE: Uninline. + int requiredSp = skillsGetCost(skillValue); + + int newUnspentSp = unspentSp + requiredSp; + int rc = pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, newUnspentSp); + if (rc != 0) { + return rc; + } + + proto->critter.data.skills[skill] -= 1; + + if (skillIsTagged(skill)) { + int oldSkillCost = skillsGetCost(skillValue); + int newSkillCost = skillsGetCost(skillGetValue(critter, skill)); + if (oldSkillCost != newSkillCost) { + rc = pcSetStat(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 skillSubForce(Object* obj, int skill) +{ + Proto* proto; + + if (obj != gDude) { + return -5; + } + + if (!skillIsValid(skill)) { + return -5; + } + + protoGetProto(obj->pid, &proto); + + if (proto->critter.data.skills[skill] <= 0) { + return -2; + } + + proto->critter.data.skills[skill] -= 1; + + return 0; +} + +// 0x4AAAA4 +int skillRoll(Object* critter, int skill, int modifier, int* howMuch) +{ + if (!skillIsValid(skill)) { + return ROLL_FAILURE; + } + + if (critter == gDude && skill != SKILL_STEAL) { + Object* partyMember = partyMemberGetBestInSkill(skill); + if (partyMember != NULL) { + if (partyMemberGetBestSkill(partyMember) == skill) { + critter = partyMember; + } + } + } + + int skillValue = skillGetValue(critter, skill); + + if (critter == gDude && skill == SKILL_STEAL) { + if (dudeHasState(DUDE_STATE_SNEAKING)) { + if (dudeIsSneaking()) { + skillValue += 30; + } + } + } + + int criticalChance = critterGetStat(critter, STAT_CRITICAL_CHANCE); + return randomRoll(skillValue + modifier, criticalChance, howMuch); +} + +// 0x4AAB9C +char* skillGetName(int skill) +{ + return skillIsValid(skill) ? gSkillDescriptions[skill].name : NULL; +} + +// 0x4AABC0 +char* skillGetDescription(int skill) +{ + return skillIsValid(skill) ? gSkillDescriptions[skill].description : NULL; +} + +// 0x4AABE4 +char* skillGetAttributes(int skill) +{ + return skillIsValid(skill) ? gSkillDescriptions[skill].attributes : NULL; +} + +// 0x4AAC08 +int skillGetFrmId(int skill) +{ + return skillIsValid(skill) ? gSkillDescriptions[skill].frmId : 0; +} + +// 0x4AAC2C +void _show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int criticalChanceModifier) +{ + if (obj != gDude) { + return; + } + + if (a4 <= 0) { + return; + } + + SkillDescription* skillDescription = &(gSkillDescriptions[skill]); + + int baseExperience = skillDescription->experience; + if (baseExperience == 0) { + return; + } + + if (skillDescription->field_28 && criticalChanceModifier < 0) { + baseExperience += abs(criticalChanceModifier); + } + + int xpToAdd = a4 * baseExperience; + + int before = pcGetStat(PC_STAT_EXPERIENCE); + + if (pcAddExperience(xpToAdd) == 0 && a4 > 0) { + MessageListItem messageListItem; + messageListItem.num = 505; // You earn %d XP for honing your skills + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + int after = pcGetStat(PC_STAT_EXPERIENCE); + + char text[60]; + sprintf(text, messageListItem.text, after - before); + displayMonitorAddMessage(text); + } + } +} + +// skill_use +// 0x4AAD08 +int skillUse(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 == gDude) { + if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) { + int healerRank = perkGetRank(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 (skillGetFreeUsageSlot(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 + randomBetween(0, 2); + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + } + + if (critterIsDead(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 + randomBetween(0, 2); + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + debugPrint(messageListItem.text); + } + + break; + } + + if (currentHp < maximumHp) { + paletteFadeTo(gPaletteBlack); + + int roll; + if (critterGetBodyType(a2) == BODY_TYPE_ROBOTIC) { + roll = ROLL_FAILURE; + } else { + roll = skillRoll(obj, skill, criticalChance, &hpToHeal); + } + + if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { + hpToHeal = randomBetween(minimumHpToHeal + 1, maximumHpToHeal + 5); + critterAdjustHitPoints(a2, hpToHeal); + + if (obj == gDude) { + // You heal %d hit points. + messageListItem.num = 500; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + if (maximumHp - currentHp < hpToHeal) { + hpToHeal = maximumHp - currentHp; + } + + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + } + + a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; + + skillUpdateLastUse(SKILL_FIRST_AID); + + v1 = 1; + + if (a2 == gDude) { + interfaceRenderHitPoints(true); + } + } else { + // You fail to do any healing. + messageListItem.num = 503; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + } + + scriptsExecMapUpdateProc(); + paletteFadeTo(_cmap); + } else { + if (obj == gDude) { + // 501: You look healty already + // 502: %s looks healthy already + messageListItem.num = (a2 == gDude ? 501 : 502); + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + if (a2 == gDude) { + strcpy(text, messageListItem.text); + } else { + sprintf(text, messageListItem.text, objectGetName(a2)); + } + + displayMonitorAddMessage(text); + giveExp = false; + } + } + + if (obj == gDude) { + gameTimeAddSeconds(1800); + } + + break; + case SKILL_DOCTOR: + if (skillGetFreeUsageSlot(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 + randomBetween(0, 2); + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + } + + if (critterIsDead(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 + randomBetween(0, 2); + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + break; + } + + if (currentHp < maximumHp || critterIsCrippled(a2)) { + paletteFadeTo(gPaletteBlack); + + if (critterGetBodyType(a2) != BODY_TYPE_ROBOTIC && critterIsCrippled(a2)) { + int flags[HEALABLE_DAMAGE_FLAGS_LENGTH]; + static_assert(sizeof(flags) == sizeof(gHealableDamageFlags), "wrong size"); + memcpy(flags, gHealableDamageFlags, sizeof(gHealableDamageFlags)); + + for (int index = 0; index < HEALABLE_DAMAGE_FLAGS_LENGTH; index++) { + if ((a2->data.critter.combat.results & flags[index]) != 0) { + damageHealingAttempts++; + + int roll = skillRoll(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 (messageListGetItem(&gSkillsMessageList, &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 == gDude ? 520 : 521); + + skillUpdateLastUse(SKILL_DOCTOR); + + v1 = 1; + v2 = 1; + } else { + // 525: You fail to heal your %s. + // 526: You fail to heal the %s. + prefix.num = (a2 == gDude ? 525 : 526); + } + + if (!messageListGetItem(&gSkillsMessageList, &prefix)) { + return -1; + } + + sprintf(text, prefix.text, messageListItem.text); + displayMonitorAddMessage(text); + _show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); + + giveExp = false; + } + } + } + + int roll; + if (critterGetBodyType(a2) == BODY_TYPE_ROBOTIC) { + roll = ROLL_FAILURE; + } else { + int skillValue = skillGetValue(obj, skill); + roll = randomRoll(skillValue, criticalChance, &hpToHeal); + } + + if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { + hpToHeal = randomBetween(minimumHpToHeal + 4, maximumHpToHeal + 10); + critterAdjustHitPoints(a2, hpToHeal); + + if (obj == gDude) { + // You heal %d hit points. + messageListItem.num = 500; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + if (maximumHp - currentHp < hpToHeal) { + hpToHeal = maximumHp - currentHp; + } + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + } + + if (!v2) { + skillUpdateLastUse(SKILL_DOCTOR); + } + + a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; + + if (a2 == gDude) { + interfaceRenderHitPoints(true); + } + + v1 = 1; + _show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); + scriptsExecMapUpdateProc(); + paletteFadeTo(_cmap); + + giveExp = false; + } else { + // You fail to do any healing. + messageListItem.num = 503; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + + scriptsExecMapUpdateProc(); + paletteFadeTo(_cmap); + } + } else { + if (obj == gDude) { + // 501: You look healty already + // 502: %s looks healthy already + messageListItem.num = (a2 == gDude ? 501 : 502); + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + if (a2 == gDude) { + strcpy(text, messageListItem.text); + } else { + sprintf(text, messageListItem.text, objectGetName(a2)); + } + + displayMonitorAddMessage(text); + + giveExp = false; + } + } + + if (obj == gDude) { + gameTimeAddSeconds(3600 * damageHealingAttempts); + } + + break; + case SKILL_SNEAK: + case SKILL_LOCKPICK: + break; + case SKILL_STEAL: + scriptsRequestStealing(obj, a2); + break; + case SKILL_TRAPS: + messageListItem.num = 551; // You fail to find any traps. + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + case SKILL_SCIENCE: + messageListItem.num = 552; // You fail to learn anything. + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + return -1; + case SKILL_REPAIR: + if (critterGetBodyType(a2) != BODY_TYPE_ROBOTIC) { + // You cannot repair that. + messageListItem.num = 553; + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return -1; + } + + if (skillGetFreeUsageSlot(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 + randomBetween(0, 2); + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + return -1; + } + + if (critterIsDead(a2)) { + // You got it? + messageListItem.num = 1101; + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + break; + } + + if (currentHp < maximumHp || critterIsCrippled(a2)) { + int flags[REPAIRABLE_DAMAGE_FLAGS_LENGTH]; + static_assert(sizeof(flags) == sizeof(gRepairableDamageFlags), "wrong size"); + memcpy(flags, gRepairableDamageFlags, sizeof(gRepairableDamageFlags)); + + paletteFadeTo(gPaletteBlack); + + for (int index = 0; index < REPAIRABLE_DAMAGE_FLAGS_LENGTH; index++) { + if ((a2->data.critter.combat.results & flags[index]) != 0) { + damageHealingAttempts++; + + int roll = skillRoll(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 (!messageListGetItem(&gSkillsMessageList, &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 == gDude ? 520 : 521); + skillUpdateLastUse(SKILL_REPAIR); + + v1 = 1; + v2 = 1; + } else { + // 525: You fail to heal your %s. + // 526: You fail to heal the %s. + prefix.num = (a2 == gDude ? 525 : 526); + } + + if (!messageListGetItem(&gSkillsMessageList, &prefix)) { + return -1; + } + + sprintf(text, prefix.text, messageListItem.text); + displayMonitorAddMessage(text); + + _show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); + giveExp = false; + } + } + + int skillValue = skillGetValue(obj, skill); + int roll = randomRoll(skillValue, criticalChance, &hpToHeal); + + if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { + hpToHeal = randomBetween(minimumHpToHeal + 4, maximumHpToHeal + 10); + critterAdjustHitPoints(a2, hpToHeal); + + if (obj == gDude) { + // You heal %d hit points. + messageListItem.num = 500; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + if (maximumHp - currentHp < hpToHeal) { + hpToHeal = maximumHp - currentHp; + } + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + } + + if (!v2) { + skillUpdateLastUse(SKILL_REPAIR); + } + + a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; + + if (a2 == gDude) { + interfaceRenderHitPoints(true); + } + + v1 = 1; + _show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); + scriptsExecMapUpdateProc(); + paletteFadeTo(_cmap); + + giveExp = false; + } else { + // You fail to do any healing. + messageListItem.num = 503; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, hpToHeal); + displayMonitorAddMessage(text); + + scriptsExecMapUpdateProc(); + paletteFadeTo(_cmap); + } + } else { + if (obj == gDude) { + // 501: You look healty already + // 502: %s looks healthy already + messageListItem.num = (a2 == gDude ? 501 : 502); + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, objectGetName(a2)); + displayMonitorAddMessage(text); + + giveExp = false; + } + } + + if (obj == gDude) { + gameTimeAddSeconds(1800 * damageHealingAttempts); + } + + break; + default: + messageListItem.num = 510; // skill_use: invalid skill used. + if (messageListGetItem(&gSkillsMessageList, &messageListItem)) { + debugPrint(messageListItem.text); + } + + return -1; + } + + if (giveExp) { + _show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); + } + + if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) { + scriptsExecMapUpdateProc(); + } + + return 0; +} + +// 0x4ABBE4 +int skillsPerformStealing(Object* a1, Object* a2, Object* item, bool isPlanting) +{ + int howMuch; + + int stealModifier = _gStealCount; + stealModifier--; + stealModifier = -stealModifier; + + if (a1 != gDude || !perkHasRank(a1, PERK_PICKPOCKET)) { + // -4% per item size + stealModifier -= 4 * itemGetSize(item); + + if (((a2->fid & 0xF000000) >> 24) == 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 + skillGetValue(a1, SKILL_STEAL); + if (stealChance > 95) { + stealChance = 95; + } + + int stealRoll; + if (a1 == gDude && objectIsPartyMember(a2)) { + stealRoll = ROLL_CRITICAL_SUCCESS; + } else { + int criticalChance = critterGetStat(a1, STAT_CRITICAL_CHANCE); + stealRoll = randomRoll(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 ((a2->pid >> 24) == OBJ_TYPE_CRITTER) { + catchChance = skillGetValue(a2, SKILL_STEAL) - stealModifier; + } else { + catchChance = 30 - stealModifier; + } + + catchRoll = randomRoll(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 (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, objectGetName(item)); + displayMonitorAddMessage(text); + + return 1; + } else { + // 570: You're caught stealing the %s. + // 572: You're caught planting the %s. + messageListItem.num = isPlanting ? 572 : 570; + if (!messageListGetItem(&gSkillsMessageList, &messageListItem)) { + return -1; + } + + sprintf(text, messageListItem.text, objectGetName(item)); + displayMonitorAddMessage(text); + + return 0; + } +} + +// 0x4ABDEC +int skillGetGameDifficultyModifier(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; + configGetInt(&gGameConfig, 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 +int skillGetFreeUsageSlot(int skill) +{ + for (int slot = 0; slot < SKILLS_MAX_USES_PER_DAY; slot++) { + if (_timesSkillUsed[skill][slot] == 0) { + return slot; + } + } + + int time = gameTimeGetTime(); + int hoursSinceLastUsage = (time - _timesSkillUsed[skill][SKILLS_MAX_USES_PER_DAY - 1]) / 36000; + if (hoursSinceLastUsage <= 24) { + return -1; + } + + return SKILLS_MAX_USES_PER_DAY - 1; +} + +// 0x4ABEB8 +int skillUpdateLastUse(int skill) +{ + int slot = skillGetFreeUsageSlot(skill); + if (slot == -1) { + return -1; + } + + if (_timesSkillUsed[skill][slot] != 0) { + for (int i = 0; i < slot - 1; i++) { + _timesSkillUsed[skill][i] = _timesSkillUsed[skill][i + 1]; + } + } + + _timesSkillUsed[skill][slot] = gameTimeGetTime(); + + return 0; +} + +// 0x4ABF3C +int skillsUsageSave(File* stream) +{ + return fileWriteInt32List(stream, (int*)_timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY); +} + +// 0x4ABF5C +int skillsUsageLoad(File* stream) +{ + return fileReadInt32List(stream, (int*)_timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY); +} + +// 0x4ABF7C +char* skillsGetGenericResponse(Object* critter, bool isDude) +{ + int baseMessageId; + int count; + + if (isDude) { + baseMessageId = 1100; + count = 4; + } else { + baseMessageId = 1000; + count = 5; + } + + int messageId = randomBetween(0, count); + + MessageListItem messageListItem; + char* msg = getmsg(&gSkillsMessageList, &messageListItem, baseMessageId + messageId); + return msg; +} diff --git a/src/skill.h b/src/skill.h new file mode 100644 index 0000000..63e787e --- /dev/null +++ b/src/skill.h @@ -0,0 +1,81 @@ +#ifndef SKILL_H +#define SKILL_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "proto_types.h" +#include "skill_defs.h" + +#include + +#define SKILLS_MAX_USES_PER_DAY (3) + +#define REPAIRABLE_DAMAGE_FLAGS_LENGTH (5) +#define HEALABLE_DAMAGE_FLAGS_LENGTH (5) + +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; + +extern const int gRepairableDamageFlags[REPAIRABLE_DAMAGE_FLAGS_LENGTH]; +extern const int gHealableDamageFlags[HEALABLE_DAMAGE_FLAGS_LENGTH]; + +extern SkillDescription gSkillDescriptions[SKILL_COUNT]; +extern int _gIsSteal; +extern int _gStealCount; +extern int _gStealSize; + +extern int _timesSkillUsed[SKILL_COUNT][SKILLS_MAX_USES_PER_DAY]; +extern int gTaggedSkills[NUM_TAGGED_SKILLS]; +extern MessageList gSkillsMessageList; + +int skillsInit(); +void skillsReset(); +void skillsExit(); +int skillsLoad(File* stream); +int skillsSave(File* stream); +void protoCritterDataResetSkills(CritterProtoData* data); +void skillsSetTagged(int* skills, int count); +void skillsGetTagged(int* skills, int count); +bool skillIsTagged(int skill); +int skillGetValue(Object* critter, int skill); +int skillGetDefaultValue(int skill); +int skillGetBaseValue(Object* critter, int skill); +int skillAdd(Object* critter, int skill); +int skillAddForce(Object* critter, int skill); +int skillsGetCost(int a1); +int skillSub(Object* critter, int skill); +int skillSubForce(Object* critter, int skill); +int skillRoll(Object* critter, int skill, int a3, int* a4); +char* skillGetName(int skill); +char* skillGetDescription(int skill); +char* skillGetAttributes(int skill); +int skillGetFrmId(int skill); +void _show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int a5); +int skillUse(Object* obj, Object* a2, int skill, int a4); +int skillsPerformStealing(Object* a1, Object* a2, Object* item, bool isPlanting); +int skillGetGameDifficultyModifier(int skill); +int skillGetFreeUsageSlot(int skill); +int skillUpdateLastUse(int skill); +int skillsUsageSave(File* stream); +int skillsUsageLoad(File* stream); +char* skillsGetGenericResponse(Object* critter, bool isDude); + +// Returns true if skill is valid. +static inline bool skillIsValid(int skill) +{ + return skill >= 0 && skill < SKILL_COUNT; +} + +#endif /* SKILL_H */ diff --git a/src/skill_defs.h b/src/skill_defs.h new file mode 100644 index 0000000..94b51ef --- /dev/null +++ b/src/skill_defs.h @@ -0,0 +1,32 @@ +#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 */ diff --git a/src/skilldex.c b/src/skilldex.c new file mode 100644 index 0000000..8e7e26d --- /dev/null +++ b/src/skilldex.c @@ -0,0 +1,360 @@ +#include "skilldex.h" + +#include "color.h" +#include "core.h" +#include "cycle.h" +#include "debug.h" +#include "draw.h" +#include "game.h" +#include "game_mouse.h" +#include "game_sound.h" +#include "map.h" +#include "memory.h" +#include "object.h" +#include "skill.h" +#include "text_font.h" +#include "window_manager.h" + +#include +#include + +// 0x51D43C +bool gSkilldexWindowIsoWasEnabled = false; + +// 0x51D440 +const int gSkilldexFrmIds[SKILLDEX_FRM_COUNT] = { + 121, + 119, + 120, + 8, + 9, + 170, +}; + +// Maps Skilldex options into skills. +// +// 0x51D458 +const int gSkilldexSkills[SKILLDEX_SKILL_COUNT] = { + SKILL_SNEAK, + SKILL_LOCKPICK, + SKILL_STEAL, + SKILL_TRAPS, + SKILL_FIRST_AID, + SKILL_DOCTOR, + SKILL_SCIENCE, + SKILL_REPAIR, +}; + +// 0x668088 +Size gSkilldexFrmSizes[SKILLDEX_FRM_COUNT]; + +// 0x6680B8 +unsigned char* gSkilldexButtonsData[SKILLDEX_SKILL_BUTTON_BUFFER_COUNT]; + +// skilldex.msg +// 0x6680F8 +MessageList gSkilldexMessageList; + +// 0x668100 +MessageListItem gSkilldexMessageListItem; + +// 0x668110 +unsigned char* gSkilldexFrmData[SKILLDEX_FRM_COUNT]; + +// 0x668128 +CacheEntry* gSkilldexFrmHandles[SKILLDEX_FRM_COUNT]; + +// 0x668140 +int gSkilldexWindow; + +// 0x668144 +unsigned char* gSkilldexWindowBuffer; + +// 0x668148 +int gSkilldexWindowOldFont; + +// skilldex_select +// 0x4ABFD0 +int skilldexOpen() +{ + if (skilldexWindowInit() == -1) { + debugPrint("\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) { + soundPlayFile("ib1p1xx1"); + rc = 0; + } else if (keyCode >= 501 && keyCode <= 509) { + rc = keyCode - 500; + } + } + + if (rc != 0) { + coreDelay(1000 / 9); + } + + skilldexWindowFree(); + + return rc; +} + +// 0x4AC054 +int skilldexWindowInit() +{ + gSkilldexWindowOldFont = fontGetCurrent(); + gSkilldexWindowIsoWasEnabled = false; + + gameMouseObjectsHide(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + if (!messageListInit(&gSkilldexMessageList)) { + return -1; + } + + char path[FILENAME_MAX]; + sprintf(path, "%s%s", asc_5186C8, "skilldex.msg"); + + if (!messageListLoad(&gSkilldexMessageList, path)) { + return -1; + } + + int frmIndex; + for (frmIndex = 0; frmIndex < SKILLDEX_FRM_COUNT; frmIndex++) { + int fid = buildFid(6, gSkilldexFrmIds[frmIndex], 0, 0, 0); + gSkilldexFrmData[frmIndex] = artLockFrameDataReturningSize(fid, &(gSkilldexFrmHandles[frmIndex]), &(gSkilldexFrmSizes[frmIndex].width), &(gSkilldexFrmSizes[frmIndex].height)); + if (gSkilldexFrmData[frmIndex] == NULL) { + break; + } + } + + if (frmIndex < SKILLDEX_FRM_COUNT) { + while (--frmIndex >= 0) { + artUnlock(gSkilldexFrmHandles[frmIndex]); + } + + messageListFree(&gSkilldexMessageList); + + return -1; + } + + bool cycle = false; + int buttonDataIndex; + for (buttonDataIndex = 0; buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; buttonDataIndex++) { + gSkilldexButtonsData[buttonDataIndex] = internal_malloc(gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].height * gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].width + 512); + if (gSkilldexButtonsData[buttonDataIndex] == NULL) { + break; + } + + // NOTE: Original code uses bitwise XOR. + cycle = !cycle; + + unsigned char* data; + int size; + if (cycle) { + size = gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width * gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].height; + data = gSkilldexFrmData[SKILLDEX_FRM_BUTTON_OFF]; + } else { + size = gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].width * gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].height; + data = gSkilldexFrmData[SKILLDEX_FRM_BUTTON_ON]; + } + + memcpy(gSkilldexButtonsData[buttonDataIndex], data, size); + } + + if (buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT) { + while (--buttonDataIndex >= 0) { + internal_free(gSkilldexButtonsData[buttonDataIndex]); + } + + for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { + artUnlock(gSkilldexFrmHandles[index]); + } + + messageListFree(&gSkilldexMessageList); + + return -1; + } + + gSkilldexWindow = windowCreate(640 - gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width - 4, + 379 - gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].height - 6, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].height, + 256, + WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); + if (gSkilldexWindow == -1) { + for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { + internal_free(gSkilldexButtonsData[index]); + } + + for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { + artUnlock(gSkilldexFrmHandles[index]); + } + + messageListFree(&gSkilldexMessageList); + + return -1; + } + + gSkilldexWindowIsoWasEnabled = isoDisable(); + + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + gSkilldexWindowBuffer = windowGetBuffer(gSkilldexWindow); + memcpy(gSkilldexWindowBuffer, + gSkilldexFrmData[SKILLDEX_FRM_BACKGROUND], + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width * gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].height); + + fontSetCurrent(103); + + // Render "SKILLDEX" title. + char* title = getmsg(&gSkilldexMessageList, &gSkilldexMessageListItem, 100); + fontDrawText(gSkilldexWindowBuffer + 14 * gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width + 55, + title, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width, + _colorTable[18979]); + + // Render skill values. + int valueY = 48; + for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) { + int value = skillGetValue(gDude, gSkilldexSkills[index]); + if (value == -1) { + value = 0; + } + + int hundreds = value / 100; + blitBufferToBuffer(gSkilldexFrmData[SKILLDEX_FRM_BIG_NUMBERS] + 14 * hundreds, + 14, + 24, + 336, + gSkilldexWindowBuffer + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width * valueY + 110, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width); + + int tens = (value % 100) / 10; + blitBufferToBuffer(gSkilldexFrmData[SKILLDEX_FRM_BIG_NUMBERS] + 14 * tens, + 14, + 24, + 336, + gSkilldexWindowBuffer + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width * valueY + 124, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width); + + int ones = (value % 100) % 10; + blitBufferToBuffer(gSkilldexFrmData[SKILLDEX_FRM_BIG_NUMBERS] + 14 * ones, + 14, + 24, + 336, + gSkilldexWindowBuffer + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width * valueY + 138, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width); + + valueY += 36; + } + + // Render skill buttons. + int lineHeight = fontGetLineHeight(); + + int buttonY = 45; + int nameY = ((gSkilldexFrmSizes[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(&gSkilldexMessageList, &gSkilldexMessageListItem, 102 + index)); + + int nameX = ((gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width - fontGetStringWidth(name)) / 2) + 1; + if (nameX < 0) { + nameX = 0; + } + + fontDrawText(gSkilldexButtonsData[index * 2] + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].width * nameY + nameX, + name, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_ON].width, + _colorTable[18979]); + + fontDrawText(gSkilldexButtonsData[index * 2 + 1] + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width * nameY + nameX, + name, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width, + _colorTable[14723]); + + int btn = buttonCreate(gSkilldexWindow, + 15, + buttonY, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BUTTON_OFF].height, + -1, + -1, + -1, + 501 + index, + gSkilldexButtonsData[index * 2], + gSkilldexButtonsData[index * 2 + 1], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (btn != -1) { + buttonSetCallbacks(btn, _gsound_lrg_butt_press, _gsound_lrg_butt_release); + } + + buttonY += 36; + } + + // Render "CANCEL" button. + char* cancel = getmsg(&gSkilldexMessageList, &gSkilldexMessageListItem, 101); + fontDrawText(gSkilldexWindowBuffer + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width * 337 + 72, + cancel, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width, + gSkilldexFrmSizes[SKILLDEX_FRM_BACKGROUND].width, + _colorTable[18979]); + + int cancelBtn = buttonCreate(gSkilldexWindow, + 48, + 338, + gSkilldexFrmSizes[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].width, + gSkilldexFrmSizes[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].height, + -1, + -1, + -1, + 500, + gSkilldexFrmData[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP], + gSkilldexFrmData[SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN], + NULL, + BUTTON_FLAG_TRANSPARENT); + if (cancelBtn != -1) { + buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); + } + + windowRefresh(gSkilldexWindow); + + return 0; +} + +// 0x4AC67C +void skilldexWindowFree() +{ + windowDestroy(gSkilldexWindow); + + for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { + internal_free(gSkilldexButtonsData[index]); + } + + for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { + artUnlock(gSkilldexFrmHandles[index]); + } + + messageListFree(&gSkilldexMessageList); + + fontSetCurrent(gSkilldexWindowOldFont); + + if (gSkilldexWindowIsoWasEnabled) { + isoEnable(); + } + + colorCycleEnable(); + + gameMouseSetCursor(MOUSE_CURSOR_ARROW); +} diff --git a/src/skilldex.h b/src/skilldex.h new file mode 100644 index 0000000..0cc81a7 --- /dev/null +++ b/src/skilldex.h @@ -0,0 +1,65 @@ +#ifndef SKILLDEX_H +#define SKILLDEX_H + +#include "art.h" +#include "geometry.h" +#include "message.h" + +#include + +#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; + +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; + +extern bool gSkilldexWindowIsoWasEnabled; +extern const int gSkilldexFrmIds[SKILLDEX_FRM_COUNT]; +extern const int gSkilldexSkills[SKILLDEX_SKILL_COUNT]; + +extern Size gSkilldexFrmSizes[SKILLDEX_FRM_COUNT]; +extern unsigned char* gSkilldexButtonsData[SKILLDEX_SKILL_BUTTON_BUFFER_COUNT]; +extern MessageList gSkilldexMessageList; +extern MessageListItem gSkilldexMessageListItem; +extern unsigned char* gSkilldexFrmData[SKILLDEX_FRM_COUNT]; +extern CacheEntry* gSkilldexFrmHandles[SKILLDEX_FRM_COUNT]; +extern int gSkilldexWindow; +extern unsigned char* gSkilldexWindowBuffer; +extern int gSkilldexWindowOldFont; + +int skilldexOpen(); +int skilldexWindowInit(); +void skilldexWindowFree(); + +#endif /* SKILLDEX_H */ diff --git a/src/sound.c b/src/sound.c new file mode 100644 index 0000000..1df2251 --- /dev/null +++ b/src/sound.c @@ -0,0 +1,1764 @@ +#include "sound.h" + +#include "debug.h" +#include "memory.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(Sound) == 156, "wrong size"); + +// 0x51D478 +STRUCT_51D478* _fadeHead = NULL; + +// 0x51D47C +STRUCT_51D478* _fadeFreeList = NULL; + +// 0x51D480 +unsigned int _fadeEventHandle = UINT_MAX; + +// 0x51D488 +MallocProc* gSoundMallocProc = soundMallocProcDefaultImpl; + +// 0x51D48C +ReallocProc* gSoundReallocProc = soundReallocProcDefaultImpl; + +// 0x51D490 +FreeProc* gSoundFreeProc = soundFreeProcDefaultImpl; + +// 0x51D494 +SoundFileIO gSoundDefaultFileIO = { + open, + close, + read, + write, + lseek, + tell, + filelength, + -1, +}; + +// 0x51D4B4 +SoundFileNameMangler* gSoundFileNameMangler = soundFileManglerDefaultImpl; + +// 0x51D4B8 +const char* gSoundErrorDescriptions[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 +int gSoundLastError; + +// 0x668154 +int _masterVol; + +// 0x668158 +LPDIRECTSOUNDBUFFER gDirectSoundPrimaryBuffer; + +// 0x66815C +int _sampleRate; + +// Number of sounds currently playing. +// +// 0x668160 +int _numSounds; + +// 0x668164 +int _deviceInit; + +// 0x668168 +int _dataSize; + +// 0x66816C +int _numBuffers; + +// 0x668170 +bool gSoundInitialized; + +// 0x668174 +Sound* gSoundListHead; + +// 0x668178 +LPDIRECTSOUND gDirectSound; + +// 0x4AC6F0 +void* soundMallocProcDefaultImpl(size_t size) +{ + return malloc(size); +} + +// 0x4AC6F8 +void* soundReallocProcDefaultImpl(void* ptr, size_t size) +{ + return realloc(ptr, size); +} + +// 0x4AC700 +void soundFreeProcDefaultImpl(void* ptr) +{ + free(ptr); +} + +// 0x4AC708 +void soundSetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) +{ + gSoundMallocProc = mallocProc; + gSoundReallocProc = reallocProc; + gSoundFreeProc = freeProc; +} + +// 0x4AC78C +char* soundFileManglerDefaultImpl(char* fname) +{ + return fname; +} + +// 0x4AC790 +const char* soundGetErrorDescription(int err) +{ + if (err == -1) { + err = gSoundLastError; + } + + if (err < 0 || err > SOUND_UNKNOWN_ERROR) { + err = SOUND_UNKNOWN_ERROR; + } + + return gSoundErrorDescriptions[err]; +} + +// 0x4AC7B0 +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) { + debugPrint("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); + debugPrint("Resetting readBuffers from %d to %d\n", v53, (audioBytes1 + audioBytes2) / sound->field_7C); + + v53 = (audioBytes1 + audioBytes2) / sound->field_7C; + if (v53 < sound->field_5C) { + debugPrint("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 { + debugPrint("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 (gDirectSoundCreateProc(0, &gDirectSound, 0) != DS_OK) { + gDirectSound = NULL; + gSoundLastError = SOUND_SOS_DETECTION_FAILURE; + return gSoundLastError; + } + + if (IDirectSound_SetCooperativeLevel(gDirectSound, gProgramWindow, DSSCL_EXCLUSIVE) != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + _sampleRate = rate; + _dataSize = a4; + _numBuffers = a2; + gSoundInitialized = true; + _deviceInit = 1; + + DSBUFFERDESC dsbdesc; + memset(&dsbdesc, 0, sizeof(dsbdesc)); + dsbdesc.dwSize = sizeof(dsbdesc); + dsbdesc.dwFlags = DSCAPS_PRIMARYMONO; + dsbdesc.dwBufferBytes = 0; + + hr = IDirectSound_CreateSoundBuffer(gDirectSound, &dsbdesc, &gDirectSoundPrimaryBuffer, NULL); + if (hr != DS_OK) { + switch (hr) { + case DSERR_ALLOCATED: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_ALLOCATED"); + break; + case DSERR_BADFORMAT: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_BADFORMAT"); + break; + case DSERR_INVALIDPARAM: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_INVALIDPARAM"); + break; + case DSERR_NOAGGREGATION: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_NOAGGREGATION"); + break; + case DSERR_OUTOFMEMORY: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_OUTOFMEMORY"); + break; + case DSERR_UNINITIALIZED: + debugPrint("%s:%s\n", "CreateSoundBuffer", "DSERR_UNINITIALIZED"); + break; + case DSERR_UNSUPPORTED: + debugPrint("%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(gDirectSound, &dscaps); + if (hr != DS_OK) { + debugPrint("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; + + debugPrint("soundInit: Setting primary buffer to: %d bit, %d channels, %d rate\n", pcmwf.wBitsPerSample, pcmwf.nChannels, rate); + hr = IDirectSoundBuffer_SetFormat(gDirectSoundPrimaryBuffer, &pcmwf); + if (hr != DS_OK) { + debugPrint("soundInit: Couldn't change rate to %d\n", rate); + + switch (hr) { + case DSERR_BADFORMAT: + debugPrint("%s:%s\n", "SetFormat", "DSERR_BADFORMAT"); + break; + case DSERR_INVALIDCALL: + debugPrint("%s:%s\n", "SetFormat", "DSERR_INVALIDCALL"); + break; + case DSERR_INVALIDPARAM: + debugPrint("%s:%s\n", "SetFormat", "DSERR_INVALIDPARAM"); + break; + case DSERR_OUTOFMEMORY: + debugPrint("%s:%s\n", "SetFormat", "DSERR_OUTOFMEMORY"); + break; + case DSERR_PRIOLEVELNEEDED: + debugPrint("%s:%s\n", "SetFormat", "DSERR_PRIOLEVELNEEDED"); + break; + case DSERR_UNSUPPORTED: + debugPrint("%s:%s\n", "SetFormat", "DSERR_UNSUPPORTED"); + break; + } + + goto out; + } + + hr = IDirectSoundBuffer_GetFormat(gDirectSoundPrimaryBuffer, &pcmwf, sizeof(WAVEFORMATEX), &v24); + if (hr != DS_OK) { + debugPrint("soundInit: Couldn't read new settings\n"); + goto out; + } + + debugPrint("soundInit: Primary buffer settings set to: %d bit, %d channels, %d rate\n", pcmwf.wBitsPerSample, pcmwf.nChannels, pcmwf.nSamplesPerSec); + + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) { + debugPrint("soundInit: using DirectSound emulated drivers\n"); + } + +out: + + _soundSetMasterVolume(VOLUME_MAX); + gSoundLastError = SOUND_NO_ERROR; + + return 0; +} + +// 0x4AD04C +void soundExit() +{ + while (gSoundListHead != NULL) { + Sound* next = gSoundListHead->next; + soundDelete(gSoundListHead); + gSoundListHead = next; + } + + if (_fadeEventHandle != -1) { + _removeTimedEvent(&_fadeEventHandle); + } + + while (_fadeFreeList != NULL) { + STRUCT_51D478* next = _fadeFreeList->next; + gSoundFreeProc(_fadeFreeList); + _fadeFreeList = next; + } + + if (gDirectSoundPrimaryBuffer != NULL) { + IDirectSoundBuffer_Release(gDirectSoundPrimaryBuffer); + gDirectSoundPrimaryBuffer = NULL; + } + + if (gDirectSound != NULL) { + IDirectSound_Release(gDirectSound); + gDirectSound = NULL; + } + + gSoundLastError = SOUND_NO_ERROR; + gSoundInitialized = false; +} + +// 0x4AD0FC +Sound* soundAllocate(int a1, int a2) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return NULL; + } + + Sound* sound = gSoundMallocProc(sizeof(*sound)); + memset(sound, 0, sizeof(*sound)); + + memcpy(&(sound->io), &gSoundDefaultFileIO, sizeof(gSoundDefaultFileIO)); + + WAVEFORMATEX* wfxFormat = gSoundMallocProc(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 = gSoundListHead; + + if (gSoundListHead != NULL) { + gSoundListHead->prev = sound; + } + + gSoundListHead = sound; + + return sound; +} + +// 0x4AD308 +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*)gSoundMallocProc(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); + gSoundFreeProc(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*)gSoundMallocProc(sound->field_7C); + } + } + + return result; +} + +// 0x4AD498 +int soundLoad(Sound* sound, char* filePath) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + sound->io.fd = sound->io.open(gSoundFileNameMangler(filePath), 0x0200); + if (sound->io.fd == -1) { + gSoundLastError = SOUND_FILE_NOT_FOUND; + return gSoundLastError; + } + + return _preloadBuffers(sound); +} + +// 0x4AD504 +int _soundRewind(Sound* sound) +{ + HRESULT hr; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + sound->field_40 &= ~(0x01); + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AD5C8 +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) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + 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) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AD6C0 +int _soundSetData(Sound* sound, unsigned char* buf, int size) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (sound->directSoundBuffer == NULL) { + sound->directSoundBufferDescription.dwBufferBytes = size; + + if (IDirectSound_CreateSoundBuffer(gDirectSound, &(sound->directSoundBufferDescription), &(sound->directSoundBuffer), NULL) != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + } + + return _addSoundData(sound, buf, size); +} + +// 0x4AD73C +int soundPlay(Sound* sound) +{ + HRESULT hr; + DWORD readPos; + DWORD writePos; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + // TODO: Check. + if (sound->field_40 & 0x01) { + _soundRewind(sound); + } + + soundSetVolume(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) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + sound->field_40 |= SOUND_FLAG_SOUND_IS_PLAYING; + + ++_numSounds; + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AD828 +int soundStop(Sound* sound) +{ + HRESULT hr; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) { + gSoundLastError = SOUND_NOT_PLAYING; + return gSoundLastError; + } + + hr = IDirectSoundBuffer_Stop(sound->directSoundBuffer); + if (hr != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + sound->field_40 &= ~SOUND_FLAG_SOUND_IS_PLAYING; + _numSounds--; + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AD8DC +int soundDelete(Sound* sample) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sample == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (sample->io.fd != -1) { + sample->io.close(sample->io.fd); + sample->io.fd = -1; + } + + soundDeleteInternal(sample); + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AD948 +int soundContinue(Sound* sound) +{ + HRESULT hr; + DWORD status; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) || (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) { + gSoundLastError = SOUND_NOT_PLAYING; + return gSoundLastError; + } + + if (sound->field_40 & 0x01) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + hr = IDirectSoundBuffer_GetStatus(sound->directSoundBuffer, &status); + if (hr != DS_OK) { + debugPrint("Error in soundContinue, %x\n", hr); + + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + 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 |= 0x01; + + if (sound->field_40 & 0x02) { + --_numSounds; + } + + soundStop(sound); + + sound->field_40 &= ~(0x03); + } + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4ADA84 +bool soundIsPlaying(Sound* sound) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return false; + } + + if (sound == NULL || sound->directSoundBuffer == 0) { + gSoundLastError = SOUND_NO_SOUND; + return false; + } + + return (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0; +} + +// 0x4ADAC4 +bool _soundDone(Sound* sound) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return false; + } + + if (sound == NULL || sound->directSoundBuffer == 0) { + gSoundLastError = SOUND_NO_SOUND; + return false; + } + + return sound->field_40 & 1; +} + +// 0x4ADB44 +bool soundIsPaused(Sound* sound) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return false; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return false; + } + + return (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) != 0; +} + +// 0x4ADBC4 +int _soundType(Sound* sound, int a2) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return 0; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return 0; + } + + return sound->field_44 & a2; +} + +// 0x4ADC04 +int soundGetDuration(Sound* sound) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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 soundSetLooping(Sound* sound, int a2) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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); + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 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 soundSetVolume(Sound* sound, int volume) +{ + int normalizedVolume; + HRESULT hr; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + sound->volume = volume; + + if (sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; + } + + normalizedVolume = _soundVolumeHMItoDirectSound(_masterVol * volume / VOLUME_MAX); + + hr = IDirectSoundBuffer_SetVolume(sound->directSoundBuffer, normalizedVolume); + if (hr != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4ADE80 +int _soundGetVolume(Sound* sound) +{ + long volume; + int v13; + int v8; + int diff; + + if (!_deviceInit) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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) { + debugPrint("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 (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + sound->callback = callback; + sound->callbackUserData = userData; + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AE02C +int soundSetChannels(Sound* sound, int channels) +{ + LPWAVEFORMATEX format; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (channels == 3) { + format = sound->directSoundBufferDescription.lpwfxFormat; + + format->nBlockAlign = (2 * format->wBitsPerSample) / 8; + format->nChannels = 2; + format->nAvgBytesPerSec = format->nBlockAlign * _sampleRate; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AE0B0 +int soundSetReadLimit(Sound* sound, int readLimit) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_DEVICE; + return gSoundLastError; + } + + sound->readLimit = readLimit; + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// TODO: Check, looks like it uses couple of inlined functions. +// +// 0x4AE0E4 +int soundPause(Sound* sound) +{ + HRESULT hr; + DWORD readPos; + DWORD writePos; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) { + gSoundLastError = SOUND_NOT_PLAYING; + return gSoundLastError; + } + + if (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) { + gSoundLastError = SOUND_ALREADY_PAUSED; + return gSoundLastError; + } + + hr = IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos); + if (hr != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + 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 soundResume(Sound* sound) +{ + HRESULT hr; + + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL || sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if ((sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0) { + gSoundLastError = SOUND_NOT_PAUSED; + return gSoundLastError; + } + + if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) { + gSoundLastError = SOUND_NOT_PAUSED; + return gSoundLastError; + } + + hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, sound->field_48); + if (hr != DS_OK) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + 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 (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AE378 +void soundDeleteInternal(Sound* sound) +{ + STRUCT_51D478* curr; + Sound* v10; + Sound* v11; + + if (sound->field_40 & 0x04) { + curr = _fadeHead; + + while (curr != NULL) { + if (sound == curr->field_0) { + break; + } + + curr = curr->next; + } + + _removeFadeSound(curr); + } + + if (sound->directSoundBuffer != NULL) { + // NOTE: Uninline. + if (!soundIsPlaying(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) { + gSoundFreeProc(sound->field_20); + sound->field_20 = NULL; + } + + if (sound->directSoundBufferDescription.lpwfxFormat != NULL) { + gSoundFreeProc(sound->directSoundBufferDescription.lpwfxFormat); + } + + v10 = sound->next; + if (v10 != NULL) { + v10->prev = sound->prev; + } + + v11 = sound->prev; + if (v11 != NULL) { + v11->next = sound->next; + } else { + gSoundListHead = sound->next; + } + + gSoundFreeProc(sound); +} + +// 0x4AE578 +int _soundSetMasterVolume(int volume) +{ + if (volume < VOLUME_MIN || volume > VOLUME_MAX) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + _masterVol = volume; + + Sound* curr = gSoundListHead; + while (curr != NULL) { + soundSetVolume(curr, curr->volume); + curr = curr->next; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AE5C8 +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 +void _removeTimedEvent(unsigned int* timerId) +{ + if (*timerId != -1) { + timeKillEvent(*timerId); + *timerId = -1; + } +} + +// 0x4AE634 +int _soundGetPosition(Sound* sound) +{ + if (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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 (!gSoundInitialized) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + if (sound->directSoundBuffer == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + 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); + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AE830 +void _removeFadeSound(STRUCT_51D478* a1) +{ + STRUCT_51D478* prev; + STRUCT_51D478* next; + STRUCT_51D478* tmp; + + if (a1 == NULL) { + return; + } + + if (a1->field_0 == NULL) { + return; + } + + if (!(a1->field_0->field_40 & 0x04)) { + return; + } + + prev = a1->prev; + if (prev != NULL) { + prev->next = a1->next; + } else { + _fadeHead = a1->next; + } + + next = a1->next; + if (next != NULL) { + next->prev = a1->prev; + } + + a1->field_0->field_40 &= ~(0x04); + a1->field_0 = NULL; + + tmp = _fadeFreeList; + _fadeFreeList = a1; + a1->next = tmp; +} + +// 0x4AE8B0 +void _fadeSounds() +{ + STRUCT_51D478* ptr; + + ptr = _fadeHead; + while (ptr != NULL) { + if ((ptr->field_10 > ptr->field_8 || ptr->field_10 + ptr->field_4 < ptr->field_8) && (ptr->field_10 < ptr->field_8 || ptr->field_10 + ptr->field_4 > ptr->field_8)) { + ptr->field_10 += ptr->field_4; + soundSetVolume(ptr->field_0, ptr->field_10); + } else { + if (ptr->field_8 == 0) { + if (ptr->field_14) { + soundPause(ptr->field_0); + soundSetVolume(ptr->field_0, ptr->field_C); + } else { + if (ptr->field_0->field_44 & 0x04) { + soundDelete(ptr->field_0); + } else { + soundStop(ptr->field_0); + + ptr->field_C = ptr->field_8; + ptr->field_10 = ptr->field_8; + ptr->field_4 = 0; + + soundSetVolume(ptr->field_0, ptr->field_8); + } + } + } + + _removeFadeSound(ptr); + } + } + + if (_fadeHead == NULL && _fadeEventHandle != -1) { + timeKillEvent(_fadeEventHandle); + _fadeEventHandle = -1; + } +} + +// 0x4AE988 +int _internalSoundFade(Sound* sound, int a2, int a3, int a4) +{ + STRUCT_51D478* ptr; + + if (!_deviceInit) { + gSoundLastError = SOUND_NOT_INITIALIZED; + return gSoundLastError; + } + + if (sound == NULL) { + gSoundLastError = SOUND_NO_SOUND; + return gSoundLastError; + } + + ptr = NULL; + if (sound->field_40 & 0x04) { + ptr = _fadeHead; + while (ptr != NULL) { + if (ptr->field_0 == sound) { + break; + } + + ptr = ptr->next; + } + } + + if (ptr == NULL) { + if (_fadeFreeList != NULL) { + ptr = _fadeFreeList; + _fadeFreeList = _fadeFreeList->next; + } else { + ptr = (STRUCT_51D478*)gSoundMallocProc(sizeof(STRUCT_51D478)); + } + + if (ptr != NULL) { + if (_fadeHead != NULL) { + _fadeHead->prev = ptr; + } + + ptr->field_0 = sound; + ptr->prev = NULL; + ptr->next = _fadeHead; + _fadeHead = ptr; + } + } + + if (ptr == NULL) { + gSoundLastError = SOUND_NO_MEMORY_AVAILABLE; + return gSoundLastError; + } + + ptr->field_8 = a3; + ptr->field_C = _soundGetVolume(sound); + ptr->field_10 = ptr->field_C; + ptr->field_14 = a4; + // TODO: Check. + ptr->field_4 = 8 * (125 * (a3 - ptr->field_C)) / (40 * a2); + + sound->field_40 |= 0x04; + + bool v14; + if (gSoundInitialized) { + if (sound->directSoundBuffer != NULL) { + v14 = (sound->field_40 & 0x02) == 0; + } else { + gSoundLastError = SOUND_NO_SOUND; + v14 = true; + } + } else { + gSoundLastError = SOUND_NOT_INITIALIZED; + v14 = true; + } + + if (v14) { + soundPlay(sound); + } + + if (_fadeEventHandle != -1) { + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; + } + + _fadeEventHandle = timeSetEvent(40, 10, _doTimerEvent, (DWORD_PTR)_fadeSounds, 1); + if (_fadeEventHandle == 0) { + gSoundLastError = SOUND_UNKNOWN_ERROR; + return gSoundLastError; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} + +// 0x4AEB0C +int _soundFade(Sound* sound, int a2, int a3) +{ + return _internalSoundFade(sound, a2, a3, 0); +} + +// 0x4AEB54 +void soundDeleteAll() +{ + while (gSoundListHead != NULL) { + soundDelete(gSoundListHead); + } +} + +// 0x4AEBE0 +void soundContinueAll() +{ + Sound* curr = gSoundListHead; + 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) { + gSoundDefaultFileIO.open = openProc; + } + + if (closeProc != NULL) { + gSoundDefaultFileIO.close = closeProc; + } + + if (readProc != NULL) { + gSoundDefaultFileIO.read = readProc; + } + + if (writeProc != NULL) { + gSoundDefaultFileIO.write = writeProc; + } + + if (seekProc != NULL) { + gSoundDefaultFileIO.seek = seekProc; + } + + if (tellProc != NULL) { + gSoundDefaultFileIO.tell = tellProc; + } + + if (fileLengthProc != NULL) { + gSoundDefaultFileIO.filelength = fileLengthProc; + } + + gSoundLastError = SOUND_NO_ERROR; + return gSoundLastError; +} diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 0000000..5443636 --- /dev/null +++ b/src/sound.h @@ -0,0 +1,190 @@ +#ifndef SOUND_H +#define SOUND_H + +#include "memory_defs.h" +#include "win32.h" + +#define SOUND_FLAG_SOUND_IS_PLAYING (0x02) +#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 int 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; + +typedef struct STRUCT_51D478 { + Sound* field_0; + int field_4; + int field_8; + int field_C; + int field_10; + int field_14; + struct STRUCT_51D478* prev; + struct STRUCT_51D478* next; +} STRUCT_51D478; + +extern STRUCT_51D478* _fadeHead; +extern STRUCT_51D478* _fadeFreeList; + +extern unsigned int _fadeEventHandle; +extern MallocProc* gSoundMallocProc; +extern ReallocProc* gSoundReallocProc; +extern FreeProc* gSoundFreeProc; +extern SoundFileIO gSoundDefaultFileIO; +extern SoundFileNameMangler* gSoundFileNameMangler; +extern const char* gSoundErrorDescriptions[SOUND_ERR_COUNT]; + +extern int gSoundLastError; +extern int _masterVol; +extern LPDIRECTSOUNDBUFFER gDirectSoundPrimaryBuffer; +extern int _sampleRate; +extern int _numSounds; +extern int _deviceInit; +extern int _dataSize; +extern int _numBuffers; +extern bool gSoundInitialized; +extern Sound* gSoundListHead; +extern LPDIRECTSOUND gDirectSound; + +void* soundMallocProcDefaultImpl(size_t size); +void* soundReallocProcDefaultImpl(void* ptr, size_t size); +void soundFreeProcDefaultImpl(void* ptr); +void soundSetMemoryProcs(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); + +char* soundFileManglerDefaultImpl(char* fname); +const char* soundGetErrorDescription(int err); +void _refreshSoundBuffers(Sound* sound); +int soundInit(int a1, int a2, int a3, int a4, int rate); +void soundExit(); +Sound* soundAllocate(int a1, int a2); +int _preloadBuffers(Sound* sound); +int soundLoad(Sound* sound, char* filePath); +int _soundRewind(Sound* sound); +int _addSoundData(Sound* sound, unsigned char* buf, int size); +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 soundIsPlaying(Sound* sound); +bool _soundDone(Sound* sound); +bool soundIsPaused(Sound* sound); +int _soundType(Sound* sound, int a2); +int soundGetDuration(Sound* sound); +int soundSetLooping(Sound* sound, int a2); +int _soundVolumeHMItoDirectSound(int a1); +int soundSetVolume(Sound* sound, int volume); +int _soundGetVolume(Sound* sound); +int soundSetCallback(Sound* sound, SoundCallback* callback, void* userData); +int soundSetChannels(Sound* sound, int channels); +int soundSetReadLimit(Sound* sound, int readLimit); +int soundPause(Sound* sound); +int soundResume(Sound* sound); +int soundSetFileIO(Sound* sound, SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc); +void soundDeleteInternal(Sound* sound); +int _soundSetMasterVolume(int value); +void CALLBACK _doTimerEvent(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2); +void _removeTimedEvent(unsigned int* timerId); +int _soundGetPosition(Sound* sound); +int _soundSetPosition(Sound* sound, int a2); +void _removeFadeSound(STRUCT_51D478* a1); +void _fadeSounds(); +int _internalSoundFade(Sound* sound, int a2, int a3, int a4); +int _soundFade(Sound* sound, int a2, int a3); +void soundDeleteAll(); +void soundContinueAll(); +int soundSetDefaultFileIO(SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc); + +#endif /* SOUND_H */ diff --git a/src/sound_decoder.c b/src/sound_decoder.c new file mode 100644 index 0000000..394c2fa --- /dev/null +++ b/src/sound_decoder.c @@ -0,0 +1,1232 @@ +// 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 = 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 = 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 = malloc(sizeof(unsigned char*) * v73); + if (soundDecoder->field_30 == NULL) { + goto L66; + } + + memset(soundDecoder->field_30, 0, sizeof(unsigned char*) * v73); + } + + soundDecoder->field_34 = malloc(sizeof(unsigned char*) * soundDecoder->field_2C); + if (soundDecoder->field_34 == NULL) { + goto L66; + } + + soundDecoder->field_50 = 0; + + if (gSoundDecodersCount == 1) { + _AudioDecoder_scale_tbl = 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; +} diff --git a/src/sound_decoder.h b/src/sound_decoder.h new file mode 100644 index 0000000..b165c43 --- /dev/null +++ b/src/sound_decoder.h @@ -0,0 +1,85 @@ +#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 */ diff --git a/src/sound_effects_cache.c b/src/sound_effects_cache.c new file mode 100644 index 0000000..20b48d5 --- /dev/null +++ b/src/sound_effects_cache.c @@ -0,0 +1,499 @@ +#include "sound_effects_cache.h" + +#include "db.h" +#include "game_config.h" +#include "memory.h" +#include "sound_decoder.h" +#include "sound_effects_list.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(SoundEffect) == 32, "wrong size"); + +// 0x50DE04 +const char* off_50DE04 = ""; + +// sfxc_initialized +// 0x51C8F0 +bool gSoundEffectsCacheInitialized = false; + +// 0x51C8F4 +int _sfxc_cmpr = 1; + +// sfxc_pcache +// 0x51C8EC +Cache* gSoundEffectsCache = NULL; + +// sfxc_dlevel +// 0x51C8DC +int gSoundEffectsCacheDebugLevel = INT_MAX; + +// 0x51C8E0 +char* gSoundEffectsCacheEffectsPath = NULL; + +// 0x51C8E4 +SoundEffect* gSoundEffects = NULL; + +// 0x51C8E8 +int _sfxc_files_open = 0; + +// sfxc_init +// 0x4A8FC0 +int soundEffectsCacheInit(int cacheSize, const char* effectsPath) +{ + if (!configGetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_SFXC_KEY, &gSoundEffectsCacheDebugLevel)) { + gSoundEffectsCacheDebugLevel = 1; + } + + if (cacheSize <= SOUND_EFFECTS_CACHE_MIN_SIZE) { + return -1; + } + + if (effectsPath == NULL) { + effectsPath = off_50DE04; + } + + gSoundEffectsCacheEffectsPath = internal_strdup(effectsPath); + if (gSoundEffectsCacheEffectsPath == NULL) { + return -1; + } + + if (soundEffectsListInit(gSoundEffectsCacheEffectsPath, _sfxc_cmpr, gSoundEffectsCacheDebugLevel) != SFXL_OK) { + internal_free(gSoundEffectsCacheEffectsPath); + return -1; + } + + if (soundEffectsCacheCreateHandles() != 0) { + soundEffectsListExit(); + internal_free(gSoundEffectsCacheEffectsPath); + return -1; + } + + gSoundEffectsCache = internal_malloc(sizeof(*gSoundEffectsCache)); + if (gSoundEffectsCache == NULL) { + soundEffectsCacheFreeHandles(); + soundEffectsListExit(); + internal_free(gSoundEffectsCacheEffectsPath); + return -1; + } + + if (!cacheInit(gSoundEffectsCache, soundEffectsCacheGetFileSizeImpl, soundEffectsCacheReadDataImpl, soundEffectsCacheFreeImpl, cacheSize)) { + internal_free(gSoundEffectsCache); + soundEffectsCacheFreeHandles(); + soundEffectsListExit(); + internal_free(gSoundEffectsCacheEffectsPath); + return -1; + } + + gSoundEffectsCacheInitialized = true; + + return 0; +} + +// 0x4A90FC +void soundEffectsCacheExit() +{ + if (gSoundEffectsCacheInitialized) { + cacheFree(gSoundEffectsCache); + internal_free(gSoundEffectsCache); + gSoundEffectsCache = NULL; + + soundEffectsCacheFreeHandles(); + + soundEffectsListExit(); + + internal_free(gSoundEffectsCacheEffectsPath); + + gSoundEffectsCacheInitialized = false; + } +} + +// 0x4A9140 +int soundEffectsCacheInitialized() +{ + return gSoundEffectsCacheInitialized; +} + +// 0x4A9148 +void soundEffectsCacheFlush() +{ + if (gSoundEffectsCacheInitialized) { + cacheFlush(gSoundEffectsCache); + } +} + +// sfxc_cached_open +// 0x4A915C +int soundEffectsCacheFileOpen(const char* fname, int mode, ...) +{ + if (_sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) { + return -1; + } + + char* copy = internal_strdup(fname); + if (copy == NULL) { + return -1; + } + + int tag; + int err = soundEffectsListGetTag(copy, &tag); + + internal_free(copy); + + if (err != SFXL_OK) { + return -1; + } + + void* data; + CacheEntry* cacheHandle; + if (cacheLock(gSoundEffectsCache, tag, &data, &cacheHandle) == -1) { + return -1; + } + + int handle; + if (soundEffectsCreate(&handle, tag, data, cacheHandle) != 0) { + cacheUnlock(gSoundEffectsCache, cacheHandle); + return -1; + } + + return handle; +} + +// sfxc_cached_close +// 0x4A9220 +int soundEffectsCacheFileClose(int handle) +{ + if (!soundEffectsIsValidHandle(handle)) { + return -1; + } + + SoundEffect* soundEffect = &(gSoundEffects[handle]); + if (!cacheUnlock(gSoundEffectsCache, soundEffect->cacheHandle)) { + return -1; + } + + // FIXME: This is check is redundant and implemented incorrectly. There is + // an overflow when handle == SOUND_EFFECTS_MAX_COUNT, but thanks to + // [soundEffectsIsValidHandle] handle will always be less than + // [SOUND_EFFECTS_MAX_COUNT]. + if (handle <= SOUND_EFFECTS_MAX_COUNT) { + soundEffect->used = false; + } + + return 0; +} + +// 0x4A9274 +int soundEffectsCacheFileRead(int handle, void* buf, unsigned int size) +{ + if (!soundEffectsIsValidHandle(handle)) { + return -1; + } + + if (size == 0) { + return 0; + } + + SoundEffect* soundEffect = &(gSoundEffects[handle]); + if (soundEffect->dataSize - soundEffect->position <= 0) { + return 0; + } + + size_t bytesToRead; + if (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 (soundEffectsCacheFileReadCompressed(handle, buf, bytesToRead) != 0) { + return -1; + } + break; + default: + return -1; + } + + soundEffect->position += bytesToRead; + + return bytesToRead; +} + +// 0x4A9350 +int soundEffectsCacheFileWrite(int handle, const void* buf, unsigned int size) +{ + return -1; +} + +// 0x4A9358 +int soundEffectsCacheFileSeek(int handle, long offset, int origin) +{ + if (!soundEffectsIsValidHandle(handle)) { + return -1; + } + + SoundEffect* soundEffect = &(gSoundEffects[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 soundEffectsCacheFileTell(int handle) +{ + if (!soundEffectsIsValidHandle(handle)) { + return -1; + } + + SoundEffect* soundEffect = &(gSoundEffects[handle]); + return soundEffect->position; +} + +// 0x4A9418 +long soundEffectsCacheFileLength(int handle) +{ + if (!soundEffectsIsValidHandle(handle)) { + return 0; + } + + SoundEffect* soundEffect = &(gSoundEffects[handle]); + return soundEffect->dataSize; +} + +// sfxc_cached_file_size +// 0x4A9434 +int soundEffectsCacheGetFileSizeImpl(int tag, int* sizePtr) +{ + int size; + if (soundEffectsListGetFileSize(tag, &size) == -1) { + return -1; + } + + *sizePtr = size; + + return 0; +} + +// 0x4A945C +int soundEffectsCacheReadDataImpl(int tag, int* sizePtr, unsigned char* data) +{ + if (!soundEffectsListIsValidTag(tag)) { + return -1; + } + + int size; + soundEffectsListGetFileSize(tag, &size); + + char* name; + soundEffectsListGetFilePath(tag, &name); + + if (dbGetFileContents(name, data)) { + internal_free(name); + return -1; + } + + internal_free(name); + + *sizePtr = size; + + return 0; +} + +// 0x4A94CC +void soundEffectsCacheFreeImpl(void* ptr) +{ + internal_free(ptr); +} + +// 0x4A94D4 +int soundEffectsCacheCreateHandles() +{ + gSoundEffects = internal_malloc(sizeof(*gSoundEffects) * SOUND_EFFECTS_MAX_COUNT); + if (gSoundEffects == NULL) { + return -1; + } + + for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) { + SoundEffect* soundEffect = &(gSoundEffects[index]); + soundEffect->used = false; + } + + _sfxc_files_open = 0; + + return 0; +} + +// 0x4A9518 +void soundEffectsCacheFreeHandles() +{ + if (_sfxc_files_open) { + for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) { + SoundEffect* soundEffect = &(gSoundEffects[index]); + if (!soundEffect->used) { + soundEffectsCacheFileClose(index); + } + } + } + + internal_free(gSoundEffects); +} + +// 0x4A9550 +int soundEffectsCreate(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 = &(gSoundEffects[index]); + if (!soundEffect->used) { + break; + } + } + + if (index == SOUND_EFFECTS_MAX_COUNT) { + return -1; + } + + soundEffect->used = true; + soundEffect->cacheHandle = cacheHandle; + soundEffect->tag = tag; + + soundEffectsListGetDataSize(tag, &(soundEffect->dataSize)); + soundEffectsListGetFileSize(tag, &(soundEffect->fileSize)); + + soundEffect->position = 0; + soundEffect->dataPosition = 0; + + soundEffect->data = data; + + *handlePtr = index; + + return 0; +} + +// 0x4A961C +bool soundEffectsIsValidHandle(int handle) +{ + if (handle >= SOUND_EFFECTS_MAX_COUNT) { + return false; + } + + SoundEffect* soundEffect = &gSoundEffects[handle]; + + if (!soundEffect->used) { + return false; + } + + if (soundEffect->dataSize < soundEffect->position) { + return false; + } + + return soundEffectsListIsValidTag(soundEffect->tag); +} + +// 0x4A967C +int soundEffectsCacheFileReadCompressed(int handle, void* buf, unsigned int size) +{ + if (!soundEffectsIsValidHandle(handle)) { + return -1; + } + + SoundEffect* soundEffect = &(gSoundEffects[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 = internal_malloc(soundEffect->position); + if (temp == NULL) { + soundDecoderFree(soundDecoder); + return -1; + } + + size_t bytesRead = soundDecoderDecode(soundDecoder, temp, soundEffect->position); + internal_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 +int _sfxc_ad_reader(int handle, void* buf, unsigned int size) +{ + if (size == 0) { + return 0; + } + + SoundEffect* soundEffect = &(gSoundEffects[handle]); + + int bytesToRead = soundEffect->fileSize - soundEffect->dataPosition; + if (size <= bytesToRead) { + bytesToRead = size; + } + + memcpy(buf, soundEffect->data + soundEffect->dataPosition, bytesToRead); + + soundEffect->dataPosition += bytesToRead; + + return bytesToRead; +} diff --git a/src/sound_effects_cache.h b/src/sound_effects_cache.h new file mode 100644 index 0000000..84d49cc --- /dev/null +++ b/src/sound_effects_cache.h @@ -0,0 +1,59 @@ +#ifndef SOUND_EFFECTS_CACHE_H +#define SOUND_EFFECTS_CACHE_H + +#include "cache.h" + +#include + +// The maximum number of sound effects that can be loaded and played +// simultaneously. +#define SOUND_EFFECTS_MAX_COUNT (4) + +#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; + +extern const char* off_50DE04; +extern bool gSoundEffectsCacheInitialized; +extern int _sfxc_cmpr; +extern Cache* gSoundEffectsCache; +extern int gSoundEffectsCacheDebugLevel; +extern char* gSoundEffectsCacheEffectsPath; +extern SoundEffect* gSoundEffects; +extern int _sfxc_files_open; + +int soundEffectsCacheInit(int cache_size, const char* effectsPath); +void soundEffectsCacheExit(); +int soundEffectsCacheInitialized(); +void soundEffectsCacheFlush(); +int soundEffectsCacheFileOpen(const char* fname, int mode, ...); +int soundEffectsCacheFileClose(int handle); +int soundEffectsCacheFileRead(int handle, void* buf, unsigned int size); +int soundEffectsCacheFileWrite(int handle, const void* buf, unsigned int size); +int soundEffectsCacheFileSeek(int handle, long offset, int origin); +long soundEffectsCacheFileTell(int handle); +long soundEffectsCacheFileLength(int handle); +int soundEffectsCacheGetFileSizeImpl(int tag, int* sizePtr); +int soundEffectsCacheReadDataImpl(int tag, int* sizePtr, unsigned char* data); +void soundEffectsCacheFreeImpl(void* ptr); +int soundEffectsCacheCreateHandles(); +void soundEffectsCacheFreeHandles(); +int soundEffectsCreate(int* handlePtr, int id, void* data, CacheEntry* cacheHandle); +bool soundEffectsIsValidHandle(int a1); +int soundEffectsCacheFileReadCompressed(int handle, void* buf, unsigned int size); +int _sfxc_ad_reader(int handle, void* buf, unsigned int size); + +#endif /* SOUND_EFFECTS_CACHE_H */ diff --git a/src/sound_effects_list.c b/src/sound_effects_list.c new file mode 100644 index 0000000..2cac757 --- /dev/null +++ b/src/sound_effects_list.c @@ -0,0 +1,451 @@ +#include "sound_effects_list.h" + +#include "db.h" +#include "debug.h" +#include "memory.h" +#include "sound_decoder.h" + +#include +#include +#include +#include + +// 0x51C8F8 +bool gSoundEffectsListInitialized = false; + +// 0x51C8FC +int gSoundEffectsListDebugLevel = INT_MAX; + +// sfxl_effect_path +// 0x51C900 +char* gSoundEffectsListPath = NULL; + +// sfxl_effect_path_len +// 0x51C904 +int gSoundEffectsListPathLength = 0; + +// sndlist.lst +// +// sfxl_list +// 0x51C908 +SoundEffectsListEntry* gSoundEffectsListEntries = NULL; + +// The length of [gSoundEffectsListEntries] array. +// +// 0x51C90C +int gSoundEffectsListEntriesLength = 0; + +// 0x667F94 +int _sfxl_compression; + +// sfxl_tag_is_legal +// 0x4A98E0 +bool soundEffectsListIsValidTag(int a1) +{ + return soundEffectsListTagToIndex(a1, NULL) == SFXL_OK; +} + +// sfxl_init +// 0x4A98F4 +int soundEffectsListInit(const char* soundEffectsPath, int a2, int debugLevel) +{ + char path[FILENAME_MAX]; + + // TODO: What for? + // memcpy(path, byte_4A97E0, 0xFF); + + gSoundEffectsListDebugLevel = debugLevel; + _sfxl_compression = a2; + gSoundEffectsListEntriesLength = 0; + + gSoundEffectsListPath = internal_strdup(soundEffectsPath); + if (gSoundEffectsListPath == NULL) { + return SFXL_ERR; + } + + gSoundEffectsListPathLength = strlen(gSoundEffectsListPath); + + if (gSoundEffectsListPathLength == 0 || soundEffectsPath[gSoundEffectsListPathLength - 1] == '\\') { + sprintf(path, "%sSNDLIST.LST", soundEffectsPath); + } else { + sprintf(path, "%s\\SNDLIST.LST", soundEffectsPath); + } + + File* stream = fileOpen(path, "rt"); + if (stream != NULL) { + fileReadString(path, 255, stream); + gSoundEffectsListEntriesLength = atoi(path); + + gSoundEffectsListEntries = internal_malloc(sizeof(*gSoundEffectsListEntries) * gSoundEffectsListEntriesLength); + for (int index = 0; index < gSoundEffectsListEntriesLength; index++) { + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + + fileReadString(path, 255, stream); + + // Remove trailing newline. + *(path + strlen(path) - 1) = '\0'; + entry->name = internal_strdup(path); + + fileReadString(path, 255, stream); + entry->dataSize = atoi(path); + + fileReadString(path, 255, stream); + entry->fileSize = atoi(path); + + fileReadString(path, 255, stream); + entry->tag = atoi(path); + } + + fileClose(stream); + + debugPrint("Reading SNDLIST.LST Sound FX Count: %d", gSoundEffectsListEntriesLength); + } else { + int err; + + err = soundEffectsListPopulateFileNames(); + if (err != SFXL_OK) { + internal_free(gSoundEffectsListPath); + return err; + } + + err = soundEffectsListPopulateFileSizes(); + if (err != SFXL_OK) { + soundEffectsListClear(); + internal_free(gSoundEffectsListPath); + 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 [soundEffectsListPopulateFileNames]). + // + // 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 [soundEffectsListGetTag]). + + // NOTE: Uninline. + soundEffectsListSort(); + + File* stream = fileOpen(path, "wt"); + if (stream != NULL) { + filePrintFormatted(stream, "%d\n", gSoundEffectsListEntriesLength); + + for (int index = 0; index < gSoundEffectsListEntriesLength; index++) { + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + + filePrintFormatted(stream, "%s\n", entry->name); + filePrintFormatted(stream, "%d\n", entry->dataSize); + filePrintFormatted(stream, "%d\n", entry->fileSize); + filePrintFormatted(stream, "%d\n", entry->tag); + } + + fileClose(stream); + } else { + debugPrint("SFXLIST: Can't open file for write %s\n", path); + } + } + + gSoundEffectsListInitialized = true; + + return SFXL_OK; +} + +// 0x4A9C04 +void soundEffectsListExit() +{ + if (gSoundEffectsListInitialized) { + soundEffectsListClear(); + internal_free(gSoundEffectsListPath); + gSoundEffectsListInitialized = false; + } +} + +// sfxl_name_to_tag +// 0x4A9C28 +int soundEffectsListGetTag(char* name, int* tagPtr) +{ + if (strnicmp(gSoundEffectsListPath, name, gSoundEffectsListPathLength) != 0) { + return SFXL_ERR; + } + + SoundEffectsListEntry dummy; + dummy.name = name + gSoundEffectsListPathLength; + + SoundEffectsListEntry* entry = bsearch(&dummy, gSoundEffectsListEntries, gSoundEffectsListEntriesLength, sizeof(*gSoundEffectsListEntries), soundEffectsListCompareByName); + if (entry == NULL) { + return SFXL_ERR; + } + + int index = entry - gSoundEffectsListEntries; + if (index < 0 || index >= gSoundEffectsListEntriesLength) { + return SFXL_ERR; + } + + *tagPtr = 2 * index + 2; + + return SFXL_OK; +} + +// sfxl_name +// 0x4A9CD8 +int soundEffectsListGetFilePath(int tag, char** pathPtr) +{ + int index; + int err = soundEffectsListTagToIndex(tag, &index); + if (err != SFXL_OK) { + return err; + } + + char* name = gSoundEffectsListEntries[index].name; + + char* path = internal_malloc(strlen(gSoundEffectsListPath) + strlen(name) + 1); + if (path == NULL) { + return SFXL_ERR; + } + + strcpy(path, gSoundEffectsListPath); + strcat(path, name); + + *pathPtr = path; + + return SFXL_OK; +} + +// 0x4A9D90 +int soundEffectsListGetDataSize(int tag, int* sizePtr) +{ + int index; + int rc = soundEffectsListTagToIndex(tag, &index); + if (rc != SFXL_OK) { + return rc; + } + + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + *sizePtr = entry->dataSize; + + return SFXL_OK; +} + +// 0x4A9DBC +int soundEffectsListGetFileSize(int tag, int* sizePtr) +{ + int index; + int err = soundEffectsListTagToIndex(tag, &index); + if (err != SFXL_OK) { + return err; + } + + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + *sizePtr = entry->fileSize; + + return SFXL_OK; +} + +// sfxl_tag_to_index +// 0x4A9DE8 +int soundEffectsListTagToIndex(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 >= gSoundEffectsListEntriesLength) { + return SFXL_ERR_TAG_INVALID; + } + + if (indexPtr != NULL) { + *indexPtr = index; + } + + return SFXL_OK; +} + +// 0x4A9E44 +void soundEffectsListClear() +{ + if (gSoundEffectsListEntriesLength < 0) { + return; + } + + if (gSoundEffectsListEntries == NULL) { + return; + } + + for (int index = 0; index < gSoundEffectsListEntriesLength; index++) { + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + if (entry->name != NULL) { + internal_free(entry->name); + } + } + + internal_free(gSoundEffectsListEntries); + gSoundEffectsListEntries = NULL; + + gSoundEffectsListEntriesLength = 0; +} + +// sfxl_get_names +// 0x4A9EA0 +int soundEffectsListPopulateFileNames() +{ + const char* extension; + switch (_sfxl_compression) { + case 0: + extension = "*.SND"; + break; + case 1: + extension = "*.ACM"; + break; + default: + return SFXL_ERR; + } + + char* pattern = internal_malloc(strlen(gSoundEffectsListPath) + strlen(extension) + 1); + if (pattern == NULL) { + return SFXL_ERR; + } + + strcpy(pattern, gSoundEffectsListPath); + strcat(pattern, extension); + + char** fileNameList; + gSoundEffectsListEntriesLength = fileNameListInit(pattern, &fileNameList, 0, 0); + internal_free(pattern); + + if (gSoundEffectsListEntriesLength > 10000) { + fileNameListFree(&fileNameList, 0); + return SFXL_ERR; + } + + if (gSoundEffectsListEntriesLength <= 0) { + return SFXL_ERR; + } + + gSoundEffectsListEntries = internal_malloc(sizeof(*gSoundEffectsListEntries) * gSoundEffectsListEntriesLength); + if (gSoundEffectsListEntries == NULL) { + fileNameListFree(&fileNameList, 0); + return SFXL_ERR; + } + + memset(gSoundEffectsListEntries, 0, sizeof(*gSoundEffectsListEntries) * gSoundEffectsListEntriesLength); + + int err = soundEffectsListCopyFileNames(fileNameList); + + fileNameListFree(&fileNameList, 0); + + if (err != SFXL_OK) { + soundEffectsListClear(); + return err; + } + + return SFXL_OK; +} + +// sfxl_copy_names +// 0x4AA000 +int soundEffectsListCopyFileNames(char** fileNameList) +{ + for (int index = 0; index < gSoundEffectsListEntriesLength; index++) { + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + entry->name = internal_strdup(*fileNameList++); + if (entry->name == NULL) { + soundEffectsListClear(); + return SFXL_ERR; + } + } + + return SFXL_OK; +} + +// 0x4AA050 +int soundEffectsListPopulateFileSizes() +{ + + char* path = internal_malloc(gSoundEffectsListPathLength + 13); + if (path == NULL) { + return SFXL_ERR; + } + + strcpy(path, gSoundEffectsListPath); + + char* fileName = path + gSoundEffectsListPathLength; + + for (int index = 0; index < gSoundEffectsListEntriesLength; index++) { + SoundEffectsListEntry* entry = &(gSoundEffectsListEntries[index]); + strcpy(fileName, entry->name); + + int fileSize; + if (dbGetFileSize(path, &fileSize) != 0) { + internal_free(path); + return SFXL_ERR; + } + + if (fileSize <= 0) { + internal_free(path); + return SFXL_ERR; + } + + entry->fileSize = fileSize; + + switch (_sfxl_compression) { + case 0: + entry->dataSize = fileSize; + break; + case 1: + if (1) { + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + internal_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); + fileClose(stream); + } + break; + default: + internal_free(path); + return SFXL_ERR; + } + } + + internal_free(path); + + return SFXL_OK; +} + +// NOTE: Inlined. +// +// 0x4AA200 +int soundEffectsListSort() +{ + if (gSoundEffectsListEntriesLength != 1) { + qsort(gSoundEffectsListEntries, gSoundEffectsListEntriesLength, sizeof(*gSoundEffectsListEntries), soundEffectsListCompareByName); + } + return 0; +} + +// 0x4AA228 +int soundEffectsListCompareByName(const void* a1, const void* a2) +{ + SoundEffectsListEntry* v1 = (SoundEffectsListEntry*)a1; + SoundEffectsListEntry* v2 = (SoundEffectsListEntry*)a2; + + return stricmp(v1->name, v2->name); +} + +// read via xfile +int _sfxl_ad_reader(int fileHandle, void* buf, unsigned int size) +{ + return fileRead(buf, 1, size, (File*)fileHandle); +} diff --git a/src/sound_effects_list.h b/src/sound_effects_list.h new file mode 100644 index 0000000..10341f9 --- /dev/null +++ b/src/sound_effects_list.h @@ -0,0 +1,41 @@ +#ifndef SOUND_EFFECTS_LIST_H +#define SOUND_EFFECTS_LIST_H + +#include + +#define SFXL_OK (0) +#define SFXL_ERR (1) +#define SFXL_ERR_TAG_INVALID (2) + +typedef struct SoundEffectsListEntry { + char* name; + int dataSize; + int fileSize; + int tag; +} SoundEffectsListEntry; + +extern bool gSoundEffectsListInitialized; +extern int gSoundEffectsListDebugLevel; +extern char* gSoundEffectsListPath; +extern int gSoundEffectsListPathLength; +extern SoundEffectsListEntry* gSoundEffectsListEntries; +extern int gSoundEffectsListEntriesLength; +extern int _sfxl_compression; + +bool soundEffectsListIsValidTag(int tag); +int soundEffectsListInit(const char* soundEffectsPath, int a2, int debugLevel); +void soundEffectsListExit(); +int soundEffectsListGetTag(char* name, int* tagPtr); +int soundEffectsListGetFilePath(int tag, char** pathPtr); +int soundEffectsListGetDataSize(int tag, int* sizePtr); +int soundEffectsListGetFileSize(int tag, int* sizePtr); +int soundEffectsListTagToIndex(int tag, int* indexPtr); +void soundEffectsListClear(); +int soundEffectsListPopulateFileNames(); +int soundEffectsListCopyFileNames(char** fileNameList); +int soundEffectsListPopulateFileSizes(); +int soundEffectsListSort(); +int soundEffectsListCompareByName(const void* a1, const void* a2); +int _sfxl_ad_reader(int fileHandle, void* buf, unsigned int size); + +#endif /* SOUND_EFFECTS_LIST_H */ diff --git a/src/stat.c b/src/stat.c new file mode 100644 index 0000000..87f434f --- /dev/null +++ b/src/stat.c @@ -0,0 +1,801 @@ +#include "stat.h" + +#include "combat.h" +#include "core.h" +#include "critter.h" +#include "display_monitor.h" +#include "game.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "memory.h" +#include "object.h" +#include "perk.h" +#include "proto.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "tile.h" +#include "trait.h" + +#include + +// 0x51D53C +StatDescription gStatDescriptions[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 +StatDescription gPcStatDescriptions[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 +MessageList gStatsMessageList; + +// 0x668184 +char* gStatValueDescriptions[PRIMARY_STAT_RANGE]; + +// 0x6681AC +int gPcStatValues[PC_STAT_COUNT]; + +// 0x4AED70 +int statsInit() +{ + MessageListItem messageListItem; + + // NOTE: Uninline. + pcStatsReset(); + + if (!messageListInit(&gStatsMessageList)) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "%s%s", asc_5186C8, "stat.msg"); + + if (!messageListLoad(&gStatsMessageList, path)) { + return -1; + } + + for (int stat = 0; stat < STAT_COUNT; stat++) { + gStatDescriptions[stat].name = getmsg(&gStatsMessageList, &messageListItem, 100 + stat); + gStatDescriptions[stat].description = getmsg(&gStatsMessageList, &messageListItem, 200 + stat); + } + + for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) { + gPcStatDescriptions[pcStat].name = getmsg(&gStatsMessageList, &messageListItem, 400 + pcStat); + gPcStatDescriptions[pcStat].description = getmsg(&gStatsMessageList, &messageListItem, 500 + pcStat); + } + + for (int index = 0; index < PRIMARY_STAT_RANGE; index++) { + gStatValueDescriptions[index] = getmsg(&gStatsMessageList, &messageListItem, 301 + index); + } + + return 0; +} + +// 0x4AEEC0 +int statsReset() +{ + // NOTE: Uninline. + pcStatsReset(); + + return 0; +} + +// 0x4AEEE4 +int statsExit() +{ + messageListFree(&gStatsMessageList); + + return 0; +} + +// 0x4AEEF4 +int statsLoad(File* stream) +{ + for (int index = 0; index < PC_STAT_COUNT; index++) { + if (fileReadInt32(stream, &(gPcStatValues[index])) == -1) { + return -1; + } + } + + return 0; +} + +// 0x4AEF20 +int statsSave(File* stream) +{ + for (int index = 0; index < PC_STAT_COUNT; index++) { + if (fileWriteInt32(stream, gPcStatValues[index]) == -1) { + return -1; + } + } + + return 0; +} + +// 0x4AEF48 +int critterGetStat(Object* critter, int stat) +{ + int value; + if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { + value = critterGetBaseStatWithTraitModifier(critter, stat); + value += critterGetBonusStat(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) - objectGetInventoryWeight(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 == gDude) { + if (perkHasRank(gDude, PERK_HTH_EVADE)) { + bool hasWeapon = false; + + Object* item2 = critterGetItem2(gDude); + if (item2 != NULL) { + if (itemGetType(item2) == ITEM_TYPE_WEAPON) { + if (weaponGetAnimationCode(item2) != WEAPON_ANIMATION_NONE) { + hasWeapon = true; + } + } + } + + if (!hasWeapon) { + Object* item1 = critterGetItem1(gDude); + if (item1 != NULL) { + if (itemGetType(item1) == ITEM_TYPE_WEAPON) { + if (weaponGetAnimationCode(item1) != WEAPON_ANIMATION_NONE) { + hasWeapon = true; + } + } + } + } + + if (!hasWeapon) { + actionPointsMultiplier = 2; + hthEvadeBonus = skillGetValue(gDude, SKILL_UNARMED) / 12; + } + } + } + value += hthEvadeBonus; + value += critter->data.critter.combat.ap * actionPointsMultiplier; + } + } + break; + case STAT_AGE: + value += gameTimeGetTime() / GAME_TIME_TICKS_PER_YEAR; + break; + } + + if (critter == gDude) { + switch (stat) { + case STAT_STRENGTH: + if (perkGetRank(critter, PERK_GAIN_STRENGTH)) { + value++; + } + + if (perkGetRank(critter, PERK_ADRENALINE_RUSH)) { + if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) < (critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) / 2)) { + value++; + } + } + break; + case STAT_PERCEPTION: + if (perkGetRank(critter, PERK_GAIN_PERCEPTION)) { + value++; + } + break; + case STAT_ENDURANCE: + if (perkGetRank(critter, PERK_GAIN_ENDURANCE)) { + value++; + } + break; + case STAT_CHARISMA: + if (1) { + if (perkGetRank(critter, PERK_GAIN_CHARISMA)) { + value++; + } + + bool hasMirrorShades = false; + + Object* item2 = critterGetItem2(critter); + if (item2 != NULL && item2->pid == PROTO_ID_MIRRORED_SHADES) { + hasMirrorShades = true; + } + + Object* item1 = critterGetItem1(critter); + if (item1 != NULL && item1->pid == PROTO_ID_MIRRORED_SHADES) { + hasMirrorShades = true; + } + + if (hasMirrorShades) { + value++; + } + } + break; + case STAT_INTELLIGENCE: + if (perkGetRank(critter, PERK_GAIN_INTELLIGENCE)) { + value++; + } + break; + case STAT_AGILITY: + if (perkGetRank(critter, PERK_GAIN_AGILITY)) { + value++; + } + break; + case STAT_LUCK: + if (perkGetRank(critter, PERK_GAIN_LUCK)) { + value++; + } + break; + case STAT_MAXIMUM_HIT_POINTS: + if (perkGetRank(critter, PERK_ALCOHOL_RAISED_HIT_POINTS)) { + value += 2; + } + + if (perkGetRank(critter, PERK_ALCOHOL_RAISED_HIT_POINTS_II)) { + value += 4; + } + + if (perkGetRank(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS)) { + value -= 2; + } + + if (perkGetRank(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS_II)) { + value -= 4; + } + + if (perkGetRank(critter, PERK_AUTODOC_RAISED_HIT_POINTS)) { + value += 2; + } + + if (perkGetRank(critter, PERK_AUTODOC_RAISED_HIT_POINTS_II)) { + value += 4; + } + + if (perkGetRank(critter, PERK_AUTODOC_LOWERED_HIT_POINTS)) { + value -= 2; + } + + if (perkGetRank(critter, PERK_AUTODOC_LOWERED_HIT_POINTS_II)) { + value -= 4; + } + break; + case STAT_DAMAGE_RESISTANCE: + if (perkGetRank(critter, PERK_DERMAL_IMPACT_ARMOR)) { + value += 5; + } else if (perkGetRank(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 (perkGetRank(critter, PERK_PHOENIX_ARMOR_IMPLANTS)) { + value += 5; + } else if (perkGetRank(critter, PERK_PHOENIX_ASSAULT_ENHANCEMENT)) { + value += 10; + } + break; + case STAT_RADIATION_RESISTANCE: + case STAT_POISON_RESISTANCE: + if (perkGetRank(critter, PERK_VAULT_CITY_INOCULATIONS)) { + value += 10; + } + break; + } + } + + value = min(max(value, gStatDescriptions[stat].minimumValue), gStatDescriptions[stat].maximumValue); + } else { + switch (stat) { + case STAT_CURRENT_HIT_POINTS: + value = critterGetHitPoints(critter); + break; + case STAT_CURRENT_POISON_LEVEL: + value = critterGetPoison(critter); + break; + case STAT_CURRENT_RADIATION_LEVEL: + value = critterGetRadiation(critter); + break; + default: + value = 0; + break; + } + } + + return value; +} + +// Returns base stat value (accounting for traits if critter is dude). +// +// 0x4AF3E0 +int critterGetBaseStatWithTraitModifier(Object* critter, int stat) +{ + int value = critterGetBaseStat(critter, stat); + + if (critter == gDude) { + value += traitGetStatModifier(stat); + } + + return value; +} + +// 0x4AF408 +int critterGetBaseStat(Object* critter, int stat) +{ + Proto* proto; + + if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { + protoGetProto(critter->pid, &proto); + return proto->critter.data.baseStats[stat]; + } else { + switch (stat) { + case STAT_CURRENT_HIT_POINTS: + return critterGetHitPoints(critter); + case STAT_CURRENT_POISON_LEVEL: + return critterGetPoison(critter); + case STAT_CURRENT_RADIATION_LEVEL: + return critterGetRadiation(critter); + } + } + + return 0; +} + +// 0x4AF474 +int critterGetBonusStat(Object* critter, int stat) +{ + if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { + Proto* proto; + protoGetProto(critter->pid, &proto); + return proto->critter.data.bonusStats[stat]; + } + + return 0; +} + +// 0x4AF4BC +int critterSetBaseStat(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 == gDude) { + value -= traitGetStatModifier(stat); + } + + if (value < gStatDescriptions[stat].minimumValue) { + return -2; + } + + if (value > gStatDescriptions[stat].maximumValue) { + return -3; + } + + protoGetProto(critter->pid, &proto); + proto->critter.data.baseStats[stat] = value; + + if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) { + critterUpdateDerivedStats(critter); + } + + return 0; + } + + switch (stat) { + case STAT_CURRENT_HIT_POINTS: + return critterAdjustHitPoints(critter, value - critterGetHitPoints(critter)); + case STAT_CURRENT_POISON_LEVEL: + return critterAdjustPoison(critter, value - critterGetPoison(critter)); + case STAT_CURRENT_RADIATION_LEVEL: + return critterAdjustRadiation(critter, value - critterGetRadiation(critter)); + } + + // Should be unreachable + return 0; +} + +// 0x4AF5D4 +int critterIncBaseStat(Object* critter, int stat) +{ + int value = critterGetBaseStat(critter, stat); + + if (critter == gDude) { + value += traitGetStatModifier(stat); + } + + return critterSetBaseStat(critter, stat, value + 1); +} + +// 0x4AF608 +int critterDecBaseStat(Object* critter, int stat) +{ + int value = critterGetBaseStat(critter, stat); + + if (critter == gDude) { + value += traitGetStatModifier(stat); + } + + return critterSetBaseStat(critter, stat, value - 1); +} + +// 0x4AF63C +int critterSetBonusStat(Object* critter, int stat, int value) +{ + if (!statIsValid(stat)) { + return -5; + } + + if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { + Proto* proto; + protoGetProto(critter->pid, &proto); + proto->critter.data.bonusStats[stat] = value; + + if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) { + critterUpdateDerivedStats(critter); + } + + return 0; + } else { + switch (stat) { + case STAT_CURRENT_HIT_POINTS: + return critterAdjustHitPoints(critter, value); + case STAT_CURRENT_POISON_LEVEL: + return critterAdjustPoison(critter, value); + case STAT_CURRENT_RADIATION_LEVEL: + return critterAdjustRadiation(critter, value); + } + } + + // Should be unreachable + return -1; +} + +// 0x4AF6CC +void protoCritterDataResetStats(CritterProtoData* data) +{ + for (int stat = 0; stat < SAVEABLE_STAT_COUNT; stat++) { + data->baseStats[stat] = gStatDescriptions[stat].defaultValue; + data->bonusStats[stat] = 0; + } +} + +// 0x4AF6FC +void critterUpdateDerivedStats(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; + protoGetProto(critter->pid, &proto); + CritterProtoData* data = &(proto->critter.data); + + data->baseStats[STAT_MAXIMUM_HIT_POINTS] = critterGetBaseStatWithTraitModifier(critter, STAT_STRENGTH) + critterGetBaseStatWithTraitModifier(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* statGetName(int stat) +{ + return statIsValid(stat) ? gStatDescriptions[stat].name : NULL; +} + +// 0x4AF898 +char* statGetDescription(int stat) +{ + return statIsValid(stat) ? gStatDescriptions[stat].description : NULL; +} + +// 0x4AF8DC +char* statGetValueDescription(int value) +{ + if (value < PRIMARY_STAT_MIN) { + value = PRIMARY_STAT_MIN; + } else if (value > PRIMARY_STAT_MAX) { + value = PRIMARY_STAT_MAX; + } + + return gStatValueDescriptions[value - PRIMARY_STAT_MIN]; +} + +// 0x4AF8FC +int pcGetStat(int pcStat) +{ + return pcStatIsValid(pcStat) ? gPcStatValues[pcStat] : 0; +} + +// 0x4AF910 +int pcSetStat(int pcStat, int value) +{ + int result; + + if (!pcStatIsValid(pcStat)) { + return -5; + } + + if (value < gPcStatDescriptions[pcStat].minimumValue) { + return -2; + } + + if (value > gPcStatDescriptions[pcStat].maximumValue) { + return -3; + } + + if (pcStat != PC_STAT_EXPERIENCE || value >= gPcStatValues[PC_STAT_EXPERIENCE]) { + gPcStatValues[pcStat] = value; + if (pcStat == PC_STAT_EXPERIENCE) { + result = pcAddExperienceWithOptions(0, true); + } else { + result = 0; + } + } else { + result = pcSetExperience(value); + } + + return result; +} + +// Reset stats. +// +// 0x4AF980 +void pcStatsReset() +{ + for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) { + gPcStatValues[pcStat] = gPcStatDescriptions[pcStat].defaultValue; + } +} + +// Returns experience to reach next level. +// +// 0x4AF9A0 +int pcGetExperienceForNextLevel() +{ + return pcGetExperienceForLevel(gPcStatValues[PC_STAT_LEVEL] + 1); +} + +// Returns exp to reach given level. +// +// 0x4AF9A8 +int pcGetExperienceForLevel(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* pcStatGetName(int pcStat) +{ + return pcStat >= 0 && pcStat < PC_STAT_COUNT ? gPcStatDescriptions[pcStat].name : NULL; +} + +// 0x4AFA14 +char* pcStatGetDescription(int pcStat) +{ + return pcStat >= 0 && pcStat < PC_STAT_COUNT ? gPcStatDescriptions[pcStat].description : NULL; +} + +// 0x4AFA34 +int statGetFrmId(int stat) +{ + return statIsValid(stat) ? gStatDescriptions[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 statRoll(Object* critter, int stat, int modifier, int* howMuch) +{ + int value = critterGetStat(critter, stat) + modifier; + int chance = randomBetween(PRIMARY_STAT_MIN, PRIMARY_STAT_MAX); + + if (howMuch != NULL) { + *howMuch = value - chance; + } + + if (chance <= value) { + return ROLL_SUCCESS; + } + + return ROLL_FAILURE; +} + +// 0x4AFAA8 +int pcAddExperience(int xp) +{ + return pcAddExperienceWithOptions(xp, true); +} + +// 0x4AFAB8 +int pcAddExperienceWithOptions(int xp, bool a2) +{ + int newXp = gPcStatValues[PC_STAT_EXPERIENCE]; + newXp += xp; + newXp += perkGetRank(gDude, PERK_SWIFT_LEARNER) * 5 * xp / 100; + + if (newXp < gPcStatDescriptions[PC_STAT_EXPERIENCE].minimumValue) { + newXp = gPcStatDescriptions[PC_STAT_EXPERIENCE].minimumValue; + } + + if (newXp > gPcStatDescriptions[PC_STAT_EXPERIENCE].maximumValue) { + newXp = gPcStatDescriptions[PC_STAT_EXPERIENCE].maximumValue; + } + + gPcStatValues[PC_STAT_EXPERIENCE] = newXp; + + while (gPcStatValues[PC_STAT_LEVEL] < PC_LEVEL_MAX) { + if (newXp < pcGetExperienceForNextLevel()) { + break; + } + + if (pcSetStat(PC_STAT_LEVEL, gPcStatValues[PC_STAT_LEVEL] + 1) == 0) { + int maxHpBefore = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + + // You have gone up a level. + MessageListItem messageListItem; + messageListItem.num = 600; + if (messageListGetItem(&gStatsMessageList, &messageListItem)) { + displayMonitorAddMessage(messageListItem.text); + } + + dudeEnableState(DUDE_STATE_LEVEL_UP_AVAILABLE); + + soundPlayFile("levelup"); + + // NOTE: Uninline. + int endurance = critterGetBaseStatWithTraitModifier(gDude, STAT_ENDURANCE); + + int hpPerLevel = endurance / 2 + 2; + hpPerLevel += perkGetRank(gDude, PERK_LIFEGIVER) * 4; + + int bonusHp = critterGetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS); + critterSetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS, bonusHp + hpPerLevel); + + int maxHpAfter = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS); + critterAdjustHitPoints(gDude, maxHpAfter - maxHpBefore); + + interfaceRenderHitPoints(false); + + if (a2) { + _partyMemberIncLevels(); + } + } + } + + return 0; +} + +// 0x4AFC38 +int pcSetExperience(int xp) +{ + int oldLevel = gPcStatValues[PC_STAT_LEVEL]; + gPcStatValues[PC_STAT_EXPERIENCE] = xp; + + int level = 1; + do { + level += 1; + } while (xp >= pcGetExperienceForLevel(level) && level < PC_LEVEL_MAX); + + int newLevel = level - 1; + + pcSetStat(PC_STAT_LEVEL, newLevel); + dudeDisableState(DUDE_STATE_LEVEL_UP_AVAILABLE); + + int endurance = critterGetBaseStat(gDude, STAT_ENDURANCE); + if (gDude == gDude) { + endurance += traitGetStatModifier(STAT_ENDURANCE); + } + + int hpPerLevel = endurance / 2 + 2; + hpPerLevel += perkGetRank(gDude, PERK_LIFEGIVER) * 4; + + int deltaHp = (oldLevel - newLevel) * hpPerLevel; + critterAdjustHitPoints(gDude, -deltaHp); + + int bonusHp = critterGetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS); + + critterSetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS, bonusHp - deltaHp); + + interfaceRenderHitPoints(false); + + return 0; +} diff --git a/src/stat.h b/src/stat.h new file mode 100644 index 0000000..4f91ffc --- /dev/null +++ b/src/stat.h @@ -0,0 +1,72 @@ +#ifndef STAT_H +#define STAT_H + +#include "db.h" +#include "message.h" +#include "obj_types.h" +#include "proto_types.h" +#include "stat_defs.h" + +#include + +#define STAT_ERR_INVALID_STAT (-5) + +// Provides metadata about stats. +typedef struct StatDescription { + char* name; + char* description; + int frmId; + int minimumValue; + int maximumValue; + int defaultValue; +} StatDescription; + +extern StatDescription gStatDescriptions[STAT_COUNT]; +extern StatDescription gPcStatDescriptions[PC_STAT_COUNT]; + +extern MessageList gStatsMessageList; +extern char* gStatValueDescriptions[PRIMARY_STAT_RANGE]; +extern int gPcStatValues[PC_STAT_COUNT]; + +int statsInit(); +int statsReset(); +int statsExit(); +int statsLoad(File* stream); +int statsSave(File* stream); +int critterGetStat(Object* critter, int stat); +int critterGetBaseStatWithTraitModifier(Object* critter, int stat); +int critterGetBaseStat(Object* critter, int stat); +int critterGetBonusStat(Object* critter, int stat); +int critterSetBaseStat(Object* critter, int stat, int value); +int critterIncBaseStat(Object* critter, int stat); +int critterDecBaseStat(Object* critter, int stat); +int critterSetBonusStat(Object* critter, int stat, int value); +void protoCritterDataResetStats(CritterProtoData* data); +void critterUpdateDerivedStats(Object* critter); +char* statGetName(int stat); +char* statGetDescription(int stat); +char* statGetValueDescription(int value); +int pcGetStat(int pcStat); +int pcSetStat(int pcStat, int value); +void pcStatsReset(); +int pcGetExperienceForNextLevel(); +int pcGetExperienceForLevel(int level); +char* pcStatGetName(int pcStat); +char* pcStatGetDescription(int pcStat); +int statGetFrmId(int stat); +int statRoll(Object* critter, int stat, int modifier, int* howMuch); +int pcAddExperience(int xp); +int pcAddExperienceWithOptions(int xp, bool a2); +int pcSetExperience(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 /* STAT_H */ diff --git a/src/stat_defs.h b/src/stat_defs.h new file mode 100644 index 0000000..b867888 --- /dev/null +++ b/src/stat_defs.h @@ -0,0 +1,84 @@ +#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 */ + diff --git a/src/string_parsers.c b/src/string_parsers.c new file mode 100644 index 0000000..db07833 --- /dev/null +++ b/src/string_parsers.c @@ -0,0 +1,259 @@ +#include "string_parsers.h" + +#include "debug.h" + +#include +#include + +// strParseInt +// 0x4AFD10 +int strParseInt(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) { + debugPrint("\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) { + debugPrint("\nstrParseStrFromFunc Error: Couldn't find match for string: %s!", str); + *valuePtr = -1; + return -1; + } + + return 0; +} + +// 0x4AFF7C +int strParseIntWithKey(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 strParseKeyValue(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; +} diff --git a/src/string_parsers.h b/src/string_parsers.h new file mode 100644 index 0000000..84f573c --- /dev/null +++ b/src/string_parsers.h @@ -0,0 +1,12 @@ +#ifndef STRING_PARSERS_H +#define STRING_PARSERS_H + +typedef int(StringParserCallback)(char* string, int* valuePtr); + +int strParseInt(char** stringPtr, int* valuePtr); +int strParseStrFromList(char** stringPtr, int* valuePtr, const char** list, int count); +int strParseStrFromFunc(char** stringPtr, int* valuePtr, StringParserCallback* callback); +int strParseIntWithKey(char** stringPtr, const char* key, int* valuePtr, const char* delimeter); +int strParseKeyValue(char** stringPtr, char* key, int* valuePtr, const char* delimeter); + +#endif /* STRING_PARSERS_H */ diff --git a/src/text_font.c b/src/text_font.c new file mode 100644 index 0000000..54caac2 --- /dev/null +++ b/src/text_font.c @@ -0,0 +1,382 @@ +#include "text_font.h" + +#include "color.h" +#include "db.h" +#include "memory.h" + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +// 0x4D5530 +FontManager gTextFontManager = { + 0, + 9, + textFontSetCurrentImpl, + textFontDrawImpl, + textFontGetLineHeightImpl, + textFontGeStringWidthImpl, + textFontGetCharacterWidthImpl, + textFontGetMonospacedStringWidthImpl, + textFontGetLetterSpacingImpl, + textFontGetBufferSizeImpl, + textFontGetMonospacedCharacterWidthImpl, +}; + +// 0x51E3B0 +int gCurrentFont = -1; + +// 0x51E3B4 +int gFontManagersCount = 0; + +// 0x51E3B8 +FontManagerDrawTextProc* fontDrawText = NULL; + +// 0x51E3BC +FontManagerGetLineHeightProc* fontGetLineHeight = NULL; + +// 0x51E3C0 +FontManagerGetStringWidthProc* fontGetStringWidth = NULL; + +// 0x51E3C4 +FontManagerGetCharacterWidthProc* fontGetCharacterWidth = NULL; + +// 0x51E3C8 +FontManagerGetMonospacedStringWidthProc* fontGetMonospacedStringWidth = NULL; + +// 0x51E3CC +FontManagerGetLetterSpacingProc* fontGetLetterSpacing = NULL; + +// 0x51E3D0 +FontManagerGetBufferSizeProc* fontGetBufferSize = NULL; + +// 0x51E3D4 +FontManagerGetMonospacedCharacterWidth* fontGetMonospacedCharacterWidth = NULL; + +// 0x6ADB08 +TextFontDescriptor gTextFontDescriptors[TEXT_FONT_MAX]; + +// 0x6ADBD0 +FontManager gFontManagers[FONT_MANAGER_MAX]; + +// 0x6ADD88 +TextFontDescriptor* gCurrentTextFontDescriptor; + +// 0x4D555C +int textFontsInit() +{ + int currentFont = -1; + + FontManager fontManager; + static_assert(sizeof(fontManager) == sizeof(gTextFontManager), "wrong size"); + memcpy(&fontManager, &gTextFontManager, sizeof(fontManager)); + + for (int font = 0; font < TEXT_FONT_MAX; font++) { + if (textFontLoad(font) == -1) { + gTextFontDescriptors[font].glyphCount = 0; + } else { + if (currentFont == -1) { + currentFont = font; + } + } + } + + if (currentFont == -1) { + return -1; + } + + if (fontManagerAdd(&fontManager) == -1) { + return -1; + } + + fontSetCurrent(currentFont); + + return 0; +} + +// 0x4D55CC +void textFontsExit() +{ + for (int index = 0; index < TEXT_FONT_MAX; index++) { + TextFontDescriptor* textFontDescriptor = &(gTextFontDescriptors[index]); + if (textFontDescriptor->glyphCount != 0) { + internal_free(textFontDescriptor->glyphs); + internal_free(textFontDescriptor->data); + } + } +} + +// 0x4D55FC +int textFontLoad(int font) +{ + int rc = -1; + + char path[MAX_PATH]; + sprintf(path, "font%d.fon", font); + + // NOTE: Original code is slightly different. It uses deep nesting and + // unwinds everything from the point of failure. + TextFontDescriptor* textFontDescriptor = &(gTextFontDescriptors[font]); + textFontDescriptor->data = NULL; + textFontDescriptor->glyphs = NULL; + + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + goto out; + } + + if (fileRead(textFontDescriptor, sizeof(TextFontDescriptor), 1, stream) != 1) { + goto out; + } + + textFontDescriptor->glyphs = internal_malloc(textFontDescriptor->glyphCount * sizeof(TextFontGlyph)); + if (textFontDescriptor->glyphs == NULL) { + goto out; + } + + if (fileRead(textFontDescriptor->glyphs, sizeof(TextFontGlyph), textFontDescriptor->glyphCount, stream) != textFontDescriptor->glyphCount) { + goto out; + } + + int dataSize = textFontDescriptor->lineHeight * ((textFontDescriptor->glyphs[textFontDescriptor->glyphCount - 1].width + 7) >> 3) + textFontDescriptor->glyphs[textFontDescriptor->glyphCount - 1].dataOffset; + textFontDescriptor->data = internal_malloc(dataSize); + if (textFontDescriptor->data == NULL) { + goto out; + } + + if (fileRead(textFontDescriptor->data, 1, dataSize, stream) != dataSize) { + goto out; + } + + rc = 0; + +out: + + if (rc != 0) { + if (textFontDescriptor->data != NULL) { + internal_free(textFontDescriptor->data); + textFontDescriptor->data = NULL; + } + + if (textFontDescriptor->glyphs != NULL) { + internal_free(textFontDescriptor->glyphs); + textFontDescriptor->glyphs = NULL; + } + } + + if (stream != NULL) { + fileClose(stream); + } + + return rc; +} + +// 0x4D5780 +int fontManagerAdd(FontManager* fontManager) +{ + if (fontManager == NULL) { + return -1; + } + + if (gFontManagersCount >= FONT_MANAGER_MAX) { + return -1; + } + + // Check if a font manager exists for any font in the specified range. + for (int index = fontManager->minFont; index < fontManager->maxFont; index++) { + FontManager* existingFontManager; + if (fontManagerFind(index, &existingFontManager)) { + return -1; + } + } + + static_assert(sizeof(*gFontManagers) == sizeof(*fontManager), "wrong size"); + memcpy(&(gFontManagers[gFontManagersCount]), fontManager, sizeof(*fontManager)); + gFontManagersCount++; + + return 0; +} + +// 0x4D58AC +void textFontSetCurrentImpl(int font) +{ + if (font >= TEXT_FONT_MAX) { + return; + } + + TextFontDescriptor* textFontDescriptor = &(gTextFontDescriptors[font]); + if (textFontDescriptor->glyphCount == 0) { + return; + } + + gCurrentTextFontDescriptor = textFontDescriptor; +} + +// 0x4D58D4 +int fontGetCurrent() +{ + return gCurrentFont; +} + +// 0x4D58DC +void fontSetCurrent(int font) +{ + FontManager* fontManager; + + if (fontManagerFind(font, &fontManager)) { + fontDrawText = fontManager->drawTextProc; + fontGetLineHeight = fontManager->getLineHeightProc; + fontGetStringWidth = fontManager->getStringWidthProc; + fontGetCharacterWidth = fontManager->getCharacterWidthProc; + fontGetMonospacedStringWidth = fontManager->getMonospacedStringWidthProc; + fontGetLetterSpacing = fontManager->getLetterSpacingProc; + fontGetBufferSize = fontManager->getBufferSizeProc; + fontGetMonospacedCharacterWidth = fontManager->getMonospacedCharacterWidthProc; + + gCurrentFont = font; + + fontManager->setCurrentProc(font); + } +} + +// 0x4D595C +bool fontManagerFind(int font, FontManager** fontManagerPtr) +{ + for (int index = 0; index < gFontManagersCount; index++) { + FontManager* fontManager = &(gFontManagers[index]); + if (font >= fontManager->minFont && font <= fontManager->maxFont) { + *fontManagerPtr = fontManager; + return true; + } + } + + return false; +} + +// 0x4D59B0 +void textFontDrawImpl(unsigned char* buf, const char* string, int length, int pitch, int color) +{ + if ((color & FONT_SHADOW) != 0) { + color &= ~FONT_SHADOW; + fontDrawText(buf + pitch + 1, string, length, pitch, _colorTable[0]); + } + + int monospacedCharacterWidth; + if ((color & FONT_MONO) != 0) { + monospacedCharacterWidth = fontGetMonospacedCharacterWidth(); + } + + unsigned char* ptr = buf; + while (*string != '\0') { + char ch = *string++; + if (ch < gCurrentTextFontDescriptor->glyphCount) { + TextFontGlyph* glyph = &(gCurrentTextFontDescriptor->glyphs[ch & 0xFF]); + + unsigned char* end; + if ((color & FONT_MONO) != 0) { + end = ptr + monospacedCharacterWidth; + ptr += (monospacedCharacterWidth - gCurrentTextFontDescriptor->letterSpacing - glyph->width) / 2; + } else { + end = ptr + glyph->width + gCurrentTextFontDescriptor->letterSpacing; + } + + if (end - buf > length) { + break; + } + + unsigned char* glyphData = gCurrentTextFontDescriptor->data + glyph->dataOffset; + for (int y = 0; y < gCurrentTextFontDescriptor->lineHeight; 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 += pitch - glyph->width; + } + + ptr = end; + } + } + + if ((color & FONT_UNDERLINE) != 0) { + // TODO: Probably additional -1 present, check. + int length = ptr - buf; + unsigned char* underlinePtr = buf + pitch * (gCurrentTextFontDescriptor->lineHeight - 1); + for (int pix = 0; pix < length; pix++) { + *underlinePtr++ = color & 0xFF; + } + } +} + +// 0x4D5B54 +int textFontGetLineHeightImpl() +{ + return gCurrentTextFontDescriptor->lineHeight; +} + +// 0x4D5B60 +int textFontGeStringWidthImpl(const char* string) +{ + int width = 0; + + const char* ch = string; + while (*ch != '\0') { + if (*ch < gCurrentTextFontDescriptor->glyphCount) { + width += gCurrentTextFontDescriptor->letterSpacing + gCurrentTextFontDescriptor->glyphs[*ch & 0xFF].width; + } + ch++; + } + + return width; +} + +// 0x4D5BA4 +int textFontGetCharacterWidthImpl(int ch) +{ + return gCurrentTextFontDescriptor->glyphs[ch & 0xFF].width; +} + +// 0x4D5BB8 +int textFontGetMonospacedStringWidthImpl(const char* string) +{ + return fontGetMonospacedCharacterWidth() * strlen(string); +} + +// 0x4D5BD8 +int textFontGetLetterSpacingImpl() +{ + return gCurrentTextFontDescriptor->letterSpacing; +} + +// 0x4D5BE4 +int textFontGetBufferSizeImpl(const char* string) +{ + return fontGetStringWidth(string) * fontGetLineHeight(); +} + +// 0x4D5BF8 +int textFontGetMonospacedCharacterWidthImpl() +{ + int width = 0; + + for (int index = 0; index < gCurrentTextFontDescriptor->glyphCount; index++) { + TextFontGlyph* glyph = &(gCurrentTextFontDescriptor->glyphs[index]); + if (width < glyph->width) { + width = glyph->width; + } + } + + return width + gCurrentTextFontDescriptor->letterSpacing; +} diff --git a/src/text_font.h b/src/text_font.h new file mode 100644 index 0000000..0fd00ad --- /dev/null +++ b/src/text_font.h @@ -0,0 +1,97 @@ +#ifndef TEXT_FONT_H +#define TEXT_FONT_H + +#include + +// The maximum number of text fonts. +#define TEXT_FONT_MAX (10) + +// The maximum number of font managers. +#define FONT_MANAGER_MAX (10) + +typedef void FontManagerSetCurrentFontProc(int font); +typedef void FontManagerDrawTextProc(unsigned char* buffer, const char* string, int length, int pitch, int color); +typedef int FontManagerGetLineHeightProc(); +typedef int FontManagerGetStringWidthProc(const char* string); +typedef int FontManagerGetCharacterWidthProc(int ch); +typedef int FontManagerGetMonospacedStringWidthProc(const char* string); +typedef int FontManagerGetLetterSpacingProc(); +typedef int FontManagerGetBufferSizeProc(const char* string); +typedef int FontManagerGetMonospacedCharacterWidth(); + +typedef struct FontManager { + int minFont; + int maxFont; + FontManagerSetCurrentFontProc* setCurrentProc; + FontManagerDrawTextProc* drawTextProc; + FontManagerGetLineHeightProc* getLineHeightProc; + FontManagerGetStringWidthProc* getStringWidthProc; + FontManagerGetCharacterWidthProc* getCharacterWidthProc; + FontManagerGetMonospacedStringWidthProc* getMonospacedStringWidthProc; + FontManagerGetLetterSpacingProc* getLetterSpacingProc; + FontManagerGetBufferSizeProc* getBufferSizeProc; + FontManagerGetMonospacedCharacterWidth* getMonospacedCharacterWidthProc; +} FontManager; + +static_assert(sizeof(FontManager) == 44, "wrong size"); + +typedef struct TextFontGlyph { + // The width of the glyph in pixels. + int width; + + // Data offset into [TextFont.data]. + int dataOffset; +} TextFontGlyph; + +typedef struct TextFontDescriptor { + // The number of glyphs in the font. + int glyphCount; + + // The height of the font. + int lineHeight; + + // Horizontal spacing between characters in pixels. + int letterSpacing; + + TextFontGlyph* glyphs; + unsigned char* data; +} TextFontDescriptor; + +#define FONT_SHADOW (0x10000) +#define FONT_UNDERLINE (0x20000) +#define FONT_MONO (0x40000) + +extern FontManager gTextFontManager; +extern int gCurrentFont; +extern int gFontManagersCount; +extern FontManagerDrawTextProc* fontDrawText; +extern FontManagerGetLineHeightProc* fontGetLineHeight; +extern FontManagerGetStringWidthProc* fontGetStringWidth; +extern FontManagerGetCharacterWidthProc* fontGetCharacterWidth; +extern FontManagerGetMonospacedStringWidthProc* fontGetMonospacedStringWidth; +extern FontManagerGetLetterSpacingProc* fontGetLetterSpacing; +extern FontManagerGetBufferSizeProc* fontGetBufferSize; +extern FontManagerGetMonospacedCharacterWidth* fontGetMonospacedCharacterWidth; + +extern TextFontDescriptor gTextFontDescriptors[TEXT_FONT_MAX]; +extern FontManager gFontManagers[FONT_MANAGER_MAX]; +extern TextFontDescriptor* gCurrentTextFontDescriptor; + +int textFontsInit(); +void textFontsExit(); +int textFontLoad(int font); +int fontManagerAdd(FontManager* fontManager); +void textFontSetCurrentImpl(int font); +int fontGetCurrent(); +void fontSetCurrent(int font); +bool fontManagerFind(int font, FontManager** fontManagerPtr); +void textFontDrawImpl(unsigned char* buf, const char* string, int length, int pitch, int color); +int textFontGetLineHeightImpl(); +int textFontGeStringWidthImpl(const char* string); +int textFontGetCharacterWidthImpl(int ch); +int textFontGetMonospacedStringWidthImpl(const char* string); +int textFontGetLetterSpacingImpl(); +int textFontGetBufferSizeImpl(const char* string); +int textFontGetMonospacedCharacterWidthImpl(); + +#endif /* TEXT_FONT_H */ diff --git a/src/text_object.c b/src/text_object.c new file mode 100644 index 0000000..ac5f457 --- /dev/null +++ b/src/text_object.c @@ -0,0 +1,450 @@ +#include "text_object.h" + +#include "core.h" +#include "debug.h" +#include "draw.h" +#include "game_config.h" +#include "memory.h" +#include "object.h" +#include "text_font.h" +#include "tile.h" +#include "word_wrap.h" + +static_assert(sizeof(TextObject) == 48, "wrong size"); + +// 0x51D944 +int gTextObjectsCount = 0; + +// 0x51D948 +unsigned int gTextObjectsBaseDelay = 3500; + +// 0x51D94C +unsigned int gTextObjectsLineDelay = 1399; + +// 0x6681C0 +TextObject* gTextObjects[TEXT_OBJECTS_MAX_COUNT]; + +// 0x668210 +int gTextObjectsWindowWidth; + +// 0x668214 +int gTextObjectsWindowHeight; + +// 0x668218 +unsigned char* gTextObjectsWindowBuffer; + +// 0x66821C +bool gTextObjectsEnabled; + +// 0x668220 +bool gTextObjectsInitialized; + +// 0x4B0130 +int textObjectsInit(unsigned char* windowBuffer, int width, int height) +{ + if (gTextObjectsInitialized) { + return -1; + } + + gTextObjectsWindowBuffer = windowBuffer; + gTextObjectsWindowWidth = width; + gTextObjectsWindowHeight = height; + gTextObjectsCount = 0; + + tickersAdd(textObjectsTicker); + + double textBaseDelay; + if (!configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &textBaseDelay)) { + textBaseDelay = 3.5; + } + + double textLineDelay; + if (!configGetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, &textLineDelay)) { + textLineDelay = 1.399993896484375; + } + + gTextObjectsBaseDelay = (unsigned int)(textBaseDelay * 1000.0); + gTextObjectsLineDelay = (unsigned int)(textLineDelay * 1000.0); + + gTextObjectsEnabled = true; + gTextObjectsInitialized = true; + + return 0; +} + +// 0x4B021C +int textObjectsReset() +{ + if (!gTextObjectsInitialized) { + return -1; + } + + for (int index = 0; index < gTextObjectsCount; index++) { + internal_free(gTextObjects[index]->data); + internal_free(gTextObjects[index]); + } + + gTextObjectsCount = 0; + tickersAdd(textObjectsTicker); + + return 0; +} + +// 0x4B0280 +void textObjectsFree() +{ + if (gTextObjectsInitialized) { + textObjectsReset(); + tickersRemove(textObjectsTicker); + gTextObjectsInitialized = false; + } +} + +// 0x4B02A4 +void textObjectsDisable() +{ + gTextObjectsEnabled = false; +} + +// 0x4B02B0 +void textObjectsEnable() +{ + gTextObjectsEnabled = true; +} + +// 0x4B02C4 +void textObjectsSetBaseDelay(double value) +{ + if (value < 1.0) { + value = 1.0; + } + + gTextObjectsBaseDelay = (int)(value * 1000.0); +} + +// 0x4B031C +void textObjectsSetLineDelay(double value) +{ + if (value < 0.0) { + value = 0.0; + } + + gTextObjectsLineDelay = (int)(value * 1000.0); +} + +// text_object_create +// 0x4B036C +int textObjectAdd(Object* object, char* string, int font, int color, int a5, Rect* rect) +{ + if (!gTextObjectsInitialized) { + return -1; + } + + if (gTextObjectsCount >= TEXT_OBJECTS_MAX_COUNT - 1) { + return -1; + } + + if (string == NULL) { + return -1; + } + + if (*string == '\0') { + return -1; + } + + TextObject* textObject = internal_malloc(sizeof(*textObject)); + if (textObject == NULL) { + return -1; + } + + memset(textObject, 0, sizeof(*textObject)); + + int oldFont = fontGetCurrent(); + fontSetCurrent(font); + + short beginnings[WORD_WRAP_MAX_COUNT]; + short count; + if (wordWrap(string, 200, beginnings, &count) != 0) { + fontSetCurrent(oldFont); + return -1; + } + + textObject->linesCount = count - 1; + if (textObject->linesCount < 1) { + debugPrint("**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 [fontGetStringWidth] twice, probably result of using min/max macro + int width = fontGetStringWidth(beginning); + if (width >= textObject->width) { + textObject->width = width; + } + + *ending = c; + } + + textObject->height = (fontGetLineHeight() + 1) * textObject->linesCount; + + if (a5 != -1) { + textObject->width += 2; + textObject->height += 2; + } + + int size = textObject->width * textObject->height; + textObject->data = internal_malloc(size); + if (textObject->data == NULL) { + fontSetCurrent(oldFont); + return -1; + } + + memset(textObject->data, 0, size); + + unsigned char* dest = textObject->data; + int skip = textObject->width * (fontGetLineHeight() + 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 = fontGetStringWidth(beginning); + fontDrawText(dest + (textObject->width - width) / 2, beginning, textObject->width, textObject->width, color); + + *ending = c; + + dest += skip; + } + + if (a5 != -1) { + bufferOutline(textObject->data, textObject->width, textObject->height, textObject->width, a5); + } + + if (object != NULL) { + textObject->tile = object->tile; + } else { + textObject->flags |= TEXT_OBJECT_UNBOUNDED; + textObject->tile = gCenterTile; + } + + textObjectFindPlacement(textObject); + + if (rect != NULL) { + rect->left = textObject->x; + rect->top = textObject->y; + rect->right = textObject->x + textObject->width - 1; + rect->bottom = textObject->y + textObject->height - 1; + } + + textObjectsRemoveByOwner(object); + + textObject->owner = object; + textObject->time = _get_bk_time(); + + gTextObjects[gTextObjectsCount] = textObject; + gTextObjectsCount++; + + fontSetCurrent(oldFont); + + return 0; +} + +// 0x4B06E8 +void textObjectsRenderInRect(Rect* rect) +{ + if (!gTextObjectsInitialized) { + return; + } + + for (int index = 0; index < gTextObjectsCount; index++) { + TextObject* textObject = gTextObjects[index]; + tileToScreenXY(textObject->tile, &(textObject->x), &(textObject->y), gElevation); + textObject->x += textObject->sx; + textObject->y += textObject->sy; + + Rect textObjectRect; + textObjectRect.left = textObject->x; + textObjectRect.top = textObject->y; + textObjectRect.right = textObject->width + textObject->x - 1; + textObjectRect.bottom = textObject->height + textObject->y - 1; + if (rectIntersection(&textObjectRect, rect, &textObjectRect) == 0) { + blitBufferToBufferTrans(textObject->data + textObject->width * (textObjectRect.top - textObject->y) + (textObjectRect.left - textObject->x), + textObjectRect.right - textObjectRect.left + 1, + textObjectRect.bottom - textObjectRect.top + 1, + textObject->width, + gTextObjectsWindowBuffer + gTextObjectsWindowWidth * textObjectRect.top + textObjectRect.left, + gTextObjectsWindowWidth); + } + } +} + +// 0x4B07F0 +int textObjectsGetCount() +{ + return gTextObjectsCount; +} + +// 0x4B07F8 +void textObjectsTicker() +{ + if (!gTextObjectsEnabled) { + return; + } + + bool textObjectsRemoved = false; + Rect dirtyRect; + + for (int index = 0; index < gTextObjectsCount; index++) { + TextObject* textObject = gTextObjects[index]; + + unsigned int delay = gTextObjectsLineDelay * textObject->linesCount + gTextObjectsBaseDelay; + if ((textObject->flags & TEXT_OBJECT_MARKED_FOR_REMOVAL) != 0 || (getTicksBetween(_get_bk_time(), textObject->time) > delay)) { + tileToScreenXY(textObject->tile, &(textObject->x), &(textObject->y), gElevation); + textObject->x += textObject->sx; + textObject->y += textObject->sy; + + Rect textObjectRect; + textObjectRect.left = textObject->x; + textObjectRect.top = textObject->y; + textObjectRect.right = textObject->width + textObject->x - 1; + textObjectRect.bottom = textObject->height + textObject->y - 1; + + if (textObjectsRemoved) { + rectUnion(&dirtyRect, &textObjectRect, &dirtyRect); + } else { + rectCopy(&dirtyRect, &textObjectRect); + textObjectsRemoved = true; + } + + internal_free(textObject->data); + internal_free(textObject); + + memmove(&(gTextObjects[index]), &(gTextObjects[index + 1]), sizeof(*gTextObjects) * (gTextObjectsCount - index - 1)); + + gTextObjectsCount--; + index--; + } + } + + if (textObjectsRemoved) { + tileWindowRefreshRect(&dirtyRect, gElevation); + } +} + +// Finds best position for placing text object. +// +// 0x4B0954 +void textObjectFindPlacement(TextObject* textObject) +{ + int tileScreenX; + int tileScreenY; + tileToScreenXY(textObject->tile, &tileScreenX, &tileScreenY, gElevation); + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + textObject->sx = textObject->x - tileScreenX; + textObject->sy = textObject->y - tileScreenY; + return; + } + + textObject->x += textObject->width; + if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + textObject->sx = textObject->x - tileScreenX; + textObject->sy = textObject->y - tileScreenY; + return; + } + + textObject->x += textObject->width; + if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < gTextObjectsWindowWidth) + && (textObject->y >= 0 && textObject->y + textObject->height - 1 < gTextObjectsWindowHeight)) { + 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 textObjectsRemoveByOwner(Object* object) +{ + for (int index = 0; index < gTextObjectsCount; index++) { + if (gTextObjects[index]->owner == object) { + gTextObjects[index]->flags |= TEXT_OBJECT_MARKED_FOR_REMOVAL; + } + } +} diff --git a/src/text_object.h b/src/text_object.h new file mode 100644 index 0000000..fa7fdf2 --- /dev/null +++ b/src/text_object.h @@ -0,0 +1,57 @@ +#ifndef TEXT_OBJECT_H +#define TEXT_OBJECT_H + +#include "geometry.h" +#include "obj_types.h" + +#include + +// 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; + +extern int gTextObjectsCount; +extern unsigned int gTextObjectsBaseDelay; +extern unsigned int gTextObjectsLineDelay; + +extern TextObject* gTextObjects[TEXT_OBJECTS_MAX_COUNT]; +extern int gTextObjectsWindowWidth; +extern int gTextObjectsWindowHeight; +extern unsigned char* gTextObjectsWindowBuffer; +extern bool gTextObjectsEnabled; +extern bool gTextObjectsInitialized; + +int textObjectsInit(unsigned char* windowBuffer, int width, int height); +int textObjectsReset(); +void textObjectsFree(); +void textObjectsDisable(); +void textObjectsEnable(); +void textObjectsSetBaseDelay(double value); +void textObjectsSetLineDelay(double value); +int textObjectAdd(Object* object, char* string, int font, int color, int a5, Rect* rect); +void textObjectsRenderInRect(Rect* rect); +int textObjectsGetCount(); +void textObjectsTicker(); +void textObjectFindPlacement(TextObject* textObject); +void textObjectsRemoveByOwner(Object* object); + +#endif /* TEXT_OBJECT_H */ diff --git a/src/tile.c b/src/tile.c new file mode 100644 index 0000000..f39d400 --- /dev/null +++ b/src/tile.c @@ -0,0 +1,1921 @@ +#include "tile.h" + +#include "color.h" +#include "config.h" +#include "core.h" +#include "debug.h" +#include "draw.h" +#include "game_config.h" +#include "game_mouse.h" +#include "light.h" +#include "map.h" +#include "object.h" + +#include +#include + +#define _USE_MATH_DEFINES +#include + +// 0x50E7C7 +double const dbl_50E7C7 = -4.0; + +// 0x51D950 +bool _borderInitialized = false; + +// 0x51D954 +bool _scroll_blocking_on = true; + +// 0x51D958 +bool _scroll_limiting_on = true; + +// 0x51D95C +int _show_roof = 1; + +// 0x51D960 +int _show_grid = 0; + +// 0x51D964 +TileWindowRefreshElevationProc* _tile_refresh = _refresh_game; + +// 0x51D968 +bool gTileEnabled = true; + +// 0x51D96C +const int _off_tile[6] = { + 16, + 32, + 16, + -16, + -32, + -16, +}; + +// 0x51D984 +const int dword_51D984[6] = { + -12, + 0, + 12, + 12, + 0, + -12, +}; + +// 0x51D99C +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 +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 +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 +STRUCT_51DB0C _rightside_up_triangles[5] = { + { 2, 3, 0 }, + { 3, 4, 1 }, + { 5, 6, 3 }, + { 6, 7, 4 }, + { 8, 9, 6 }, +}; + +// 0x51DB48 +STRUCT_51DB48 _upside_down_triangles[5] = { + { 0, 3, 1 }, + { 2, 5, 3 }, + { 3, 6, 4 }, + { 5, 8, 6 }, + { 6, 9, 7 }, +}; + +// 0x668224 +int _intensity_map[3280]; + +// 0x66B564 +int _dir_tile2[2][6]; + +// Deltas to perform tile calculations in given direction. +// +// 0x66B594 +int _dir_tile[2][6]; + +// 0x66B5C4 +unsigned char _tile_grid_blocked[512]; + +// 0x66B7C4 +unsigned char _tile_grid_occupied[512]; + +// 0x66B9C4 +unsigned char _tile_mask[512]; + +// 0x66BBC4 +int _tile_border = 0; + +// 0x66BBC8 +int dword_66BBC8 = 0; + +// 0x66BBCC +int dword_66BBCC = 0; + +// 0x66BBD0 +int dword_66BBD0 = 0; + +// 0x66BBD4 +Rect gTileWindowRect; + +// 0x66BBE4 +unsigned char _tile_grid[32 * 16]; + +// 0x66BDE4 +int _square_rect; + +// 0x66BDE8 +int _square_x; + +// 0x66BDEC +int _square_offx; + +// 0x66BDF0 +int _square_offy; + +// 0x66BDF4 +TileWindowRefreshProc* gTileWindowRefreshProc; + +// 0x66BDF8 +int _tile_offy; + +// 0x66BDFC +int _tile_offx; + +// 0x66BE00 +int gSquareGridSize; + +// Number of tiles horizontally. +// +// Currently this value is always 200. +// +// 0x66BE04 +int gHexGridWidth; + +// 0x66BE08 +TileData** _squares; + +// 0x66BE0C +unsigned char* gTileWindowBuffer; + +// Number of tiles vertically. +// +// Currently this value is always 200. +// +// 0x66BE10 +int gHexGridHeight; + +// 0x66BE14 +int gTileWindowHeight; + +// 0x66BE18 +int _tile_x; + +// 0x66BE1C +int _tile_y; + +// The number of tiles in the hex grid. +// +// 0x66BE20 +int gHexGridSize; + +// 0x66BE24 +int gSquareGridHeight; + +// 0x66BE28 +int gTileWindowPitch; + +// 0x66BE2C +int gSquareGridWidth; + +// 0x66BE30 +int gTileWindowWidth; + +// 0x66BE34 +int gCenterTile; + +// 0x4B0C40 +int tileInit(TileData** a1, int squareGridWidth, int squareGridHeight, int hexGridWidth, int hexGridHeight, unsigned char* buf, 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; + + gSquareGridWidth = squareGridWidth; + _squares = a1; + gHexGridHeight = hexGridHeight; + gSquareGridHeight = squareGridHeight; + gHexGridWidth = hexGridWidth; + _dir_tile[0][0] = -1; + _dir_tile[0][4] = 1; + _dir_tile[1][1] = -1; + gHexGridSize = hexGridWidth * hexGridHeight; + _dir_tile[1][3] = 1; + gTileWindowBuffer = buf; + _dir_tile2[0][0] = -1; + gTileWindowWidth = windowWidth; + _dir_tile2[0][3] = -1; + gTileWindowHeight = windowHeight; + _dir_tile2[1][1] = 1; + gTileWindowPitch = windowPitch; + _dir_tile2[1][2] = 1; + gTileWindowRect.right = windowWidth - 1; + gSquareGridSize = squareGridHeight * squareGridWidth; + gTileWindowRect.bottom = windowHeight - 1; + gTileWindowRect.left = 0; + gTileWindowRefreshProc = windowRefreshProc; + gTileWindowRect.top = 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); + + bufferFill(_tile_grid, 32, 16, 32, 0); + bufferDrawLine(_tile_grid, 32, 16, 0, 31, 4, _colorTable[4228]); + bufferDrawLine(_tile_grid, 32, 31, 4, 31, 12, _colorTable[4228]); + bufferDrawLine(_tile_grid, 32, 31, 12, 16, 15, _colorTable[4228]); + bufferDrawLine(_tile_grid, 32, 0, 12, 16, 15, _colorTable[4228]); + bufferDrawLine(_tile_grid, 32, 0, 4, 0, 12, _colorTable[4228]); + bufferDrawLine(_tile_grid, 32, 16, 0, 0, 4, _colorTable[4228]); + + bufferFill(_tile_grid_occupied, 32, 16, 32, 0); + bufferDrawLine(_tile_grid_occupied, 32, 16, 0, 31, 4, _colorTable[31]); + bufferDrawLine(_tile_grid_occupied, 32, 31, 4, 31, 12, _colorTable[31]); + bufferDrawLine(_tile_grid_occupied, 32, 31, 12, 16, 15, _colorTable[31]); + bufferDrawLine(_tile_grid_occupied, 32, 0, 12, 16, 15, _colorTable[31]); + bufferDrawLine(_tile_grid_occupied, 32, 0, 4, 0, 12, _colorTable[31]); + bufferDrawLine(_tile_grid_occupied, 32, 16, 0, 0, 4, _colorTable[31]); + + bufferFill(_tile_grid_blocked, 32, 16, 32, 0); + bufferDrawLine(_tile_grid_blocked, 32, 16, 0, 31, 4, _colorTable[31744]); + bufferDrawLine(_tile_grid_blocked, 32, 31, 4, 31, 12, _colorTable[31744]); + bufferDrawLine(_tile_grid_blocked, 32, 31, 12, 16, 15, _colorTable[31744]); + bufferDrawLine(_tile_grid_blocked, 32, 0, 12, 16, 15, _colorTable[31744]); + bufferDrawLine(_tile_grid_blocked, 32, 0, 4, 0, 12, _colorTable[31744]); + bufferDrawLine(_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); + } + + bufferDrawLine(_tile_grid_blocked, 32, v25, v20, v22, v20, _colorTable[31744]); + } + + tileSetCenter(hexGridWidth * (hexGridHeight / 2) + hexGridWidth / 2, 2); + _tile_set_border(windowWidth, windowHeight, hexGridWidth, hexGridHeight); + + char* executable; + configGetString(&gGameConfig, 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 = tileFromScreenXY(-320, -240, 0); + int v2 = tileFromScreenXY(-320, windowHeight + 240, 0); + + _tile_border = abs(hexGridWidth - 1 - v2 % hexGridWidth - _tile_x) + 6; + dword_66BBC8 = abs(_tile_y - v1 / hexGridWidth) + 7; + dword_66BBCC = hexGridWidth - _tile_border - 1; + dword_66BBD0 = hexGridHeight - dword_66BBC8 - 1; + + if ((_tile_border & 1) == 0) { + _tile_border++; + } + + if ((dword_66BBCC & 1) == 0) { + _tile_border--; + } + + _borderInitialized = true; +} + +// NOTE: Collapsed. +// +// 0x4B129C +void _tile_reset_() +{ +} + +// NOTE: Uncollapsed 0x4B129C. +void tileReset() +{ + _tile_reset_(); +} + +// NOTE: Uncollapsed 0x4B129C. +void tileExit() +{ + _tile_reset_(); +} + +// 0x4B12A8 +void tileDisable() +{ + gTileEnabled = false; +} + +// 0x4B12B4 +void tileEnable() +{ + gTileEnabled = true; +} + +// 0x4B12C0 +void tileWindowRefreshRect(Rect* rect, int elevation) +{ + if (gTileEnabled) { + if (elevation == gElevation) { + _tile_refresh(rect, elevation); + } + } +} + +// 0x4B12D8 +void tileWindowRefresh() +{ + if (gTileEnabled) { + _tile_refresh(&gTileWindowRect, gElevation); + } +} + +// 0x4B12F8 +int tileSetCenter(int tile, int flags) +{ + if (tile < 0 || tile >= gHexGridSize) { + return -1; + } + + if ((_scroll_limiting_on & ((flags & TILE_SET_CENTER_FLAG_0x02) == 0)) != 0) { + int tileScreenX; + int tileScreenY; + tileToScreenXY(tile, &tileScreenX, &tileScreenY, gElevation); + + int dudeScreenX; + int dudeScreenY; + tileToScreenXY(gDude->tile, &dudeScreenX, &dudeScreenY, gElevation); + + 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 & ((flags & TILE_SET_CENTER_FLAG_0x02) == 0)) != 0) { + if (_obj_scroll_blocking_at(tile, gElevation) == 0) { + return -1; + } + } + + int v9 = gHexGridWidth - 1 - tile % gHexGridWidth; + int v10 = tile / gHexGridWidth; + + if (_borderInitialized) { + if (v9 <= _tile_border || v9 >= dword_66BBCC || v10 <= dword_66BBC8 || v10 >= dword_66BBD0) { + return -1; + } + } + + _tile_y = v10; + _tile_offx = (gTileWindowWidth - 32) / 2; + _tile_x = v9; + _tile_offy = (gTileWindowHeight - 16) / 2; + + if (v9 & 1) { + _tile_x -= 1; + _tile_offx -= 32; + } + + _square_x = _tile_x / 2; + _square_rect = _tile_y / 2; + _square_offx = _tile_offx - 16; + _square_offy = _tile_offy - 2; + + if (_tile_y & 1) { + _square_offy -= 12; + _square_offx -= 16; + } + + gCenterTile = tile; + + if (flags & TILE_SET_CENTER_FLAG_0x01) { + if (gTileEnabled) { + _tile_refresh(&gTileWindowRect, gElevation); + } + } + + return 0; +} + +// 0x4B1554 +void _refresh_mapper(Rect* rect, int elevation) +{ + Rect rectToUpdate; + + if (rectIntersection(rect, &gTileWindowRect, &rectToUpdate) == -1) { + return; + } + + bufferFill(gTileWindowBuffer + gTileWindowPitch * rectToUpdate.top + rectToUpdate.left, + rectToUpdate.right - rectToUpdate.left + 1, + rectToUpdate.bottom - rectToUpdate.top + 1, + gTileWindowPitch, + 0); + + tileRenderFloorsInRect(&rectToUpdate, elevation); + _grid_render(&rectToUpdate, elevation); + _obj_render_pre_roof(&rectToUpdate, elevation); + tileRenderRoofsInRect(&rectToUpdate, elevation); + _obj_render_post_roof(&rectToUpdate, elevation); + gTileWindowRefreshProc(&rectToUpdate); +} + +// 0x4B15E8 +void _refresh_game(Rect* rect, int elevation) +{ + Rect rectToUpdate; + + if (rectIntersection(rect, &gTileWindowRect, &rectToUpdate) == -1) { + return; + } + + tileRenderFloorsInRect(&rectToUpdate, elevation); + _obj_render_pre_roof(&rectToUpdate, elevation); + tileRenderRoofsInRect(&rectToUpdate, elevation); + _obj_render_post_roof(&rectToUpdate, elevation); + gTileWindowRefreshProc(&rectToUpdate); +} + +// 0x4B166C +int _tile_roof_visible() +{ + return _show_roof; +} + +// 0x4B1674 +int tileToScreenXY(int tile, int* screenX, int* screenY, int elevation) +{ + int v3; + int v4; + int v5; + int v6; + + if (tile < 0 || tile >= gHexGridSize) { + return -1; + } + + v3 = gHexGridWidth - 1 - tile % gHexGridWidth; + v4 = tile / gHexGridWidth; + + *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 tileFromScreenXY(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 = gHexGridWidth - 1 - v11; + if (v12 >= 0 && v12 < gHexGridWidth && v10 >= 0 && v10 < gHexGridHeight) { + return gHexGridWidth * v10 + v12; + } + + return -1; +} + +// tile_distance +// 0x4B185C +int tileDistanceBetween(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; + tileToScreenXY(tile2, &x1, &y1, 0); + + v2 = tile1; + for (i = 0; v2 != tile2; i++) { + // TODO: Looks like inlined rotation_to_tile. + int x2; + int y2; + tileToScreenXY(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 % gHexGridWidth & 1][v9]; + } + + return i; +} + +// 0x4B1994 +bool _tile_in_front_of(int tile1, int tile2) +{ + int x1; + int y1; + tileToScreenXY(tile1, &x1, &y1, 0); + + int x2; + int y2; + tileToScreenXY(tile2, &x2, &y2, 0); + + int dx = x2 - x1; + int dy = y2 - y1; + + return (double)dx <= (double)dy * dbl_50E7C7; +} + +// 0x4B1A00 +bool _tile_to_right_of(int tile1, int tile2) +{ + int x1; + int y1; + tileToScreenXY(tile1, &x1, &y1, 0); + + int x2; + int y2; + tileToScreenXY(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 tileGetTileInDirection(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 % gHexGridWidth) & 1; + newTile += _dir_tile[parity][rotation]; + } + + return newTile; +} + +// rotation_to_tile +// 0x4B1ABC +int tileGetRotationTo(int tile1, int tile2) +{ + int x1; + int y1; + tileToScreenXY(tile1, &x1, &y1, 0); + + int x2; + int y2; + tileToScreenXY(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; + tileToScreenXY(from, &fromX, &fromY, 0); + fromX += 16; + fromY += 8; + + int toX; + int toY; + tileToScreenXY(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 = tileFromScreenXY(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 = tileFromScreenXY(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"); +} + +// Probably returns true if tile is a border. +// +// 0x4B1D20 +int _tile_on_edge(int tile) +{ + if (tile < 0 || tile >= gHexGridSize) { + return 0; + } + + if (tile < gHexGridWidth) { + return 1; + } + + if (tile >= gHexGridSize - gHexGridWidth) { + return 1; + } + + if (tile % gHexGridWidth == 0) { + return 1; + } + + if (tile % gHexGridWidth == gHexGridWidth - 1) { + return 1; + } + + return 0; +} + +// 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 a1, int* a2, int* a3, int elevation) +{ + int v5; + int v6; + int v7; + int v8; + int v9; + + if (a1 < 0 || a1 >= gSquareGridSize) { + return -1; + } + + v5 = gSquareGridWidth - 1 - a1 % gSquareGridWidth; + v6 = a1 / gSquareGridWidth; + v7 = _square_x; + + *a2 = _square_offx; + *a3 = _square_offy; + + v8 = v5 - v7; + *a2 += 48 * v8; + *a3 -= 12 * v8; + + v9 = v6 - _square_rect; + *a2 += 32 * v9; + *a3 += 24 * v9; + + return 0; +} + +// 0x4B1E60 +int squareTileToScreenXY(int a1, int* a2, int* a3, int elevation) +{ + int v5; + int v6; + int v7; + int v8; + int v9; + int v10; + + if (a1 < 0 || a1 >= gSquareGridSize) { + return -1; + } + + v5 = gSquareGridWidth - 1 - a1 % gSquareGridWidth; + v6 = a1 / gSquareGridWidth; + v7 = _square_x; + *a2 = _square_offx; + *a3 = _square_offy; + + v8 = v5 - v7; + *a2 += 48 * v8; + *a3 -= 12 * v8; + + v9 = v6 - _square_rect; + *a2 += 32 * v9; + v10 = 24 * v9 + *a3; + *a3 = v10; + *a3 = v10 - 96; + + return 0; +} + +// 0x4B1F04 +int _square_num(int x, int y, int elevation) +{ + int v5; + int v6; + + _square_xy(x, y, elevation, &v6, &v5); + + if (v6 >= 0 && v6 < gSquareGridWidth && v5 >= 0 && v5 < gSquareGridHeight) { + return v6 + gSquareGridWidth * v5; + } + + return -1; +} + +// 0x4B1F94 +void _square_xy(int a1, int a2, int elevation, int* a3, int* a4) +{ + int v4; + int v5; + int v6; + int v8; + + v4 = a1 - _square_offx; + v5 = a2 - _square_offy - 12; + v6 = 3 * v4 - 4 * v5; + *a3 = v6 >= 0 ? (v6 / 192) : ((v6 + 1) / 192 - 1); + + v8 = 4 * v5 + v4; + *a4 = v8 >= 0 + ? ((v8 - ((v8 >> 31) << 7)) >> 7) + : ((((v8 + 1) - (((v8 + 1) >> 31) << 7)) >> 7) - 1); + + *a3 += _square_x; + *a4 += _square_rect; + + *a3 = gSquareGridWidth - 1 - *a3; +} + +// 0x4B203C +void _square_xy_roof(int a1, int a2, int elevation, int* a3, int* a4) +{ + int v4; + int v5; + int v6; + int v8; + + v4 = a1 - _square_offx; + v5 = a2 + 96 - _square_offy - 12; + v6 = 3 * v4 - 4 * v5; + + *a3 = (v6 >= 0) ? (v6 / 192) : ((v6 + 1) / 192 - 1); + + v8 = 4 * v5 + v4; + *a4 = (v8 >= 0) + ? ((v8 - ((v8 >> 31) << 7)) >> 7) + : ((((v8 + 1) - (((v8 + 1) >> 31) << 7)) >> 7) - 1); + + *a3 += _square_x; + *a4 += _square_rect; + + *a3 = gSquareGridWidth - 1 - *a3; +} + +// 0x4B20E8 +void tileRenderRoofsInRect(Rect* rect, int elevation) +{ + if (!_show_roof) { + return; + } + + int temp; + int minY; + int minX; + int maxX; + int maxY; + + _square_xy_roof(rect->left, rect->top, elevation, &temp, &minY); + _square_xy_roof(rect->right, rect->top, elevation, &minX, &temp); + _square_xy_roof(rect->left, rect->bottom, elevation, &maxX, &temp); + _square_xy_roof(rect->right, rect->bottom, elevation, &temp, &maxY); + + if (minX < 0) { + minX = 0; + } + + if (minX >= gSquareGridWidth) { + minX = gSquareGridWidth - 1; + } + + if (minY < 0) { + minY = 0; + } + + // FIXME: Probably a bug - testing X, then changing Y. + if (minX >= gSquareGridHeight) { + minY = gSquareGridHeight - 1; + } + + int light = lightGetLightLevel(); + + int baseSquareTile = gSquareGridWidth * 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 = buildFid(4, frmId & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + int screenX; + int screenY; + squareTileToScreenXY(squareTile, &screenX, &screenY, elevation); + tileRenderRoof(fid, screenX, screenY, rect, light); + } + } + } + baseSquareTile += gSquareGridWidth; + } +} + +// 0x4B22D0 +void _roof_fill_on(int a1, int a2, int elevation) +{ + while ((a1 >= 0 && a1 < gSquareGridWidth) && (a2 >= 0 && a2 < gSquareGridHeight)) { + int squareTile = gSquareGridWidth * a2 + a1; + int value = _squares[elevation]->field_0[squareTile]; + int upper = (value >> 16) & 0xFFFF; + + int id = upper & 0xFFF; + if (buildFid(4, id, 0, 0, 0) == buildFid(4, 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 { + sub_4B23DC(a1, a2, elevation); + } +} + +// 0x4B23DC +void sub_4B23DC(int a1, int a2, int elevation) +{ + while ((a1 >= 0 && a1 < gSquareGridWidth) && (a2 >= 0 && a2 < gSquareGridHeight)) { + int squareTile = gSquareGridWidth * a2 + a1; + int value = _squares[elevation]->field_0[squareTile]; + int upper = (value >> 16) & 0xFFFF; + + int id = upper & 0xFFF; + if (buildFid(4, id, 0, 0, 0) == buildFid(4, 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); + + sub_4B23DC(a1 - 1, a2, elevation); + sub_4B23DC(a1 + 1, a2, elevation); + sub_4B23DC(a1, a2 - 1, elevation); + + a2++; + } +} + +// 0x4B24E0 +void tileRenderRoof(int fid, int x, int y, Rect* rect, int light) +{ + CacheEntry* tileFrmHandle; + Art* tileFrm = artLock(fid, &tileFrmHandle); + if (tileFrm == NULL) { + return; + } + + int tileWidth = artGetWidth(tileFrm, 0, 0); + int tileHeight = artGetHeight(tileFrm, 0, 0); + + Rect tileRect; + tileRect.left = x; + tileRect.top = y; + tileRect.right = x + tileWidth - 1; + tileRect.bottom = y + tileHeight - 1; + + if (rectIntersection(&tileRect, rect, &tileRect) == 0) { + unsigned char* tileFrmBuffer = artGetFrameData(tileFrm, 0, 0); + tileFrmBuffer += tileWidth * (tileRect.top - y) + (tileRect.left - x); + + CacheEntry* eggFrmHandle; + Art* eggFrm = artLock(gEgg->fid, &eggFrmHandle); + if (eggFrm != NULL) { + int eggWidth = artGetWidth(eggFrm, 0, 0); + int eggHeight = artGetHeight(eggFrm, 0, 0); + + int eggScreenX; + int eggScreenY; + tileToScreenXY(gEgg->tile, &eggScreenX, &eggScreenY, gEgg->elevation); + + eggScreenX += 16; + eggScreenY += 8; + + eggScreenX += eggFrm->xOffsets[0]; + eggScreenY += eggFrm->yOffsets[0]; + + eggScreenX += gEgg->x; + eggScreenY += gEgg->y; + + Rect eggRect; + eggRect.left = eggScreenX - eggWidth / 2; + eggRect.top = eggScreenY - eggHeight + 1; + eggRect.right = eggRect.left + eggWidth - 1; + eggRect.bottom = eggScreenY; + + gEgg->sx = eggRect.left; + gEgg->sy = eggRect.top; + + Rect intersectedRect; + if (rectIntersection(&eggRect, &tileRect, &intersectedRect) == 0) { + Rect rects[4]; + + rects[0].left = tileRect.left; + rects[0].top = tileRect.top; + rects[0].right = tileRect.right; + rects[0].bottom = intersectedRect.top - 1; + + rects[1].left = tileRect.left; + rects[1].top = intersectedRect.top; + rects[1].right = intersectedRect.left - 1; + rects[1].bottom = intersectedRect.bottom; + + rects[2].left = intersectedRect.right + 1; + rects[2].top = intersectedRect.top; + rects[2].right = tileRect.right; + rects[2].bottom = intersectedRect.bottom; + + rects[3].left = tileRect.left; + rects[3].top = intersectedRect.bottom + 1; + rects[3].right = tileRect.right; + rects[3].bottom = tileRect.bottom; + + for (int i = 0; i < 4; i++) { + Rect* cr = &(rects[i]); + if (cr->left <= cr->right && cr->top <= cr->bottom) { + _dark_trans_buf_to_buf(tileFrmBuffer + tileWidth * (cr->top - tileRect.top) + (cr->left - tileRect.left), + cr->right - cr->left + 1, + cr->bottom - cr->top + 1, + tileWidth, + gTileWindowBuffer, + cr->left, + cr->top, + gTileWindowPitch, + light); + } + } + + unsigned char* eggBuf = artGetFrameData(eggFrm, 0, 0); + _intensity_mask_buf_to_buf(tileFrmBuffer + tileWidth * (intersectedRect.top - tileRect.top) + (intersectedRect.left - tileRect.left), + intersectedRect.right - intersectedRect.left + 1, + intersectedRect.bottom - intersectedRect.top + 1, + tileWidth, + gTileWindowBuffer + gTileWindowPitch * intersectedRect.top + intersectedRect.left, + gTileWindowPitch, + eggBuf + eggWidth * (intersectedRect.top - eggRect.top) + (intersectedRect.left - eggRect.left), + eggWidth, + light); + } else { + _dark_trans_buf_to_buf(tileFrmBuffer, tileRect.right - tileRect.left + 1, tileRect.bottom - tileRect.top + 1, tileWidth, gTileWindowBuffer, tileRect.left, tileRect.top, gTileWindowPitch, light); + } + + artUnlock(eggFrmHandle); + } + } + + artUnlock(tileFrmHandle); +} + +// 0x4B2944 +void tileRenderFloorsInRect(Rect* rect, int elevation) +{ + int v9; + int v8; + int v7; + int v10; + int v11; + + _square_xy(rect->left, rect->top, elevation, &v11, &v9); + _square_xy(rect->right, rect->top, elevation, &v10, &v11); + _square_xy(rect->left, rect->bottom, elevation, &v8, &v11); + _square_xy(rect->right, rect->bottom, elevation, &v11, &v7); + + if (v10 < 0) { + v10 = 0; + } + + if (v10 >= gSquareGridWidth) { + v10 = gSquareGridWidth - 1; + } + + if (v9 < 0) { + v9 = 0; + } + + if (v10 >= gSquareGridHeight) { + v9 = gSquareGridHeight - 1; + } + + lightGetLightLevel(); + + v11 = gSquareGridWidth * v9; + for (int v15 = v9; v15 <= v7; v15++) { + for (int i = v10; i <= v8; i++) { + int v3 = v11 + 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 = buildFid(4, frmId & 0xFFF, 0, 0, 0); + tileRenderFloor(fid, v12, v13, rect); + } + } + v11 += gSquareGridWidth; + } +} + +// 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 = gSquareGridWidth * tileY + tileX; + int upper = ptr->field_0[gSquareGridWidth * tileY + tileX] >> 16; + int fid = buildFid(4, upper & 0xFFF, 0, 0, 0); + if (fid != buildFid(4, 1, 0, 0, 0)) { + if ((((upper & 0xF000) >> 12) & 1) == 0) { + int fid = buildFid(4, upper & 0xFFF, 0, 0, 0); + CacheEntry* handle; + Art* art = artLock(fid, &handle); + if (art != NULL) { + unsigned char* data = artGetFrameData(art, 0, 0); + if (data != NULL) { + int v18; + int v17; + squareTileToScreenXY(idx, &v18, &v17, elevation); + + int width = artGetWidth(art, 0, 0); + if (data[width * (y - v17) + x - v18] != 0) { + result = true; + } + } + artUnlock(handle); + } + } + } + + return result; +} + +// 0x4B2E98 +void _grid_render(Rect* rect, int elevation) +{ + if (!_show_grid) { + return; + } + + for (int y = rect->top - 12; y < rect->bottom + 12; y += 6) { + for (int x = rect->left - 32; x < rect->right + 32; x += 16) { + int tile = tileFromScreenXY(x, y, elevation); + _draw_grid(tile, elevation, rect); + } + } +} + +// 0x4B2F4C +void _draw_grid(int tile, int elevation, Rect* rect) +{ + if (tile == -1) { + return; + } + + int x; + int y; + tileToScreenXY(tile, &x, &y, elevation); + + Rect r; + r.left = x; + r.top = y; + r.right = x + 32 - 1; + r.bottom = y + 16 - 1; + + if (rectIntersection(&r, rect, &r) == -1) { + return; + } + + if (_obj_blocking_at(NULL, tile, elevation) != NULL) { + blitBufferToBufferTrans(_tile_grid_blocked + 32 * (r.top - y) + (r.left - x), + r.right - r.left + 1, + r.bottom - r.top + 1, + 32, + gTileWindowBuffer + gTileWindowPitch * r.top + r.left, + gTileWindowPitch); + return; + } + + if (_obj_occupied(tile, elevation)) { + blitBufferToBufferTrans(_tile_grid_occupied + 32 * (r.top - y) + (r.left - x), + r.right - r.left + 1, + r.bottom - r.top + 1, + 32, + gTileWindowBuffer + gTileWindowPitch * r.top + r.left, + gTileWindowPitch); + return; + } + + _translucent_trans_buf_to_buf(_tile_grid_occupied + 32 * (r.top - y) + (r.left - x), + r.right - r.left + 1, + r.bottom - r.top + 1, + 32, + gTileWindowBuffer + gTileWindowPitch * r.top + r.left, + 0, + 0, + gTileWindowPitch, + _wallBlendTable, + _commonGrayTable); +} + +// 0x4B30C4 +void tileRenderFloor(int fid, int x, int y, Rect* rect) +{ + if (artIsObjectTypeHidden((fid & 0xF000000) >> 24) != 0) { + return; + } + + CacheEntry* cacheEntry; + Art* art = artLock(fid, &cacheEntry); + if (art == NULL) { + return; + } + + int elev = gElevation; + int left = rect->left; + int top = rect->top; + int width = rect->right - rect->left + 1; + int height = rect->bottom - rect->top + 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 > gTileWindowWidth) { + width = gTileWindowWidth - left; + } + + if (top + height > gTileWindowHeight) { + height = gTileWindowHeight - top; + } + + if (x >= gTileWindowWidth || x > rect->right || y >= gTileWindowHeight || y > rect->bottom) goto out; + + frameWidth = artGetWidth(art, 0, 0); + frameHeight = artGetHeight(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 = tileFromScreenXY(savedX, savedY + 13, gElevation); + if (v15 != -1) { + int v17 = lightGetLightLevel(); + 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* buf = artGetFrameData(art, 0, 0); + _dark_trans_buf_to_buf(buf + frameWidth * v78 + v79, v77, v76, frameWidth, gTileWindowBuffer, x, y, gTileWindowPitch, _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 = gTileWindowBuffer + gTileWindowPitch * y + x; + unsigned char* v67 = artGetFrameData(art, 0, 0) + frameWidth * v78 + v79; + int* v68 = &(_intensity_map[160 + 80 * v78]) + v79; + int v86 = frameWidth - v77; + int v85 = gTileWindowPitch - v77; + int v87 = 80 - v77; + + while (--v76 != -1) { + for (int kk = 0; kk < v77; kk++) { + if (*v67 != 0) { + int t = (*v67 << 8) + (*v68 >> 9); + *v66 = _intensityColorTable[t]; + } + v67++; + v68++; + v66++; + } + v66 += v85; + v68 += v87; + v67 += v86; + } + } + +out: + + artUnlock(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; + tileToScreenXY(from, &fromX, &fromY, gElevation); + fromX += 16; + fromY += 8; + + int toX; + int toY; + tileToScreenXY(to, &toX, &toY, gElevation); + 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 = tileFromScreenXY(tileX, tileY, gElevation); + 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 = tileFromScreenXY(tileX, tileY, gElevation); + 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 == gCenterTile) { + return -1; + } + + int oldCenterTile = gCenterTile; + + int v9[200]; + int count = _tile_make_line(gCenterTile, tile, v9, 200); + if (count == 0) { + return -1; + } + + int index = 1; + for (; index < count; index++) { + if (tileSetCenter(v9[index], 0) == -1) { + break; + } + } + + int rc = 0; + if ((flags & 0x01) != 0) { + if (index != count) { + tileSetCenter(oldCenterTile, 0); + rc = -1; + } + } + + if ((flags & 0x02) != 0) { + if (gTileEnabled) { + _tile_refresh(&gTileWindowRect, gElevation); + } + } + + return rc; +} diff --git a/src/tile.h b/src/tile.h new file mode 100644 index 0000000..5aa39c6 --- /dev/null +++ b/src/tile.h @@ -0,0 +1,142 @@ +#ifndef TILE_H +#define TILE_H + +#include "geometry.h" +#include "map.h" +#include "obj_types.h" + +#include + +#define TILE_SET_CENTER_FLAG_0x01 0x01 +#define TILE_SET_CENTER_FLAG_0x02 0x02 + +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; + +typedef void(TileWindowRefreshProc)(Rect* rect); +typedef void(TileWindowRefreshElevationProc)(Rect* rect, int elevation); + +extern double const dbl_50E7C7; + +extern bool _borderInitialized; +extern bool _scroll_blocking_on; +extern bool _scroll_limiting_on; +extern int _show_roof; +extern int _show_grid; +extern TileWindowRefreshElevationProc* _tile_refresh; +extern bool gTileEnabled; +extern const int _off_tile[6]; +extern const int dword_51D984[6]; +extern STRUCT_51D99C _rightside_up_table[13]; +extern STRUCT_51DA04 _upside_down_table[13]; +extern STRUCT_51DA6C _verticies[10]; +extern STRUCT_51DB0C _rightside_up_triangles[5]; +extern STRUCT_51DB48 _upside_down_triangles[5]; + +extern int _intensity_map[3280]; +extern int _dir_tile2[2][6]; +extern int _dir_tile[2][6]; +extern unsigned char _tile_grid_blocked[512]; +extern unsigned char _tile_grid_occupied[512]; +extern unsigned char _tile_mask[512]; +extern int _tile_border; +extern int dword_66BBC8; +extern int dword_66BBCC; +extern int dword_66BBD0; +extern Rect gTileWindowRect; +extern unsigned char _tile_grid[512]; +extern int _square_rect; +extern int _square_x; +extern int _square_offx; +extern int _square_offy; +extern TileWindowRefreshProc* gTileWindowRefreshProc; +extern int _tile_offy; +extern int _tile_offx; +extern int gSquareGridSize; +extern int gHexGridWidth; +extern TileData** _squares; +extern unsigned char* gTileWindowBuffer; +extern int gHexGridHeight; +extern int gTileWindowHeight; +extern int _tile_x; +extern int _tile_y; +extern int gHexGridSize; +extern int gSquareGridHeight; +extern int gTileWindowPitch; +extern int gSquareGridWidth; +extern int gTileWindowWidth; +extern int gCenterTile; + +int tileInit(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 a1, int a2, int a3, int a4); +void _tile_reset_(); +void tileReset(); +void tileExit(); +void tileDisable(); +void tileEnable(); +void tileWindowRefreshRect(Rect* rect, int elevation); +void tileWindowRefresh(); +int tileSetCenter(int a1, int a2); +void _refresh_mapper(Rect* rect, int elevation); +void _refresh_game(Rect* rect, int elevation); +int _tile_roof_visible(); +int tileToScreenXY(int tile, int* x, int* y, int elevation); +int tileFromScreenXY(int x, int y, int elevation); +int tileDistanceBetween(int a1, int a2); +bool _tile_in_front_of(int tile1, int tile2); +bool _tile_to_right_of(int tile1, int tile2); +int tileGetTileInDirection(int tile, int rotation, int distance); +int tileGetRotationTo(int a1, int a2); +int _tile_num_beyond(int from, int to, int distance); +int _tile_on_edge(int a1); +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 a1, int* a2, int* a3, int elevation); +int squareTileToScreenXY(int a1, int* a2, int* a3, int elevation); +int _square_num(int x, int y, int elevation); +void _square_xy(int a1, int a2, int elevation, int* a3, int* a4); +void _square_xy_roof(int a1, int a2, int elevation, int* a3, int* a4); +void tileRenderRoofsInRect(Rect* rect, int elevation); +void _roof_fill_on(int x, int y, int elevation); +void _tile_fill_roof(int x, int y, int elevation, int a4); +void sub_4B23DC(int x, int y, int elevation); +void tileRenderRoof(int fid, int x, int y, Rect* rect, int light); +void tileRenderFloorsInRect(Rect* rect, int elevation); +bool _square_roof_intersect(int x, int y, int elevation); +void _grid_render(Rect* rect, int elevation); +void _draw_grid(int tile, int elevation, Rect* rect); +void tileRenderFloor(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 /* TILE_H */ diff --git a/src/trait.c b/src/trait.c new file mode 100644 index 0000000..43eb1c8 --- /dev/null +++ b/src/trait.c @@ -0,0 +1,290 @@ +#include "trait.h" + +#include "game.h" +#include "object.h" +#include "skill.h" +#include "stat.h" + +#include + +// 0x66BE38 +MessageList gTraitsMessageList; + +// List of selected traits. +// +// 0x66BE40 +int gSelectedTraits[TRAITS_MAX_SELECTED_COUNT]; + +// 0x51DB84 +TraitDescription gTraitDescriptions[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 traitsInit() +{ + if (!messageListInit(&gTraitsMessageList)) { + return -1; + } + + char path[FILENAME_MAX]; + sprintf(path, "%s%s", asc_5186C8, "trait.msg"); + + if (!messageListLoad(&gTraitsMessageList, path)) { + return -1; + } + + for (int trait = 0; trait < TRAIT_COUNT; trait++) { + MessageListItem messageListItem; + + messageListItem.num = 100 + trait; + if (messageListGetItem(&gTraitsMessageList, &messageListItem)) { + gTraitDescriptions[trait].name = messageListItem.text; + } + + messageListItem.num = 200 + trait; + if (messageListGetItem(&gTraitsMessageList, &messageListItem)) { + gTraitDescriptions[trait].description = messageListItem.text; + } + } + + // NOTE: Uninline. + traitsReset(); + + return true; +} + +// 0x4B3ADC +void traitsReset() +{ + for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { + gSelectedTraits[index] = -1; + } +} + +// 0x4B3AF8 +void traitsExit() +{ + messageListFree(&gTraitsMessageList); +} + +// Loads trait system state from save game. +// +// 0x4B3B08 +int traitsLoad(File* stream) +{ + return fileReadInt32List(stream, gSelectedTraits, TRAITS_MAX_SELECTED_COUNT); +} + +// Saves trait system state to save game. +// +// 0x4B3B28 +int traitsSave(File* stream) +{ + return fileWriteInt32List(stream, gSelectedTraits, TRAITS_MAX_SELECTED_COUNT); +} + +// Sets selected traits. +// +// 0x4B3B48 +void traitsSetSelected(int trait1, int trait2) +{ + gSelectedTraits[0] = trait1; + gSelectedTraits[1] = trait2; +} + +// Returns selected traits. +// +// 0x4B3B54 +void traitsGetSelected(int* trait1, int* trait2) +{ + *trait1 = gSelectedTraits[0]; + *trait2 = gSelectedTraits[1]; +} + +// Returns a name of the specified trait, or `NULL` if the specified trait is +// out of range. +// +// 0x4B3B68 +char* traitGetName(int trait) +{ + return trait >= 0 && trait < TRAIT_COUNT ? gTraitDescriptions[trait].name : NULL; +} + +// Returns a description of the specified trait, or `NULL` if the specified +// trait is out of range. +// +// 0x4B3B88 +char* traitGetDescription(int trait) +{ + return trait >= 0 && trait < TRAIT_COUNT ? gTraitDescriptions[trait].description : NULL; +} + +// Return an art ID of the specified trait, or `0` if the specified trait is +// out of range. +// +// 0x4B3BA8 +int traitGetFrmId(int trait) +{ + return trait >= 0 && trait < TRAIT_COUNT ? gTraitDescriptions[trait].frmId : 0; +} + +// Returns `true` if the specified trait is selected. +// +// 0x4B3BC8 +bool traitIsSelected(int trait) +{ + return gSelectedTraits[0] == trait || gSelectedTraits[1] == trait; +} + +// Returns stat modifier depending on selected traits. +// +// 0x4B3C7C +int traitGetStatModifier(int stat) +{ + int modifier = 0; + + switch (stat) { + case STAT_STRENGTH: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + if (traitIsSelected(TRAIT_BRUISER)) { + modifier += 2; + } + break; + case STAT_PERCEPTION: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + break; + case STAT_ENDURANCE: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + break; + case STAT_CHARISMA: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + break; + case STAT_INTELLIGENCE: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + break; + case STAT_AGILITY: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + if (traitIsSelected(TRAIT_SMALL_FRAME)) { + modifier += 1; + } + break; + case STAT_LUCK: + if (traitIsSelected(TRAIT_GIFTED)) { + modifier += 1; + } + break; + case STAT_MAXIMUM_ACTION_POINTS: + if (traitIsSelected(TRAIT_BRUISER)) { + modifier -= 2; + } + break; + case STAT_ARMOR_CLASS: + if (traitIsSelected(TRAIT_KAMIKAZE)) { + modifier -= critterGetBaseStat(gDude, STAT_ARMOR_CLASS); + } + break; + case STAT_MELEE_DAMAGE: + if (traitIsSelected(TRAIT_HEAVY_HANDED)) { + modifier += 4; + } + break; + case STAT_CARRY_WEIGHT: + if (traitIsSelected(TRAIT_SMALL_FRAME)) { + modifier -= 10 * critterGetBaseStat(gDude, STAT_STRENGTH); + } + break; + case STAT_SEQUENCE: + if (traitIsSelected(TRAIT_KAMIKAZE)) { + modifier += 5; + } + break; + case STAT_HEALING_RATE: + if (traitIsSelected(TRAIT_FAST_METABOLISM)) { + modifier += 2; + } + break; + case STAT_CRITICAL_CHANCE: + if (traitIsSelected(TRAIT_FINESSE)) { + modifier += 10; + } + break; + case STAT_BETTER_CRITICALS: + if (traitIsSelected(TRAIT_HEAVY_HANDED)) { + modifier -= 30; + } + break; + case STAT_RADIATION_RESISTANCE: + if (traitIsSelected(TRAIT_FAST_METABOLISM)) { + modifier -= -critterGetBaseStat(gDude, STAT_RADIATION_RESISTANCE); + } + break; + case STAT_POISON_RESISTANCE: + if (traitIsSelected(TRAIT_FAST_METABOLISM)) { + modifier -= -critterGetBaseStat(gDude, STAT_POISON_RESISTANCE); + } + break; + } + + return modifier; +} + +// Returns skill modifier depending on selected traits. +// +// 0x4B40FC +int traitGetSkillModifier(int skill) +{ + int modifier = 0; + + if (traitIsSelected(TRAIT_GIFTED)) { + modifier -= 10; + } + + if (traitIsSelected(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; +} diff --git a/src/trait.h b/src/trait.h new file mode 100644 index 0000000..4ce92a8 --- /dev/null +++ b/src/trait.h @@ -0,0 +1,43 @@ +#ifndef TRAIT_H +#define TRAIT_H + +#include "db.h" +#include "message.h" +#include "trait_defs.h" + +#include + +// 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; + +extern MessageList gTraitsMessageList; +extern int gSelectedTraits[TRAITS_MAX_SELECTED_COUNT]; +extern TraitDescription gTraitDescriptions[TRAIT_COUNT]; + +int traitsInit(); +void traitsReset(); +void traitsExit(); +int traitsLoad(File* stream); +int traitsSave(File* stream); +void traitsSetSelected(int trait1, int trait2); +void traitsGetSelected(int* trait1, int* trait2); +char* traitGetName(int trait); +char* traitGetDescription(int trait); +int traitGetFrmId(int trait); +bool traitIsSelected(int trait); +int traitGetStatModifier(int stat); +int traitGetSkillModifier(int skill); + +#endif /* TRAIT_H */ diff --git a/src/trait_defs.h b/src/trait_defs.h new file mode 100644 index 0000000..8b11634 --- /dev/null +++ b/src/trait_defs.h @@ -0,0 +1,28 @@ +#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 */ diff --git a/src/trap.c b/src/trap.c new file mode 100644 index 0000000..631a57b --- /dev/null +++ b/src/trap.c @@ -0,0 +1,8 @@ +#include "trap.h" + +// NOTE: Likely collapsed trapInit/trapExit. +// +// 0x4B4190 +void _trap_init() +{ +} diff --git a/src/trap.h b/src/trap.h new file mode 100644 index 0000000..f53a924 --- /dev/null +++ b/src/trap.h @@ -0,0 +1,6 @@ +#ifndef TRAP_H +#define TRAP_H + +void _trap_init(); + +#endif /* TRAP_H */ diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..2c4f033 --- /dev/null +++ b/src/version.c @@ -0,0 +1,9 @@ +#include "version.h" + +#include + +// 0x4B4580 +void versionGetVersion(char* dest) +{ + sprintf(dest, "FALLOUT II %d.%02d", VERSION_MAJOR, VERSION_MINOR); +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..0c54965 --- /dev/null +++ b/src/version.h @@ -0,0 +1,14 @@ +#ifndef VERSION_H +#define 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 versionGetVersion(char* dest); + +#endif /* VERSION_H */ diff --git a/src/widget.c b/src/widget.c new file mode 100644 index 0000000..b16820c --- /dev/null +++ b/src/widget.c @@ -0,0 +1,91 @@ +#include "widget.h" + +#include "color.h" +#include "text_font.h" +#include "window.h" + +// 0x50EB1C +const float flt_50EB1C = 31.0; + +// 0x50EB20 +const float flt_50EB20 = 31.0; + +// 0x66E6A0 +int _updateRegions[32]; + +// 0x4B5A64 +void _showRegion(int a1) +{ + // TODO: Incomplete. +} + +// 0x4B5C24 +int _update_widgets() +{ + for (int index = 0; index < 32; index++) { + if (_updateRegions[index]) { + _showRegion(_updateRegions[index]); + } + } + + return 1; +} + +// 0x4B6120 +int widgetGetFont() +{ + return gWidgetFont; +} + +// 0x4B6128 +int widgetSetFont(int a1) +{ + gWidgetFont = a1; + fontSetCurrent(a1); + return 1; +} + +// 0x4B6160 +int widgetGetTextFlags() +{ + return gWidgetTextFlags; +} + +// 0x4B6168 +int widgetSetTextFlags(int a1) +{ + gWidgetTextFlags = a1; + return 1; +} + +// 0x4B6174 +unsigned char widgetGetTextColor() +{ + return _colorTable[_currentTextColorB | (_currentTextColorG << 5) | (_currentTextColorR << 10)]; +} + +// 0x4B6198 +unsigned char widgetGetHighlightColor() +{ + return _colorTable[_currentHighlightColorB | (_currentHighlightColorG << 5) | (_currentHighlightColorR << 10)]; +} + +// 0x4B61BC +int widgetSetTextColor(float a1, float a2, float a3) +{ + _currentTextColorR = (int)(a1 * flt_50EB1C); + _currentTextColorG = (int)(a2 * flt_50EB1C); + _currentTextColorB = (int)(a3 * flt_50EB1C); + + return 1; +} + +// 0x4B6208 +int widgetSetHighlightColor(float a1, float a2, float a3) +{ + _currentHighlightColorR = (int)(a1 * flt_50EB20); + _currentHighlightColorG = (int)(a2 * flt_50EB20); + _currentHighlightColorB = (int)(a3 * flt_50EB20); + + return 1; +} diff --git a/src/widget.h b/src/widget.h new file mode 100644 index 0000000..14e2e6e --- /dev/null +++ b/src/widget.h @@ -0,0 +1,20 @@ +#ifndef WIDGET_H +#define WIDGET_H + +extern const float flt_50EB1C; +extern const float flt_50EB20; + +extern int _updateRegions[32]; + +void _showRegion(int a1); +int _update_widgets(); +int widgetGetFont(); +int widgetSetFont(int a1); +int widgetGetTextFlags(); +int widgetSetTextFlags(int a1); +unsigned char widgetGetTextColor(); +unsigned char widgetGetHighlightColor(); +int widgetSetTextColor(float a1, float a2, float a3); +int widgetSetHighlightColor(float a1, float a2, float a3); + +#endif /* WIDGET_H */ diff --git a/src/win32.c b/src/win32.c new file mode 100644 index 0000000..d8b5094 --- /dev/null +++ b/src/win32.c @@ -0,0 +1,242 @@ +#include "win32.h" + +#include "args.h" +#include "core.h" +#include "main.h" +#include "window_manager.h" + +#include + +// 0x51E428 +DirectDrawCreateProc* gDirectDrawCreateProc = NULL; + +// 0x51E42C +DirectInputCreateAProc* gDirectInputCreateAProc = NULL; + +// 0x51E430 +DirectSoundCreateProc* gDirectSoundCreateProc = NULL; + +// 0x51E434 +HWND gProgramWindow = NULL; + +// 0x51E438 +HINSTANCE gInstance = NULL; + +// 0x51E43C +LPSTR gCmdLine = NULL; + +// 0x51E440 +int gCmdShow = 0; + +// 0x51E444 +bool gProgramIsActive = false; + +// GNW95MUTEX +HANDLE _GNW95_mutex = NULL; + +// 0x51E44C +HMODULE gDDrawDLL = NULL; + +// 0x51E450 +HMODULE gDInputDLL = NULL; + +// 0x51E454 +HMODULE gDSoundDLL = NULL; + +// 0x4DE700 +int WINAPI WinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInst, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) +{ + CommandLineArguments args; + + _GNW95_mutex = CreateMutexA(0, TRUE, "GNW95MUTEX"); + if (GetLastError() == ERROR_SUCCESS) { + ShowCursor(0); + if (_InitClass(hInst)) { + if (_InitInstance()) { + if (_LoadDirectX()) { + gInstance = hInst; + gCmdLine = lpCmdLine; + gCmdShow = nCmdShow; + argsInit(&args); + if (argsParse(&args, lpCmdLine)) { + signal(1, _SignalHandler); + signal(3, _SignalHandler); + signal(5, _SignalHandler); + gProgramIsActive = true; + falloutMain(args.argc, args.argv); + argsFree(&args); + return 1; + } + } + } + } + CloseHandle(_GNW95_mutex); + } + return 0; +} + +// 0x4DE7F4 +ATOM _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 +bool _LoadDirectX() +{ + gDDrawDLL = LoadLibraryA("DDRAW.DLL"); + if (gDDrawDLL == NULL) { + goto err; + } + + gDirectDrawCreateProc = (DirectDrawCreateProc*)GetProcAddress(gDDrawDLL, "DirectDrawCreate"); + if (gDirectDrawCreateProc == NULL) { + goto err; + } + + gDInputDLL = LoadLibraryA("DINPUT.DLL"); + if (gDInputDLL == NULL) { + goto err; + } + + gDirectInputCreateAProc = (DirectInputCreateAProc*)GetProcAddress(gDInputDLL, "DirectInputCreateA"); + if (gDirectInputCreateAProc == NULL) { + goto err; + } + + gDSoundDLL = LoadLibraryA("DSOUND.DLL"); + if (gDSoundDLL == NULL) { + goto err; + } + + gDirectSoundCreateProc = (DirectSoundCreateProc*)GetProcAddress(gDSoundDLL, "DirectSoundCreate"); + if (gDirectSoundCreateProc == 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 +void _UnloadDirectX(void) +{ + if (gDSoundDLL != NULL) { + FreeLibrary(gDSoundDLL); + gDSoundDLL = NULL; + gDirectDrawCreateProc = NULL; + } + + if (gDDrawDLL != NULL) { + FreeLibrary(gDDrawDLL); + gDDrawDLL = NULL; + gDirectSoundCreateProc = NULL; + } + + if (gDInputDLL != NULL) { + FreeLibrary(gDInputDLL); + gDInputDLL = NULL; + gDirectInputCreateAProc = NULL; + } +} + +// 0x4DE9F4 +void _SignalHandler(int sig) +{ + // TODO: Incomplete. +} + +// 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.left = updateRect.left; + rect.top = updateRect.top; + rect.right = updateRect.right - 1; + rect.bottom = updateRect.bottom - 1; + windowRefreshAll(&rect); + } + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SETCURSOR: + if ((HWND)wParam == gProgramWindow) { + SetCursor(NULL); + return 1; + } + break; + case WM_SYSCOMMAND: + switch (wParam & 0xFFF0) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + return 0; + } + break; + case WM_ACTIVATEAPP: + gProgramIsActive = wParam; + if (wParam) { + _GNW95_hook_input(1); + windowRefreshAll(&_scr_size); + } else { + _GNW95_hook_input(0); + } + + return 0; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} diff --git a/src/win32.h b/src/win32.h new file mode 100644 index 0000000..5da021e --- /dev/null +++ b/src/win32.h @@ -0,0 +1,43 @@ +#ifndef WIN32_H +#define WIN32_H + +#define WIN32_LEAN_AND_MEAN +#include + +#define DIRECTDRAW_VERSION 0x0300 +#include + +#define DIRECTINPUT_VERSION 0x0300 +#include +#include + +#define DIRECTSOUND_VERSION 0x0300 +#include + +#include + +typedef HRESULT(__stdcall DirectDrawCreateProc)(GUID*, LPDIRECTDRAW*, IUnknown*); +typedef HRESULT(__stdcall DirectInputCreateAProc)(HINSTANCE, DWORD, LPDIRECTINPUTA*, IUnknown*); +typedef HRESULT(__stdcall DirectSoundCreateProc)(GUID*, LPDIRECTSOUND*, IUnknown*); + +extern DirectDrawCreateProc* gDirectDrawCreateProc; +extern DirectInputCreateAProc* gDirectInputCreateAProc; +extern DirectSoundCreateProc* gDirectSoundCreateProc; +extern HWND gProgramWindow; +extern HINSTANCE gInstance; +extern LPSTR gCmdLine; +extern int gCmdShow; +extern bool gProgramIsActive; +extern HANDLE _GNW95_mutex; +extern HMODULE gDDrawDLL; +extern HMODULE gDInputDLL; +extern HMODULE gDSoundDLL; + +ATOM _InitClass(HINSTANCE hInstance); +bool _InitInstance(); +bool _LoadDirectX(); +void _UnloadDirectX(void); +void _SignalHandler(int sig); +LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +#endif /* WIN32_H */ diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..6d99a98 --- /dev/null +++ b/src/window.c @@ -0,0 +1,1086 @@ +#include "window.h" + +#include "core.h" +#include "draw.h" +#include "interpreter_lib.h" +#include "memory_manager.h" +#include "mouse_manager.h" +#include "movie.h" +#include "text_font.h" +#include "widget.h" +#include "window_manager.h" + +#include +#include +#include + +// 0x51DCAC +int _holdTime = 250; + +// 0x51DCB0 +int _checkRegionEnable = 1; + +// 0x51DCB4 +int _winTOS = -1; + +// 051DCB8 +int gCurrentManagedWindowIndex = -1; + +// 0x51DCBC +INITVIDEOFN _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 +Size _sizes_x[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 +int _numInputFunc = 0; + +// 0x51DD80 +int _lastWin = -1; + +// 0x51DD84 +int _said_quit = 1; + +// 0x66E770 +int _winStack[MANAGED_WINDOW_COUNT]; + +// 0x66E7B0 +char _alphaBlendTable[64 * 256]; + +// 0x6727B0 +ManagedWindow gManagedWindows[MANAGED_WINDOW_COUNT]; + +// NOTE: This value is never set. +// +// 0x672D78 +void (*_selectWindowFunc)(int, ManagedWindow*); + +// 0x672D7C +int _xres; + +// 0x672D88 +int _yres; + +// Highlight color (maybe r). +// +// 0x672D8C +int _currentHighlightColorR; + +// 0x672D90 +int gWidgetFont; + +// Text color (maybe g). +// +// 0x672DA0 +int _currentTextColorG; + +// text color (maybe b). +// +// 0x672DA4 +int _currentTextColorB; + +// 0x672DA8 +int gWidgetTextFlags; + +// Text color (maybe r) +// +// 0x672DAC +int _currentTextColorR; + +// highlight color (maybe g) +// +// 0x672DB0 +int _currentHighlightColorG; + +// Highlight color (maybe b). +// +// 0x672DB4 +int _currentHighlightColorB; + +// 0x4B7680 +bool _windowDraw() +{ + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (managedWindow->window == -1) { + return false; + } + + windowRefresh(managedWindow->window); + + return true; +} + +// 0x4B81C4 +bool _selectWindowID(int index) +{ + if (index < 0 || index >= MANAGED_WINDOW_COUNT) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[index]); + if (managedWindow->window == -1) { + return false; + } + + gCurrentManagedWindowIndex = index; + + if (_selectWindowFunc != NULL) { + _selectWindowFunc(index, managedWindow); + } + + return true; +} + +// 0x4B821C +int _selectWindow(const char* windowName) +{ + if (gCurrentManagedWindowIndex != -1) { + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (stricmp(managedWindow->name, windowName) == 0) { + return gCurrentManagedWindowIndex; + } + } + + int index; + for (index = 0; index < MANAGED_WINDOW_COUNT; index++) { + ManagedWindow* managedWindow = &(gManagedWindows[index]); + if (managedWindow->window != -1) { + if (stricmp(managedWindow->name, windowName) == 0) { + break; + } + } + } + + if (!_selectWindowID(index)) { + return index; + } + + return -1; +} + +// 0x4B82DC +unsigned char* _windowGetBuffer() +{ + if (gCurrentManagedWindowIndex != -1) { + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + return windowGetBuffer(managedWindow->window); + } + + return NULL; +} + +// 0x4B8330 +int _pushWindow(const char* windowName) +{ + if (_winTOS >= MANAGED_WINDOW_COUNT) { + return -1; + } + + int oldCurrentWindowIndex = gCurrentManagedWindowIndex; + + 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 = &(gManagedWindows[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 + fontGetLineHeight() > maxY) { + return; + } + + if (stringLength > 255) { + stringLength = 255; + } + + char* stringCopy = internal_malloc_safe(stringLength + 1, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1078 + strncpy(stringCopy, string, stringLength); + stringCopy[stringLength] = '\0'; + + int stringWidth = fontGetStringWidth(stringCopy); + int stringHeight = fontGetLineHeight(); + if (stringWidth == 0 || stringHeight == 0) { + internal_free_safe(stringCopy, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1085 + return; + } + + if ((flags & FONT_SHADOW) != 0) { + stringWidth++; + stringHeight++; + } + + unsigned char* backgroundBuffer = internal_calloc_safe(stringWidth, stringHeight, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1093 + unsigned char* backgroundBufferPtr = backgroundBuffer; + fontDrawText(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 > windowGetHeight(win)) { + stringHeight = windowGetHeight(win) - y; + } + + if ((flags & 0x2000000) != 0) { + blitBufferToBufferTrans(backgroundBufferPtr, width, stringHeight, stringWidth, windowGetBuffer(win) + windowGetWidth(win) * y + x, windowGetWidth(win)); + } else { + blitBufferToBuffer(backgroundBufferPtr, width, stringHeight, stringWidth, windowGetBuffer(win) + windowGetWidth(win) * y + x, windowGetWidth(win)); + } + + internal_free_safe(backgroundBuffer, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1130 + internal_free_safe(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 += fontGetCharacterWidth(*pch & 0xFF); + if (*pch != '\n' && v1 <= maxLength) { + v1 += fontGetLetterSpacing(); + pch++; + } else { + while (v1 > maxLength) { + v1 -= fontGetCharacterWidth(*pch); + pch--; + } + + if (*pch != '\n') { + while (pch != start && *pch != ' ') { + pch--; + } + } + + if (substringList != NULL) { + substringList = internal_realloc_safe(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 1166 + } else { + substringList = internal_malloc_safe(sizeof(*substringList), __FILE__, __LINE__); // "..\int\WINDOW.C", 1167 + } + + char* substring = internal_malloc_safe(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 = internal_realloc_safe(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 1184 + } else { + substringList = internal_malloc_safe(sizeof(*substringList), __FILE__, __LINE__); // "..\int\WINDOW.C", 1185 + } + + char* substring = internal_malloc_safe(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++) { + internal_free_safe(substringList[index], __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1200 + } + + internal_free_safe(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 + fontGetLineHeight()); + _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 (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + int width = (int)(a2 * managedWindow->field_54); + int height = windowGetHeight(managedWindow->window); + int x = managedWindow->field_44; + int y = managedWindow->field_48; + int flags = widgetGetTextColor() | 0x2000000; + _windowWrapLineWithSpacing(managedWindow->window, string, width, height, x, y, flags, textAlignment, 0); + + return true; +} + +// 0x4B89B0 +bool _windowFormatMessage(char* string, int x, int y, int width, int height, int textAlignment) +{ + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + int flags = widgetGetTextColor() | 0x2000000; + _windowWrapLineWithSpacing(managedWindow->window, string, width, height, x, y, flags, textAlignment, 0); + + return true; +} + +// 0x4B9048 +int _windowGetXres() +{ + return _xres; +} + +// 0x4B9050 +int _windowGetYres() +{ + return _yres; +} + +// 0x4B9058 +void _removeProgramReferences_3(Program* program) +{ + for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { + ManagedWindow* managedWindow = &(gManagedWindows[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->field_5C = 0; + managedButton->field_60 = 0; + managedButton->field_54 = 0; + managedButton->field_58 = 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->field_4C = 0; + region->field_48 = 0; + region->field_54 = 0; + region->field_50 = 0; + } + } + } + } + } +} + +// 0x4B9190 +void _initWindow(int resolution, int a2) +{ + char err[MAX_PATH]; + int rc; + int i, j; + + _interpretRegisterProgramDeleteCallback(_removeProgramReferences_3); + + _currentTextColorR = 0; + _currentTextColorG = 0; + _currentTextColorB = 0; + _currentHighlightColorR = 0; + _currentHighlightColorG = 0; + gWidgetTextFlags = 0x2010000; + + _yres = _sizes_x[resolution].height; // screen height + _currentHighlightColorB = 0; + _xres = _sizes_x[resolution].width; // screen width + + for (int i = 0; i < MANAGED_WINDOW_COUNT; i++) { + gManagedWindows[i].window = -1; + } + + rc = windowManagerInit(_gfx_init[resolution], directDrawFree, 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); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_NO_MEMORY: + sprintf(err, "Not enough memory to initialize video mode\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS: + sprintf(err, "Couldn't find/load text fonts\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED: + sprintf(err, "Attempt to initialize window system twice\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_NOT_INITIALIZED: + sprintf(err, "Window system not initialized\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_CURRENT_WINDOWS_TOO_BIG: + sprintf(err, "Current windows are too big for new resolution\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE: + sprintf(err, "Error initializing default database.\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_8: + exit(1); + break; + case WINDOW_MANAGER_ERR_ALREADY_RUNNING: + sprintf(err, "Program already running.\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_TITLE_NOT_SET: + sprintf(err, "Program title not set.\n"); + showMesageBox(err); + exit(1); + break; + case WINDOW_MANAGER_ERR_INITIALIZING_INPUT: + sprintf(err, "Failure initializing input devices.\n"); + showMesageBox(err); + exit(1); + break; + default: + sprintf(err, "Unknown error code %d\n", rc); + showMesageBox(err); + exit(1); + break; + } + } + + gWidgetFont = 100; + fontSetCurrent(100); + + _initMousemgr(); + + _mousemgrSetNameMangler(_interpretMangleName); + + for (i = 0; i < 64; i++) { + for (j = 0; j < 256; j++) { + _alphaBlendTable[(i << 8) + j] = ((i * j) >> 9); + } + } +} + +// 0x4B947C +void _windowClose() +{ + // TODO: Incomplete, but required for graceful exit. + + for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { + ManagedWindow* managedWindow = &(gManagedWindows[index]); + if (managedWindow->window != -1) { + // _deleteWindow(managedWindow); + } + } + + dbExit(); + windowManagerExit(); +} + +// Deletes button with the specified name or all buttons if it's NULL. +// +// 0x4B9548 +bool _windowDeleteButton(const char* buttonName) +{ + if (gCurrentManagedWindowIndex != -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (managedWindow->buttonsLength == 0) { + return false; + } + + if (buttonName == NULL) { + for (int index = 0; index < managedWindow->buttonsLength; index++) { + ManagedButton* managedButton = &(managedWindow->buttons[index]); + buttonDestroy(managedButton->btn); + + if (managedButton->field_48 != NULL) { + internal_free_safe(managedButton->field_48, __FILE__, __LINE__); // "..\int\WINDOW.C", 1648 + managedButton->field_48 = NULL; + } + + if (managedButton->field_4C != NULL) { + internal_free_safe(managedButton->field_4C, __FILE__, __LINE__); // "..\int\WINDOW.C", 1649 + managedButton->field_4C = NULL; + } + + if (managedButton->field_40 != NULL) { + internal_free_safe(managedButton->field_40, __FILE__, __LINE__); // "..\int\WINDOW.C", 1650 + managedButton->field_40 = NULL; + } + + if (managedButton->field_44 != NULL) { + internal_free_safe(managedButton->field_44, __FILE__, __LINE__); // "..\int\WINDOW.C", 1651 + managedButton->field_44 = NULL; + } + + if (managedButton->field_50 != NULL) { + internal_free_safe(managedButton->field_44, __FILE__, __LINE__); // "..\int\WINDOW.C", 1652 + managedButton->field_50 = NULL; + } + } + + internal_free_safe(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) { + buttonDestroy(managedButton->btn); + + if (managedButton->field_48 != NULL) { + internal_free_safe(managedButton->field_48, __FILE__, __LINE__); // "..\int\WINDOW.C", 1665 + managedButton->field_48 = NULL; + } + + if (managedButton->field_4C != NULL) { + internal_free_safe(managedButton->field_4C, __FILE__, __LINE__); // "..\int\WINDOW.C", 1666 + managedButton->field_4C = NULL; + } + + if (managedButton->field_40 != NULL) { + internal_free_safe(managedButton->field_40, __FILE__, __LINE__); // "..\int\WINDOW.C", 1667 + managedButton->field_40 = NULL; + } + + if (managedButton->field_44 != NULL) { + internal_free_safe(managedButton->field_44, __FILE__, __LINE__); // "..\int\WINDOW.C", 1668 + managedButton->field_44 = 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) { + internal_free_safe(managedWindow->buttons, __FILE__, __LINE__); // "..\int\WINDOW.C", 1672 + managedWindow->buttons = NULL; + } + + return true; + } + } + + return false; +} + +// 0x4B9928 +bool _windowSetButtonFlag(const char* buttonName, int value) +{ + if (gCurrentManagedWindowIndex != -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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; +} + +// 0x4BA11C +bool _windowAddButtonProc(const char* buttonName, Program* program, int a3, int a4, int a5, int a6) +{ + if (gCurrentManagedWindowIndex != -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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->field_5C = a3; + managedButton->field_60 = a4; + managedButton->field_54 = a5; + managedButton->field_58 = a6; + managedButton->program = program; + return true; + } + } + + return false; +} + +// 0x4BA1B4 +bool _windowAddButtonRightProc(const char* buttonName, Program* program, int a3, int a4) +{ + if (gCurrentManagedWindowIndex != -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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->field_68 = a4; + managedButton->field_64 = a3; + managedButton->program = program; + return true; + } + } + + return false; +} + +// 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 opAddRegion, which ignores the returned +// value. +// +// 0x4BA844 +void _windowEndRegion() +{ + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + Region* region = managedWindow->regions[managedWindow->currentRegionIndex]; + _windowAddRegionPoint(region->points->x, region->points->y, false); + _regionSetBound(region); +} + +// 0x4BA988 +bool _windowCheckRegionExists(const char* regionName) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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 (gCurrentManagedWindowIndex == -1) { + return false; + } + + int newRegionIndex; + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + if (managedWindow->regions == NULL) { + managedWindow->regions = internal_malloc_safe(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 = internal_realloc_safe(managedWindow->regions, sizeof(&(managedWindow->regions)) * (managedWindow->regionsLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 2178 + managedWindow->regionsLength++; + } + } + + Region* newRegion; + if (initialCapacity != 0) { + newRegion = regionCreate(initialCapacity + 1); + } else { + newRegion = NULL; + } + + managedWindow->regions[newRegionIndex] = newRegion; + managedWindow->currentRegionIndex = newRegionIndex; + + return true; +} + +// 0x4BAB68 +bool _windowAddRegionPoint(int x, int y, bool a3) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + Region* region = managedWindow->regions[managedWindow->currentRegionIndex]; + if (region == NULL) { + region = managedWindow->regions[managedWindow->currentRegionIndex] = regionCreate(1); + } + + if (a3) { + x = (int)(x * managedWindow->field_54); + y = (int)(y * managedWindow->field_58); + } + + regionAddPoint(region, x, y); + + return true; +} + +// 0x4BADC0 +bool _windowAddRegionProc(const char* regionName, Program* program, int a3, int a4, int a5, int a6) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + for (int index = 0; index < managedWindow->regionsLength; index++) { + Region* region = managedWindow->regions[index]; + if (region != NULL) { + if (stricmp(region->name, regionName) == 0) { + region->field_50 = a3; + region->field_54 = a4; + region->field_48 = a5; + region->field_4C = a6; + region->program = program; + return true; + } + } + } + + return false; +} + +// 0x4BAE8C +bool _windowAddRegionRightProc(const char* regionName, Program* program, int a3, int a4) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + for (int index = 0; index < managedWindow->regionsLength; index++) { + Region* region = managedWindow->regions[index]; + if (region != NULL) { + if (stricmp(region->name, regionName) == 0) { + region->field_58 = a3; + region->field_5C = a4; + region->program = program; + return true; + } + } + } + + return false; +} + +// 0x4BAF2C +bool _windowSetRegionFlag(const char* regionName, int value) +{ + if (gCurrentManagedWindowIndex != -1) { + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + for (int index = 0; index < managedWindow->regionsLength; index++) { + Region* region = managedWindow->regions[index]; + if (region != NULL) { + if (stricmp(region->name, regionName) == 0) { + regionAddFlag(region, value); + return true; + } + } + } + } + + return false; +} + +// 0x4BAFA8 +bool _windowAddRegionName(const char* regionName) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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; + } + } + } + } + + regionSetName(region, regionName); + + return true; +} + +// Delete region with the specified name or all regions if it's NULL. +// +// 0x4BB0A8 +bool _windowDeleteRegion(const char* regionName) +{ + if (gCurrentManagedWindowIndex == -1) { + return false; + } + + ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]); + 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); + } + } + + internal_free_safe(managedWindow->regions, __FILE__, __LINE__); // "..\int\WINDOW.C", 2353 + + managedWindow->regions = NULL; + managedWindow->regionsLength = 0; + } + + return true; +} + +// 0x4BB220 +void _updateWindows() +{ + _movieUpdate(); + // TODO: Incomplete. + // _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) +{ + if (_movieRun(gManagedWindows[gCurrentManagedWindowIndex].window, filePath) != 0) { + return false; + } + + return true; +} + +// 0x4BB280 +bool _windowPlayMovieRect(char* filePath, int a2, int a3, int a4, int a5) +{ + if (_movieRunRect(gManagedWindows[gCurrentManagedWindowIndex].window, filePath, a2, a3, a4, a5) != 0) { + return false; + } + + return true; +} + +// 0x4BB2C4 +void _windowStopMovie() +{ + _movieStop(); +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..6d79d15 --- /dev/null +++ b/src/window.h @@ -0,0 +1,131 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#include "geometry.h" +#include "interpreter.h" +#include "region.h" + +#include + +#define MANAGED_WINDOW_COUNT (16) + +typedef void (*WINDOWDRAWINGPROC)(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); +typedef void WindowDrawingProc2(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, unsigned char a10); + +typedef enum TextAlignment { + TEXT_ALIGNMENT_LEFT, + TEXT_ALIGNMENT_RIGHT, + TEXT_ALIGNMENT_CENTER, +} TextAlignment; + +typedef struct ManagedButton { + int btn; + int field_4; + int field_8; + int field_C; + int field_10; + int flags; + int field_18; + char name[32]; + Program* program; + void* field_40; + void* field_44; + void* field_48; + void* field_4C; + void* field_50; + int field_54; + int field_58; + int field_5C; + int field_60; + int field_64; + int field_68; + int field_6C; + int field_70; + int field_74; + int field_78; +} ManagedButton; +static_assert(sizeof(ManagedButton) == 0x7C, "wrong size"); + +typedef struct ManagedWindow { + char name[32]; + int window; + int field_24; + int field_28; + 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; + +typedef int (*INITVIDEOFN)(); + +extern int _holdTime; +extern int _checkRegionEnable; +extern int _winTOS; +extern int gCurrentManagedWindowIndex; +extern INITVIDEOFN _gfx_init[12]; +extern Size _sizes_x[12]; + +extern int _winStack[MANAGED_WINDOW_COUNT]; +extern char _alphaBlendTable[64 * 256]; +extern ManagedWindow gManagedWindows[MANAGED_WINDOW_COUNT]; + +extern void(*_selectWindowFunc)(int, ManagedWindow*); +extern int _xres; +extern int _yres; +extern int _currentHighlightColorR; +extern int gWidgetFont; +extern int _currentTextColorG; +extern int _currentTextColorB; +extern int gWidgetTextFlags; +extern int _currentTextColorR; +extern int _currentHighlightColorG; +extern int _currentHighlightColorB; + +bool _windowDraw(); +bool _selectWindowID(int index); +int _selectWindow(const char* windowName); +unsigned char* _windowGetBuffer(); +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 _windowGetXres(); +int _windowGetYres(); +void _removeProgramReferences_3(Program* program); +void _initWindow(int resolution, int a2); +void _windowClose(); +bool _windowDeleteButton(const char* buttonName); +bool _windowSetButtonFlag(const char* buttonName, int value); +bool _windowAddButtonProc(const char* buttonName, Program* program, int a3, int a4, int a5, int a6); +bool _windowAddButtonRightProc(const char* buttonName, Program* program, int a3, int a4); +void _windowEndRegion(); +bool _windowCheckRegionExists(const char* regionName); +bool _windowStartRegion(int initialCapacity); +bool _windowAddRegionPoint(int x, int y, bool a3); +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(); + +#endif /* WINDOW_H */ diff --git a/src/window_manager.c b/src/window_manager.c new file mode 100644 index 0000000..a0205c8 --- /dev/null +++ b/src/window_manager.c @@ -0,0 +1,2408 @@ +#include "window_manager.h" + +#include "color.h" +#include "core.h" +#include "db.h" +#include "debug.h" +#include "draw.h" +#include "memory.h" +#include "palette.h" +#include "text_font.h" +#include "window_manager_private.h" + +static_assert(sizeof(struc_177) == 572, "wrong size"); +static_assert(sizeof(Window) == 68, "wrong size"); +static_assert(sizeof(Button) == 124, "wrong size"); + +// 0x50FA30 +char _path_patches[] = ""; + +// 0x51E3D8 +bool _GNW95_already_running = false; + +// 0x51E3DC +HANDLE _GNW95_title_mutex = INVALID_HANDLE_VALUE; + +// 0x51E3E0 +bool gWindowSystemInitialized = false; + +// 0x51E3E4 +int _GNW_wcolor[6] = { + 0, + 0, + 0, + 0, + 0, + 0, +}; + +// 0x51E3FC +unsigned char* _screen_buffer = NULL; + +// 0x51E400 +bool _insideWinExit = false; + +// 0x51E404 +int _last_button_winID = -1; + +// 0x6ADD90 +int gOrderedWindowIds[MAX_WINDOW_COUNT]; + +// 0x6ADE58 +Window* gWindows[MAX_WINDOW_COUNT]; + +// 0x6ADF20 +VideoSystemExitProc* gVideoSystemExitProc; + +// 0x6ADF24 +int gWindowsLength; + +// 0x6ADF28 +int _window_flags; + +// 0x6ADF2C +bool _buffering; + +// 0x6ADF30 +int _bk_color; + +// 0x6ADF34 +VideoSystemInitProc* gVideoSystemInitProc; + +// 0x6ADF38 +int _doing_refresh_all; + +// 0x6ADF3C +void* _GNW_texture; + +// 0x6ADF40 +RadioGroup gRadioGroups[RADIO_GROUP_LIST_CAPACITY]; + +// 0x4D5C30 +int windowManagerInit(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 (gWindowSystemInitialized) { + return WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED; + } + + for (int index = 0; index < MAX_WINDOW_COUNT; index++) { + gOrderedWindowIds[index] = -1; + } + + if (!_db_total()) { + if (dbOpen(NULL, 0, _path_patches, 1) == -1) { + return WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE; + } + } + + if (textFontsInit() == -1) { + return WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS; + } + + _get_start_mode_(); + + gVideoSystemInitProc = videoSystemInitProc; + gVideoSystemExitProc = directInputFree; + + int rc = videoSystemInitProc(); + if (rc == -1) { + if (gVideoSystemExitProc != NULL) { + gVideoSystemExitProc(); + } + + return WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE; + } + + if (rc == 8) { + return WINDOW_MANAGER_ERR_8; + } + + if (a3 & 1) { + _screen_buffer = internal_malloc((_scr_size.bottom - _scr_size.top + 1) * (_scr_size.right - _scr_size.left + 1)); + if (_screen_buffer == NULL) { + if (gVideoSystemExitProc != NULL) { + gVideoSystemExitProc(); + } else { + directDrawFree(); + } + + return WINDOW_MANAGER_ERR_NO_MEMORY; + } + } + + _buffering = false; + _doing_refresh_all = 0; + + colorPaletteSetFileIO(paletteOpenFileImpl, paletteReadFileImpl, paletteCloseFileImpl); + colorPaletteSetMemoryProcs(internal_malloc, internal_realloc, internal_free); + + if (!_initColors()) { + unsigned char* palette = internal_malloc(768); + if (palette == NULL) { + if (gVideoSystemExitProc != NULL) { + gVideoSystemExitProc(); + } else { + directDrawFree(); + } + + if (_screen_buffer != NULL) { + internal_free(_screen_buffer); + } + + return WINDOW_MANAGER_ERR_NO_MEMORY; + } + + bufferFill(palette, 768, 1, 768, 0); + + // TODO: Incomplete. + // _colorBuildColorTable(_getSystemPalette(), palette); + + internal_free(palette); + } + + _GNW_debug_init(); + + if (coreInit(a3) == -1) { + return WINDOW_MANAGER_ERR_INITIALIZING_INPUT; + } + + _GNW_intr_init(); + + Window* window = gWindows[0] = internal_malloc(sizeof(*window)); + if (window == NULL) { + if (gVideoSystemExitProc != NULL) { + gVideoSystemExitProc(); + } else { + directDrawFree(); + } + + if (_screen_buffer != NULL) { + internal_free(_screen_buffer); + } + + return WINDOW_MANAGER_ERR_NO_MEMORY; + } + + window->id = 0; + window->flags = 0; + window->rect.left = _scr_size.left; + window->rect.top = _scr_size.top; + window->rect.right = _scr_size.right; + window->rect.bottom = _scr_size.bottom; + window->width = _scr_size.right - _scr_size.left + 1; + window->height = _scr_size.bottom - _scr_size.top + 1; + window->field_24 = 0; + window->field_28 = 0; + window->buffer = NULL; + window->buttonListHead = NULL; + window->field_34 = NULL; + window->field_38 = 0; + window->field_3C = 0; + + gWindowsLength = 1; + gWindowSystemInitialized = 1; + _GNW_wcolor[3] = 21140; + _GNW_wcolor[4] = 32747; + _GNW_wcolor[5] = 31744; + gOrderedWindowIds[0] = 0; + _GNW_texture = NULL; + _bk_color = 0; + _GNW_wcolor[0] = 10570; + _window_flags = a3; + _GNW_wcolor[2] = 8456; + _GNW_wcolor[1] = 15855; + + atexit(windowManagerExit); + + return WINDOW_MANAGER_OK; +} + +// 0x4D616C +void windowManagerExit(void) +{ + if (!_insideWinExit) { + _insideWinExit = true; + if (gWindowSystemInitialized) { + _GNW_intr_exit(); + + for (int index = gWindowsLength - 1; index >= 0; index--) { + windowFree(gWindows[index]->id); + } + + if (_GNW_texture != NULL) { + internal_free(_GNW_texture); + } + + if (_screen_buffer != NULL) { + internal_free(_screen_buffer); + } + + if (gVideoSystemExitProc != NULL) { + gVideoSystemExitProc(); + } + + coreExit(); + _GNW_rect_exit(); + textFontsExit(); + _colorsClose(); + + gWindowSystemInitialized = false; + + CloseHandle(_GNW95_title_mutex); + _GNW95_title_mutex = INVALID_HANDLE_VALUE; + } + _insideWinExit = false; + } +} + +// win_add +// 0x4D6238 +int windowCreate(int x, int y, int width, int height, int a4, int flags) +{ + int v23; + int v25, v26; + Window* tmp; + + if (!gWindowSystemInitialized) { + return -1; + } + + if (gWindowsLength == MAX_WINDOW_COUNT) { + return -1; + } + + if (width > rectGetWidth(&_scr_size)) { + return -1; + } + + if (height > rectGetHeight(&_scr_size)) { + return -1; + } + + Window* window = gWindows[gWindowsLength] = internal_malloc(sizeof(*window)); + if (window == NULL) { + return -1; + } + + window->buffer = internal_malloc(width * height); + if (window->buffer == NULL) { + internal_free(window); + return -1; + } + + int index = 1; + while (windowGetWindow(index) != NULL) { + index++; + } + + window->id = index; + + if ((flags & WINDOW_FLAG_0x01) != 0) { + flags |= _window_flags; + } + + window->width = width; + window->height = height; + window->flags = flags; + window->field_24 = rand() & 0xFFFE; + window->field_28 = rand() & 0xFFFE; + + if (a4 == 256) { + if (_GNW_texture == NULL) { + a4 = _colorTable[_GNW_wcolor[0]]; + } + } else if ((a4 & 0xFF00) != 0) { + int v1 = (a4 & 0xFF00) >> 8; + a4 = (a4 & ~0xFFFF) | _colorTable[_GNW_wcolor[v1]]; + } + + window->buttonListHead = 0; + window->field_34 = 0; + window->field_38 = 0; + window->field_3C = 0; + window->blitProc = blitBufferToBufferTrans; + window->field_20 = a4; + gOrderedWindowIds[index] = gWindowsLength; + gWindowsLength++; + + windowFill(index, 0, 0, width, height, a4); + + window->flags |= WINDOW_HIDDEN; + _win_move(index, x, y); + window->flags = flags; + + if ((flags & WINDOW_FLAG_0x04) == 0) { + v23 = gWindowsLength - 2; + while (v23 > 0) { + if (!(gWindows[v23]->flags & WINDOW_FLAG_0x04)) { + break; + } + v23--; + } + + if (v23 != gWindowsLength - 2) { + v25 = v23 + 1; + v26 = gWindowsLength - 1; + while (v26 > v25) { + tmp = gWindows[v26 - 1]; + gWindows[v26] = tmp; + gOrderedWindowIds[tmp->id] = v26; + v26--; + } + + gWindows[v25] = window; + gOrderedWindowIds[index] = v25; + } + } + + return index; +} + +// win_remove +// 0x4D6468 +void windowDestroy(int win) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + Rect rect; + rectCopy(&rect, &(window->rect)); + + int v1 = gOrderedWindowIds[window->id]; + windowFree(win); + + gOrderedWindowIds[win] = -1; + + for (int index = v1; index < gWindowsLength - 1; index++) { + gWindows[index] = gWindows[index + 1]; + gOrderedWindowIds[gWindows[index]->id] = index; + } + + gWindowsLength--; + + // NOTE: Uninline. + windowRefreshAll(&rect); +} + +// 0x4D650C +void windowFree(int win) +{ + Window* window = windowGetWindow(win); + if (window == NULL) { + return; + } + + if (window->buffer != NULL) { + internal_free(window->buffer); + } + + if (window->field_3C != NULL) { + internal_free(window->field_3C); + } + + Button* curr = window->buttonListHead; + while (curr != NULL) { + Button* next = curr->next; + buttonFree(curr); + curr = next; + } + + internal_free(window); +} + +// 0x4D6558 +void _win_buffering(bool a1) +{ + if (_screen_buffer != NULL) { + _buffering = a1; + } +} + +// 0x4D6568 +void windowDrawBorder(int win) +{ + if (!gWindowSystemInitialized) { + return; + } + + Window* window = windowGetWindow(win); + if (window == NULL) { + return; + } + + _lighten_buf(window->buffer + 5, window->width - 10, 5, window->width); + _lighten_buf(window->buffer, 5, window->height, window->width); + _lighten_buf(window->buffer + window->width - 5, 5, window->height, window->width); + _lighten_buf(window->buffer + window->width * (window->height - 5) + 5, window->width - 10, 5, window->width); + + bufferDrawRect(window->buffer, window->width, 0, 0, window->width - 1, window->height - 1, _colorTable[0]); + + bufferDrawRectShadowed(window->buffer, window->width, 1, 1, window->width - 2, window->height - 2, _colorTable[_GNW_wcolor[1]], _colorTable[_GNW_wcolor[2]]); + bufferDrawRectShadowed(window->buffer, window->width, 5, 5, window->width - 6, window->height - 6, _colorTable[_GNW_wcolor[2]], _colorTable[_GNW_wcolor[1]]); +} + +// 0x4D684C +void windowDrawText(int win, char* str, int a3, int x, int y, int a6) +{ + int v7; + int v14; + unsigned char* buf; + int v27; + + Window* window = windowGetWindow(win); + v7 = a3; + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + if (a3 == 0) { + if (a6 & 0x040000) { + v7 = fontGetMonospacedStringWidth(str); + } else { + v7 = fontGetStringWidth(str); + } + } + + if (v7 + x > window->width) { + if (!(a6 & 0x04000000)) { + return; + } + + v7 = window->width - x; + } + + buf = window->buffer + x + y * window->width; + + v14 = fontGetLineHeight(); + if (v14 + y > window->height) { + return; + } + + if (!(a6 & 0x02000000)) { + if (window->field_20 == 256 && _GNW_texture != NULL) { + _buf_texture(buf, v7, fontGetLineHeight(), window->width, _GNW_texture, window->field_24 + x, window->field_28 + y); + } else { + bufferFill(buf, v7, fontGetLineHeight(), window->width, window->field_20); + } + } + + if (a6 & 0xFF00) { + int t = (a6 & 0xFF00) >> 8; + v27 = (a6 & ~0xFFFF) | _colorTable[_GNW_wcolor[t]]; + } else { + v27 = a6; + } + + fontDrawText(buf, str, v7, window->width, v27); + + if (a6 & 0x01000000) { + // TODO: Check. + Rect rect; + rect.left = window->rect.left + x; + rect.top = window->rect.top + y; + rect.right = rect.left + v7; + rect.bottom = rect.top + fontGetLineHeight(); + _GNW_win_refresh(window, &rect, NULL); + } +} + +// 0x4D6B24 +void windowDrawLine(int win, int left, int top, int right, int bottom, int color) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + if (color & 0xFF00) { + int t = (color & 0xFF00) >> 8; + color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[t]]; + } + + bufferDrawLine(window->buffer, window->width, left, top, right, bottom, color); +} + +// 0x4D6B88 +void windowDrawRect(int win, int left, int top, int right, int bottom, int color) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + if ((color & 0xFF00) != 0) { + int v1 = (color & 0xFF00) >> 8; + color = (color & ~0xFFFF) | _colorTable[_GNW_wcolor[v1]]; + } + + if (right < left) { + int tmp = left; + left = right; + right = tmp; + } + + if (bottom < top) { + int tmp = top; + top = bottom; + bottom = tmp; + } + + bufferDrawRect(window->buffer, window->width, left, top, right, bottom, color); +} + +// 0x4D6CC8 +void windowFill(int win, int x, int y, int width, int height, int a6) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + if (a6 == 256) { + if (_GNW_texture != NULL) { + _buf_texture(window->buffer + window->width * y + x, width, height, window->width, _GNW_texture, x + window->field_24, y + window->field_28); + } else { + a6 = _colorTable[_GNW_wcolor[0]] & 0xFF; + } + } else if ((a6 & 0xFF00) != 0) { + int v1 = (a6 & 0xFF00) >> 8; + a6 = (a6 & ~0xFFFF) | _colorTable[_GNW_wcolor[v1]]; + } + + if (a6 < 256) { + bufferFill(window->buffer + window->width * y + x, width, height, window->width, a6); + } +} + +// 0x4D6DAC +void windowUnhide(int win) +{ + Window* window; + int v3; + int v5; + int v7; + Window* v6; + + window = windowGetWindow(win); + v3 = gOrderedWindowIds[window->id]; + + if (!gWindowSystemInitialized) { + return; + } + + if (window->flags & WINDOW_HIDDEN) { + window->flags &= ~WINDOW_HIDDEN; + if (v3 == gWindowsLength - 1) { + _GNW_win_refresh(window, &(window->rect), NULL); + } + } + + v5 = gWindowsLength - 1; + if (v3 < v5 && !(window->flags & WINDOW_FLAG_0x02)) { + v7 = v3; + while (v3 < v5 && ((window->flags & WINDOW_FLAG_0x04) || !(gWindows[v7 + 1]->flags & WINDOW_FLAG_0x04))) { + v6 = gWindows[v7 + 1]; + gWindows[v7] = v6; + v7++; + gOrderedWindowIds[v6->id] = v3++; + } + + gWindows[v3] = window; + gOrderedWindowIds[window->id] = v3; + _GNW_win_refresh(window, &(window->rect), NULL); + } +} + +// 0x4D6E64 +void windowHide(int win) +{ + if (!gWindowSystemInitialized) { + return; + } + + Window* window = windowGetWindow(win); + if (window == NULL) { + return; + } + + if ((window->flags & WINDOW_HIDDEN) == 0) { + window->flags |= WINDOW_HIDDEN; + _refresh_all(&(window->rect), NULL); + } +} + +// 0x4D6EA0 +void _win_move(int win, int x, int y) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + Rect rect; + rectCopy(&rect, &(window->rect)); + + if (x < 0) { + x = 0; + } + + if (y < 0) { + y = 0; + } + + if ((window->flags & WINDOW_FLAG_0x0100) != 0) { + x += 2; + } + + if (x + window->width - 1 > _scr_size.right) { + x = _scr_size.right - window->width + 1; + } + + if (y + window->height - 1 > _scr_size.bottom) { + y = _scr_size.bottom - window->height + 1; + } + + if ((window->flags & WINDOW_FLAG_0x0100) != 0) { + // TODO: Not sure what this means. + x &= ~0x03; + } + + window->rect.left = x; + window->rect.top = y; + window->rect.right = window->width + x - 1; + window->rect.bottom = window->height + y - 1; + + if ((window->flags & WINDOW_HIDDEN) == 0) { + _GNW_win_refresh(window, &(window->rect), NULL); + + if (gWindowSystemInitialized) { + _refresh_all(&rect, NULL); + } + } +} + +// 0x4D6F5C +void windowRefresh(int win) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + _GNW_win_refresh(window, &(window->rect), NULL); +} + +// 0x4D6F80 +void windowRefreshRect(int win, const Rect* rect) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + Rect newRect; + rectCopy(&newRect, rect); + rectOffset(&newRect, window->rect.left, window->rect.top); + + _GNW_win_refresh(window, &newRect, NULL); +} + +// 0x4D6FD8 +void _GNW_win_refresh(Window* window, Rect* rect, unsigned char* a3) +{ + RectListNode *v26, *v20, *v23, *v24; + int dest_pitch; + + // TODO: Get rid of this. + dest_pitch = 0; + + if ((window->flags & WINDOW_HIDDEN) != 0) { + return; + } + + if ((window->flags & WINDOW_FLAG_0x20) && _buffering && !_doing_refresh_all) { + // TODO: Incomplete. + } else { + v26 = _rect_malloc(); + if (v26 == NULL) { + return; + } + + v26->next = NULL; + + v26->rect.left = max(window->rect.left, rect->left); + v26->rect.top = max(window->rect.top, rect->top); + v26->rect.right = min(window->rect.right, rect->right); + v26->rect.bottom = min(window->rect.bottom, rect->bottom); + + if (v26->rect.right >= v26->rect.left && v26->rect.bottom >= v26->rect.top) { + if (a3) { + dest_pitch = rect->right - rect->left + 1; + } + + _win_clip(window, &v26, a3); + + if (window->id) { + v20 = v26; + while (v20) { + _GNW_button_refresh(window, &(v20->rect)); + + if (a3) { + if (_buffering && (window->flags & WINDOW_FLAG_0x20)) { + window->blitProc(window->buffer + v20->rect.left - window->rect.left + (v20->rect.top - window->rect.top) * window->width, + v20->rect.right - v20->rect.left + 1, + v20->rect.bottom - v20->rect.top + 1, + window->width, + a3 + dest_pitch * (v20->rect.top - rect->top) + v20->rect.left - rect->left, + dest_pitch); + } else { + blitBufferToBuffer( + window->buffer + v20->rect.left - window->rect.left + (v20->rect.top - window->rect.top) * window->width, + v20->rect.right - v20->rect.left + 1, + v20->rect.bottom - v20->rect.top + 1, + window->width, + a3 + dest_pitch * (v20->rect.top - rect->top) + v20->rect.left - rect->left, + dest_pitch); + } + } else { + if (_buffering) { + if (window->flags & WINDOW_FLAG_0x20) { + window->blitProc( + window->buffer + v20->rect.left - window->rect.left + (v20->rect.top - window->rect.top) * window->width, + v20->rect.right - v20->rect.left + 1, + v20->rect.bottom - v20->rect.top + 1, + window->width, + _screen_buffer + v20->rect.top * (_scr_size.right - _scr_size.left + 1) + v20->rect.left, + _scr_size.right - _scr_size.left + 1); + } else { + blitBufferToBuffer( + window->buffer + v20->rect.left - window->rect.left + (v20->rect.top - window->rect.top) * window->width, + v20->rect.right - v20->rect.left + 1, + v20->rect.bottom - v20->rect.top + 1, + window->width, + _screen_buffer + v20->rect.top * (_scr_size.right - _scr_size.left + 1) + v20->rect.left, + _scr_size.right - _scr_size.left + 1); + } + } else { + _scr_blit( + window->buffer + v20->rect.left - window->rect.left + (v20->rect.top - window->rect.top) * window->width, + window->width, + v20->rect.bottom - v20->rect.bottom + 1, + 0, + 0, + v20->rect.right - v20->rect.left + 1, + v20->rect.bottom - v20->rect.top + 1, + v20->rect.left, + v20->rect.top); + } + } + + v20 = v20->next; + } + } else { + RectListNode* v16 = v26; + while (v16 != NULL) { + int width = v16->rect.right - v16->rect.left + 1; + int height = v16->rect.bottom - v16->rect.top + 1; + unsigned char* buf = internal_malloc(width * height); + if (buf != NULL) { + bufferFill(buf, width, height, width, _bk_color); + if (dest_pitch != 0) { + blitBufferToBuffer( + buf, + width, + height, + width, + a3 + dest_pitch * (v16->rect.top - rect->top) + v16->rect.left - rect->left, + dest_pitch); + } else { + if (_buffering) { + blitBufferToBuffer(buf, + width, + height, + width, + _screen_buffer + v16->rect.top * (_scr_size.right - _scr_size.left + 1) + v16->rect.left, + _scr_size.right - _scr_size.left + 1); + } else { + _scr_blit(buf, width, height, 0, 0, width, height, v16->rect.left, v16->rect.top); + } + } + + internal_free(buf); + } + v16 = v16->next; + } + } + + v23 = v26; + while (v23) { + v24 = v23->next; + + if (_buffering && !a3) { + _scr_blit( + _screen_buffer + v23->rect.left + (_scr_size.right - _scr_size.left + 1) * v23->rect.top, + _scr_size.right - _scr_size.left + 1, + v23->rect.bottom - v23->rect.top + 1, + 0, + 0, + v23->rect.right - v23->rect.left + 1, + v23->rect.bottom - v23->rect.top + 1, + v23->rect.left, + v23->rect.top); + } + + _rect_free(v23); + + v23 = v24; + } + + if (!_doing_refresh_all && a3 == NULL && cursorIsHidden() == 0) { + if (_mouse_in(rect->left, rect->top, rect->right, rect->bottom)) { + mouseShowCursor(); + } + } + } else { + _rect_free(v26); + } + } +} + +// 0x4D759C +void windowRefreshAll(Rect* rect) +{ + if (gWindowSystemInitialized) { + _refresh_all(rect, NULL); + } +} + +// 0x4D75B0 +void _win_clip(Window* window, RectListNode** rectListNodePtr, unsigned char* a3) +{ + int win; + + for (win = gOrderedWindowIds[window->id] + 1; win < gWindowsLength; win++) { + if (*rectListNodePtr == NULL) { + break; + } + + Window* window = gWindows[win]; + if (!(window->flags & WINDOW_HIDDEN)) { + if (!_buffering || !(window->flags & WINDOW_FLAG_0x20)) { + _rect_clip_list(rectListNodePtr, &(window->rect)); + } else { + if (!_doing_refresh_all) { + _GNW_win_refresh(window, &(window->rect), NULL); + _rect_clip_list(rectListNodePtr, &(window->rect)); + } + } + } + } + + if (a3 == _screen_buffer || a3 == NULL) { + if (cursorIsHidden() == 0) { + Rect rect; + mouseGetRect(&rect); + _rect_clip_list(rectListNodePtr, &rect); + } + } +} + +// 0x4D765C +void _win_drag(int win) +{ + // TODO: Probably somehow related to self-run functionality, skip for now. + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + windowUnhide(win); + + Rect rect; + rectCopy(&rect, &(window->rect)); + + tickersExecute(); + + if (_vcr_update() != 3) { + _mouse_info(); + } + + if ((window->flags & WINDOW_FLAG_0x0100) && (window->rect.left & 3)) { + _win_move(window->id, window->rect.left, window->rect.top); + } +} + +// 0x4D77F8 +void _win_get_mouse_buf(unsigned char* a1) +{ + Rect rect; + mouseGetRect(&rect); + _refresh_all(&rect, a1); +} + +// 0x4D7814 +void _refresh_all(Rect* rect, unsigned char* a2) +{ + _doing_refresh_all = 1; + + for (int index = 0; index < gWindowsLength; index++) { + _GNW_win_refresh(gWindows[index], rect, a2); + } + + _doing_refresh_all = 0; + + if (a2 == NULL) { + if (!cursorIsHidden()) { + if (_mouse_in(rect->left, rect->top, rect->right, rect->bottom)) { + mouseShowCursor(); + } + } + } +} + +// 0x4D7888 +Window* windowGetWindow(int win) +{ + int v0; + + if (win == -1) { + return NULL; + } + + v0 = gOrderedWindowIds[win]; + if (v0 == -1) { + return NULL; + } + + return gWindows[v0]; +} + +// win_get_buf +// 0x4D78B0 +unsigned char* windowGetBuffer(int win) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return NULL; + } + + if (window == NULL) { + return NULL; + } + + return window->buffer; +} + +// 0x4D78CC +int windowGetAtPoint(int x, int y) +{ + for (int index = gWindowsLength - 1; index >= 0; index--) { + Window* window = gWindows[index]; + if (x >= window->rect.left && x <= window->rect.right + && y >= window->rect.top && y <= window->rect.bottom) { + return window->id; + } + } + + return -1; +} + +// 0x4D7918 +int windowGetWidth(int win) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + return window->width; +} + +// 0x4D7934 +int windowGetHeight(int win) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + return window->height; +} + +// win_get_rect +// 0x4D7950 +int windowGetRect(int win, Rect* rect) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + rectCopy(rect, &(window->rect)); + + return 0; +} + +// 0x4D797C +int _win_check_all_buttons() +{ + if (!gWindowSystemInitialized) { + return -1; + } + + int v1 = -1; + for (int index = gWindowsLength - 1; index >= 1; index--) { + if (_GNW_check_buttons(gWindows[index], &v1) == 0) { + break; + } + + if ((gWindows[index]->flags & WINDOW_FLAG_0x10) != 0) { + break; + } + } + + return v1; +} + +// 0x4D79DC +Button* buttonGetButton(int btn, Window** windowPtr) +{ + for (int index = 0; index < gWindowsLength; index++) { + Window* window = gWindows[index]; + Button* button = window->buttonListHead; + while (button != NULL) { + if (button->id == btn) { + if (windowPtr != NULL) { + *windowPtr = window; + } + + return button; + } + button = button->next; + } + } + + return NULL; +} + +// 0x4D7A34 +int _GNW_check_menu_bars(int a1) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + int v1 = a1; + for (int index = gWindowsLength - 1; index >= 1; index--) { + Window* window = gWindows[index]; + if (window->field_3C != NULL) { + for (int v2 = 0; v2 < window->field_3C->entriesCount; v2++) { + if (v1 == window->field_3C->entries[v2].field_10) { + v1 = _GNW_process_menu(window->field_3C, v2); + break; + } + } + } + + if ((window->flags & 0x10) != 0) { + break; + } + } + + return v1; +} + +// 0x4D69DC +void _win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return; + } + + if (window == NULL) { + return; + } + + int width = window->width; + unsigned char* ptr = window->buffer + y * width + x; + int lineHeight = fontGetLineHeight(); + + 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') { + windowDrawText(win, fileName, maxWidth, x, y, flags); + } else { + if (maxWidth != 0) { + bufferDrawLine(ptr, width, 0, v1, v3, v1, _colorTable[_GNW_wcolor[2]]); + bufferDrawLine(ptr, width, 0, v2, v3, v2, _colorTable[_GNW_wcolor[1]]); + } + } + + ptr += step; + y += lineHeight; + } +} + +// 0x4D80D8 +void programWindowSetTitle(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(gProgramWindowTitle, title, 256); + gProgramWindowTitle[256 - 1] = '\0'; + + if (gProgramWindow != NULL) { + SetWindowTextA(gProgramWindow, gProgramWindowTitle); + } +} + +// [open] implementation for palette operations backed by [XFile]. +// +// 0x4D8174 +int paletteOpenFileImpl(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 = fileOpen(path, mode); + if (stream != NULL) { + return (int)stream; + } + + return -1; +} + +// [read] implementation for palette file operations backed by [XFile]. +// +// 0x4D81E8 +int paletteReadFileImpl(int fd, void* buf, size_t count) +{ + return fileRead(buf, 1, count, (File*)fd); +} + +// [close] implementation for palette file operations backed by [XFile]. +// +// 0x4D81E0 +int paletteCloseFileImpl(int fd) +{ + return fileClose((File*)fd); +} + +// 0x4D8200 +bool showMesageBox(const char* text) +{ + HCURSOR cursor = LoadCursorA(gInstance, MAKEINTRESOURCEA(IDC_ARROW)); + HCURSOR prev = SetCursor(cursor); + ShowCursor(TRUE); + MessageBoxA(NULL, text, NULL, MB_ICONSTOP); + ShowCursor(FALSE); + SetCursor(prev); + return true; +} + +// 0x4D8260 +int buttonCreate(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* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + if (up == NULL && (dn != NULL || hover != NULL)) { + return -1; + } + + Button* button = buttonCreateInternal(win, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, flags | BUTTON_FLAG_0x010000, up, dn, hover); + if (button == NULL) { + return -1; + } + + _button_draw(button, window, 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 (!gWindowSystemInitialized) { + return -1; + } + + Button* button = buttonGetButton(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 (!gWindowSystemInitialized) { + return -1; + } + + if (up == NULL && (down != NULL || hover != NULL)) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + 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, window, button->currentImage, a5, NULL, 0); + + return 0; +} + +// Sets primitive callbacks on the button. +// +// 0x4D8758 +int buttonSetMouseCallbacks(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Button* button = buttonGetButton(btn, NULL); + if (button == NULL) { + return -1; + } + + button->mouseEnterProc = mouseEnterProc; + button->mouseExitProc = mouseExitProc; + button->leftMouseDownProc = mouseDownProc; + button->leftMouseUpProc = mouseUpProc; + + return 0; +} + +// 0x4D8798 +int buttonSetRightMouseCallbacks(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Button* button = buttonGetButton(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 buttonSetCallbacks(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Button* button = buttonGetButton(btn, NULL); + if (button == NULL) { + return -1; + } + + button->onPressed = onPressed; + button->onUnpressed = onUnpressed; + + return 0; +} + +// 0x4D8828 +int buttonSetMask(int btn, unsigned char* mask) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Button* button = buttonGetButton(btn, NULL); + if (button == NULL) { + return -1; + } + + button->mask = mask; + + return 0; +} + +// 0x4D8854 +Button* buttonCreateInternal(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* window = windowGetWindow(win); + if (window == NULL) { + return NULL; + } + + Button* button = internal_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; + } + } + + int buttonId = 1; + while (buttonGetButton(buttonId, NULL) != NULL) { + buttonId++; + } + + button->id = buttonId; + button->flags = flags; + button->rect.left = x; + button->rect.top = y; + button->rect.right = x + width - 1; + button->rect.bottom = 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 = window->buttonListHead; + if (button->next != NULL) { + button->next->prev = button; + } + window->buttonListHead = button; + + return button; +} + +// 0x4D89E4 +bool _win_button_down(int btn) +{ + if (!gWindowSystemInitialized) { + return false; + } + + Button* button = buttonGetButton(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* window, int* keyCodePtr) +{ + Rect v58; + Button* field_34; + Button* field_38; + Button* button; + + if ((window->flags & WINDOW_HIDDEN) != 0) { + return -1; + } + + button = window->buttonListHead; + field_34 = window->field_34; + field_38 = window->field_38; + + if (field_34 != NULL) { + rectCopy(&v58, &(field_34->rect)); + rectOffset(&v58, window->rect.left, window->rect.top); + } else if (field_38 != NULL) { + rectCopy(&v58, &(field_38->rect)); + rectOffset(&v58, window->rect.left, window->rect.top); + } + + *keyCodePtr = -1; + + if (_mouse_click_in(window->rect.left, window->rect.top, window->rect.right, window->rect.bottom)) { + int mouseEvent = mouseGetEvent(); + if ((window->flags & WINDOW_FLAG_0x40) || (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) == 0) { + if (mouseEvent == 0) { + window->field_38 = NULL; + } + } else { + windowUnhide(window->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, window, field_34->mouseDownImage, 1, NULL, 1); + } else { + _button_draw(field_34, window, field_34->mouseUpImage, 1, NULL, 1); + } + + window->field_34 = NULL; + + _last_button_winID = window->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, window, field_38->mouseDownImage, 1, NULL, 1); + } else { + _button_draw(field_38, window, field_38->mouseUpImage, 1, NULL, 1); + } + + window->field_34 = field_38; + + _last_button_winID = window->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 != window->id) { + Window* v26 = windowGetWindow(_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, window->rect.left, window->rect.top); + 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 != window->field_34 && button != window->field_38) { + break; + } + + window->field_38 = button; + window->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, window, button->mouseDownImage, 1, NULL, 1); + break; + } + + Button* v49 = window->field_38; + if (button == v49 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_UP) != 0) { + window->field_38 = NULL; + window->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, window, 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, window, button->mouseHoverImage, 1, NULL, 1); + } else { + _button_draw(button, window, button->mouseUpImage, 1, NULL, 1); + } + break; + } + } + + if (window->field_34 == NULL && mouseEvent == 0) { + window->field_34 = button; + if (!(button->flags & BUTTON_FLAG_DISABLED)) { + *keyCodePtr = button->mouseEnterEventCode; + cb = button->mouseEnterProc; + } + + _button_draw(button, window, 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(window->id); + _button_draw(button, window, button->mouseUpImage, 1, NULL, 1); + } + } else if ((window->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(window->id); + } + } + + _last_button_winID = window->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, window, data, 1, NULL, 1); + + window->field_34 = NULL; + } + + if (*keyCodePtr != -1) { + _last_button_winID = window->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 +bool _button_under_mouse(Button* button, Rect* rect) +{ + if (!_mouse_click_in(rect->left, rect->top, rect->right, rect->bottom)) { + return false; + } + + if (button->mask == NULL) { + return true; + } + + int x; + int y; + mouseGetPosition(&x, &y); + x -= rect->left; + y -= rect->top; + + int width = button->rect.right - button->rect.left + 1; + return button->mask[width * y + x] != 0; +} + +// 0x4D927C +int buttonGetWindowId(int btn) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + if (buttonGetButton(btn, &window) == NULL) { + return -1; + } + + return window->id; +} + +// 0x4D92B4 +int _win_last_button_winID() +{ + return _last_button_winID; +} + +// 0x4D92BC +int buttonDestroy(int btn) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + if (button == NULL) { + return -1; + } + + if (button->prev != NULL) { + button->prev->next = button->next; + } else { + window->buttonListHead = button->next; + } + + if (button->next != NULL) { + button->next->prev = button->prev; + } + + windowFill(window->id, button->rect.left, button->rect.top, button->rect.right - button->rect.left + 1, button->rect.bottom - button->rect.top + 1, window->field_20); + + if (button == window->field_34) { + window->field_34 = NULL; + } + + if (button == window->field_38) { + window->field_38 = NULL; + } + + buttonFree(button); + + return 0; +} + +// 0x4D9374 +void buttonFree(Button* button) +{ + if ((button->flags & BUTTON_FLAG_0x010000) == 0) { + if (button->mouseUpImage != NULL) { + internal_free(button->mouseUpImage); + } + + if (button->mouseDownImage != NULL) { + internal_free(button->mouseDownImage); + } + + if (button->mouseHoverImage != NULL) { + internal_free(button->mouseHoverImage); + } + + if (button->field_3C != NULL) { + internal_free(button->field_3C); + } + + if (button->field_40 != NULL) { + internal_free(button->field_40); + } + + if (button->field_44 != NULL) { + internal_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; + } + } + } + + internal_free(button); +} + +// 0x4D9474 +int buttonEnable(int btn) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + if (button == NULL) { + return -1; + } + + if ((button->flags & BUTTON_FLAG_DISABLED) != 0) { + button->flags &= ~BUTTON_FLAG_DISABLED; + _button_draw(button, window, button->currentImage, 1, NULL, 0); + } + + return 0; +} + +// 0x4D94D0 +int buttonDisable(int btn) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + if (button == NULL) { + return -1; + } + + if ((button->flags & BUTTON_FLAG_DISABLED) == 0) { + button->flags |= BUTTON_FLAG_DISABLED; + + _button_draw(button, window, button->currentImage, 1, NULL, 0); + + if (button == window->field_34) { + if (window->field_34->mouseExitEventCode != -1) { + enqueueInputEvent(window->field_34->mouseExitEventCode); + window->field_34 = NULL; + } + } + } + + return 0; +} + +// 0x4D9554 +int _win_set_button_rest_state(int btn, bool a2, int a3) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + 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, window, 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, window, button->mouseDownImage, 1, NULL, 0); + } + + if (button->radioGroup != NULL) { + button->radioGroup->field_4++; + } + + keyCode = button->lefMouseDownEventCode; + } + } + + if (keyCode != -1) { + if ((a3 & 0x01) != 0) { + enqueueInputEvent(keyCode); + } + } + } + + return 0; +} + +// 0x4D962C +int _win_group_check_buttons(int buttonCount, int* btns, int a3, void (*a4)(int)) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + if (buttonCount >= RADIO_GROUP_BUTTON_LIST_CAPACITY) { + return -1; + } + + for (int groupIndex = 0; groupIndex < RADIO_GROUP_LIST_CAPACITY; groupIndex++) { + RadioGroup* radioGroup = &(gRadioGroups[groupIndex]); + if (radioGroup->buttonsLength == 0) { + radioGroup->field_4 = 0; + + for (int buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) { + Button* button = buttonGetButton(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 (!gWindowSystemInitialized) { + return -1; + } + + if (_win_group_check_buttons(count, btns, 1, NULL) == -1) { + return -1; + } + + Button* button = buttonGetButton(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 +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* window; + buttonGetButton(v1->id, &window); + _button_draw(v1, window, 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 +void _button_draw(Button* button, Window* window, unsigned char* data, int a4, Rect* a5, int a6) +{ + unsigned char* previousImage = NULL; + if (data != NULL) { + Rect v2; + rectCopy(&v2, &(button->rect)); + rectOffset(&v2, window->rect.left, window->rect.top); + + Rect v3; + if (a5 != NULL) { + if (rectIntersection(&v2, a5, &v2) == -1) { + return; + } + + rectCopy(&v3, &v2); + rectOffset(&v3, -window->rect.left, -window->rect.top); + } 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.right - button->rect.left + 1; + if ((button->flags & BUTTON_FLAG_TRANSPARENT) != 0) { + blitBufferToBufferTrans( + data + (v3.top - button->rect.top) * width + v3.left - button->rect.left, + v3.right - v3.left + 1, + v3.bottom - v3.top + 1, + width, + window->buffer + window->width * v3.top + v3.left, + window->width); + } else { + blitBufferToBuffer( + data + (v3.top - button->rect.top) * width + v3.left - button->rect.left, + v3.right - v3.left + 1, + v3.bottom - v3.top + 1, + width, + window->buffer + window->width * v3.top + v3.left, + window->width); + } + } + + previousImage = button->currentImage; + button->currentImage = data; + + if (a4 != 0) { + _GNW_win_refresh(window, &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* window, Rect* rect) +{ + Button* button = window->buttonListHead; + if (button != NULL) { + while (button->next != NULL) { + button = button->next; + } + } + + while (button != NULL) { + _button_draw(button, window, button->currentImage, 0, rect, 0); + button = button->prev; + } +} + +// 0x4D9AA0 +int _win_button_press_and_release(int btn) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + Window* window; + Button* button = buttonGetButton(btn, &window); + if (button == NULL) { + return -1; + } + + _button_draw(button, window, button->mouseDownImage, 1, NULL, 1); + + if (button->leftMouseDownProc != NULL) { + button->leftMouseDownProc(btn, button->lefMouseDownEventCode); + + if ((button->flags & BUTTON_FLAG_0x40) != 0) { + enqueueInputEvent(button->lefMouseDownEventCode); + } + } else { + if (button->lefMouseDownEventCode != -1) { + enqueueInputEvent(button->lefMouseDownEventCode); + } + } + + _button_draw(button, window, button->mouseUpImage, 1, NULL, 1); + + if (button->leftMouseUpProc != NULL) { + button->leftMouseUpProc(btn, button->leftMouseUpEventCode); + + if ((button->flags & BUTTON_FLAG_0x40) != 0) { + enqueueInputEvent(button->leftMouseUpEventCode); + } + } else { + if (button->leftMouseUpEventCode != -1) { + enqueueInputEvent(button->leftMouseUpEventCode); + } + } + + return 0; +} diff --git a/src/window_manager.h b/src/window_manager.h new file mode 100644 index 0000000..6b3751a --- /dev/null +++ b/src/window_manager.h @@ -0,0 +1,240 @@ +#ifndef WINDOW_MANAGER_H +#define WINDOW_MANAGER_H + +#include "geometry.h" + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#define MAX_WINDOW_COUNT (50) + +// The maximum number of radio groups. +#define RADIO_GROUP_LIST_CAPACITY (64) + +// The maximum number of buttons in one radio group. +#define RADIO_GROUP_BUTTON_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 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 struc_176 { + int field_0; + int field_4; + int field_8; + int field_C; + int field_10; + int field_14; + int field_18; + int field_1C; + int field_20; +} struc_176; + +typedef struct struc_177 { + int win; + Rect rect; + int entriesCount; + struc_176 entries[15]; + int field_234; + int field_238; +} struc_177; + +typedef void WindowBlitProc(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); + +typedef struct Button Button; +typedef struct RadioGroup RadioGroup; + +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; + struc_177* field_3C; + WindowBlitProc* blitProc; +} Window; + +typedef void ButtonCallback(int btn, int keyCode); + +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; + +typedef struct RadioGroup { + int field_0; + int field_4; + void (*field_8)(int); + int buttonsLength; + Button* buttons[RADIO_GROUP_BUTTON_LIST_CAPACITY]; +} RadioGroup; + +typedef int(VideoSystemInitProc)(); +typedef void(VideoSystemExitProc)(); + +extern char _path_patches[]; + +extern bool _GNW95_already_running; +extern HANDLE _GNW95_title_mutex; +extern bool gWindowSystemInitialized; +extern int _GNW_wcolor[6]; +extern unsigned char* _screen_buffer; +extern bool _insideWinExit; +extern int _last_button_winID; + +extern int gOrderedWindowIds[MAX_WINDOW_COUNT]; +extern Window* gWindows[MAX_WINDOW_COUNT]; +extern VideoSystemExitProc* gVideoSystemExitProc; +extern int gWindowsLength; +extern int _window_flags; +extern bool _buffering; +extern int _bk_color; +extern VideoSystemInitProc* gVideoSystemInitProc; +extern int _doing_refresh_all; +extern void* _GNW_texture; +extern RadioGroup gRadioGroups[RADIO_GROUP_LIST_CAPACITY]; + +int windowManagerInit(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitProc* videoSystemExitProc, int a3); +void windowManagerExit(void); +int windowCreate(int x, int y, int width, int height, int a4, int flags); +void windowDestroy(int win); +void windowFree(int win); +void _win_buffering(bool a1); +void windowDrawBorder(int win); +void windowDrawText(int win, char* str, int a3, int x, int y, int a6); +void windowDrawLine(int win, int left, int top, int right, int bottom, int color); +void windowDrawRect(int win, int left, int top, int right, int bottom, int color); +void windowFill(int win, int x, int y, int width, int height, int a6); +void windowUnhide(int win); +void windowHide(int win); +void _win_move(int win_index, int x, int y); +void windowRefresh(int win); +void windowRefreshRect(int win, const Rect* rect); +void _GNW_win_refresh(Window* window, Rect* rect, unsigned char* a3); +void windowRefreshAll(Rect* rect); +void _win_clip(Window* window, RectListNode** rect, unsigned char* a3); +void _win_drag(int win); +void _win_get_mouse_buf(unsigned char* a1); +void _refresh_all(Rect* rect, unsigned char* a2); +Window* windowGetWindow(int win); +unsigned char* windowGetBuffer(int win); +int windowGetAtPoint(int x, int y); +int windowGetWidth(int win); +int windowGetHeight(int win); +int windowGetRect(int win, Rect* rect); +int _win_check_all_buttons(); +Button* buttonGetButton(int btn, Window** out_win); +int _GNW_check_menu_bars(int a1); +void _win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags); +void programWindowSetTitle(const char* title); +int paletteOpenFileImpl(const char* path, int flags); +int paletteReadFileImpl(int fd, void* buf, size_t count); +int paletteCloseFileImpl(int fd); +bool showMesageBox(const char* str); +int buttonCreate(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_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 buttonSetMouseCallbacks(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc); +int buttonSetRightMouseCallbacks(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc); +int buttonSetCallbacks(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed); +int buttonSetMask(int btn, unsigned char* mask); +Button* buttonCreateInternal(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); +bool _win_button_down(int btn); +int _GNW_check_buttons(Window* window, int* out_a2); +bool _button_under_mouse(Button* button, Rect* rect); +int buttonGetWindowId(int btn); +int _win_last_button_winID(); +int buttonDestroy(int btn); +void buttonFree(Button* ptr); +int buttonEnable(int btn); +int buttonDisable(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); +int _button_check_group(Button* button); +void _button_draw(Button* button, Window* window, unsigned char* data, int a4, Rect* a5, int a6); +void _GNW_button_refresh(Window* window, Rect* rect); +int _win_button_press_and_release(int btn); + +#endif /* WINDOW_MANAGER_H */ diff --git a/src/window_manager_private.c b/src/window_manager_private.c new file mode 100644 index 0000000..b7c04c5 --- /dev/null +++ b/src/window_manager_private.c @@ -0,0 +1,377 @@ +#include "window_manager_private.h" + +#include "core.h" +#include "memory.h" +#include "text_font.h" +#include "window_manager.h" + +#include +#include + +// 0x51E414 +int _wd = -1; + +// 0x51E418 +int _curr_menu = 0; + +// 0x51E41C +bool _tm_watch_active = false; + +// 0x6B2340 +STRUCT_6B2340 _tm_location[5]; + +// 0x6B2368 +int _tm_text_x; + +// 0x6B236C +int _tm_h; + +// 0x6B2370 +STRUCT_6B2370 _tm_queue[5]; + +// 0x6B23AC +int _tm_persistence; + +// 0x6B23B0 +int _scr_center_x; + +// 0x6B23B4 +int _tm_text_y; + +// 0x6B23B8 +int _tm_kill; + +// 0x6B23BC +int _tm_add; + +// x +// +// 0x6B23C0 +int _curry; + +// y +// +// 0x6B23C4 +int _currx; + +// 0x6B23D0 +char gProgramWindowTitle[256]; + +// 0x4DC30C +int _win_debug(char* a1) +{ + if (!gWindowSystemInitialized) { + return -1; + } + + // TODO: Incomplete. + + windowRefresh(_wd); + + return 0; +} + +// 0x4DC65C +void _win_debug_delete() +{ + windowDestroy(_wd); + _wd = -1; +} + +// 0x4DC674 +int _win_register_menu_bar(int win, int x, int y, int width, int height, int a6, int a7) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + if (window->field_3C != NULL) { + return -1; + } + + int right = x + width; + if (right > window->width) { + return -1; + } + + int bottom = y + height; + if (bottom > window->height) { + return -1; + } + + struc_177* v14 = window->field_3C = internal_malloc(sizeof(struc_177)); + if (v14 == NULL) { + return -1; + } + + v14->win = win; + v14->rect.left = x; + v14->rect.top = y; + v14->rect.right = right - 1; + v14->rect.bottom = bottom - 1; + v14->entriesCount = 0; + v14->field_234 = a6; + v14->field_238 = a7; + + windowFill(win, x, y, width, height, a7); + windowDrawRect(win, x, y, right - 1, bottom - 1, a6); + + return 0; +} + +// 0x4DC768 +int _win_register_menu_pulldown(int win, int x, char* str, int a4) +{ + Window* window = windowGetWindow(win); + + if (!gWindowSystemInitialized) { + return -1; + } + + if (window == NULL) { + return -1; + } + + struc_177* field_3C = window->field_3C; + if (field_3C == NULL) { + return -1; + } + + if (window->field_3C->entriesCount == 15) { + return -1; + } + + int btn = buttonCreate(win, + field_3C->rect.left + x, + (field_3C->rect.top + field_3C->rect.bottom - fontGetLineHeight()) / 2, + fontGetStringWidth(str), + fontGetLineHeight(), + -1, + -1, + a4, + -1, + NULL, + NULL, + NULL, + 0); + if (btn == -1) { + return -1; + } + + // TODO: Incomplete. + + return 0; +} + +// 0x4DCA30 +int _win_width_needed(char** fileNameList, int fileNameListLength) +{ + int maxWidth = 0; + + for (int index = 0; index < fileNameListLength; index++) { + int width = fontGetStringWidth(fileNameList[index]); + if (width > maxWidth) { + maxWidth = width; + } + } + + return maxWidth; +} + +// 0x4DC930 +int _GNW_process_menu(struc_177* ptr, int i) +{ + // TODO: Incomplete + + return 0; +} + +// Calculates max length of string needed to represent a1 or a2. +// +// 0x4DD03C +int _calc_max_field_chars_wcursor(int a1, int a2) +{ + char* str = internal_malloc(17); + if (str == NULL) { + return -1; + } + + sprintf(str, "%d", a1); + int len1 = strlen(str); + + sprintf(str, "%d", a2); + int len2 = strlen(str); + + internal_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.right / 2; + + if (_scr_size.bottom >= 479) { + _tm_text_y = 16; + _tm_text_x = 16; + } else { + _tm_text_y = 10; + _tm_text_x = 10; + } + + _tm_h = 2 * _tm_text_y + fontGetLineHeight(); + + v1 = _scr_size.bottom >> 3; + v2 = _scr_size.bottom >> 2; + + for (i = 0; i < 5; i++) { + _tm_location[i].field_4 = v1 * i + v2; + _tm_location[i].field_0 = 0; + } +} + +// 0x4DD4A4 +void _GNW_intr_exit() +{ + tickersRemove(_tm_watch_msgs); + while (_tm_kill != -1) { + _tm_kill_msg(); + } +} + +// 0x4DD66C +void _tm_watch_msgs() +{ + if (_tm_watch_active) { + return; + } + + _tm_watch_active = 1; + while (_tm_kill != -1) { + if (getTicksSince(_tm_queue[_tm_kill].field_0) < _tm_persistence) { + break; + } + + _tm_kill_msg(); + } + _tm_watch_active = 0; +} + +// 0x4DD6C0 +void _tm_kill_msg() +{ + int v0; + + v0 = _tm_kill; + if (v0 != -1) { + windowDestroy(_tm_queue[_tm_kill].field_4); + _tm_location[_tm_queue[_tm_kill].field_8].field_0 = 0; + + if (v0 == 5) { + v0 = 0; + } + + if (v0 == _tm_add) { + _tm_add = 0; + _tm_kill = -1; + tickersRemove(_tm_watch_msgs); + v0 = _tm_kill; + } + } + + _tm_kill = v0; +} + +// 0x4DD744 +void _tm_kill_out_of_order(int a1) +{ + int v7; + int v6; + + if (_tm_kill == -1) { + return; + } + + if (!_tm_index_active(a1)) { + return; + } + + windowDestroy(_tm_queue[a1].field_4); + + _tm_location[_tm_queue[a1].field_8].field_0 = 0; + + if (a1 != _tm_kill) { + v6 = a1; + do { + v7 = v6 - 1; + if (v7 < 0) { + v7 = 4; + } + + memcpy(&(_tm_queue[v6]), &(_tm_queue[v7]), sizeof(STRUCT_6B2370)); + v6 = v7; + } while (v7 != _tm_kill); + } + + if (++_tm_kill == 5) { + _tm_kill = 0; + } + + if (_tm_add == _tm_kill) { + _tm_add = 0; + _tm_kill = -1; + tickersRemove(_tm_watch_msgs); + } +} + +// 0x4DD82C +void _tm_click_response(int btn) +{ + int win; + int v3; + + if (_tm_kill == -1) { + return; + } + + win = buttonGetWindowId(btn); + v3 = _tm_kill; + while (win != _tm_queue[v3].field_4) { + v3++; + if (v3 == 5) { + v3 = 0; + } + + if (v3 == _tm_kill || !_tm_index_active(v3)) + return; + } + + _tm_kill_out_of_order(v3); +} + +// 0x4DD870 +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; +} diff --git a/src/window_manager_private.h b/src/window_manager_private.h new file mode 100644 index 0000000..00e6b17 --- /dev/null +++ b/src/window_manager_private.h @@ -0,0 +1,52 @@ +#ifndef WINDOW_MANAGER_PRIVATE_H +#define WINDOW_MANAGER_PRIVATE_H + +#include + +typedef struct struc_177 struc_177; + +typedef struct STRUCT_6B2340 { + int field_0; + int field_4; +} STRUCT_6B2340; + +typedef struct STRUCT_6B2370 { + int field_0; + // win + int field_4; + int field_8; +} STRUCT_6B2370; + +extern int _wd; +extern int _curr_menu; +extern bool _tm_watch_active; + +extern STRUCT_6B2340 _tm_location[5]; +extern int _tm_text_x; +extern int _tm_h; +extern STRUCT_6B2370 _tm_queue[5]; +extern int _tm_persistence; +extern int _scr_center_x; +extern int _tm_text_y; +extern int _tm_kill; +extern int _tm_add; +extern int _curry; +extern int _currx; +extern char gProgramWindowTitle[256]; + +int _win_debug(char* a1); +void _win_debug_delete(); +int _win_register_menu_bar(int win, int x, int y, int width, int height, int a6, int a7); +int _win_register_menu_pulldown(int win, int x, char* str, int a4); +int _win_width_needed(char** fileNameList, int fileNameListLength); +int _GNW_process_menu(struc_177* ptr, int i); +int _calc_max_field_chars_wcursor(int a1, int a2); +void _GNW_intr_init(); +void _GNW_intr_exit(); +void _tm_watch_msgs(); +void _tm_kill_msg(); +void _tm_kill_out_of_order(int a1); +void _tm_click_response(int btn); +int _tm_index_active(int a1); + +#endif /* WINDOW_MANAGER_PRIVATE_H */ diff --git a/src/word_wrap.c b/src/word_wrap.c new file mode 100644 index 0000000..59471c6 --- /dev/null +++ b/src/word_wrap.c @@ -0,0 +1,74 @@ +#include "word_wrap.h" + +#include "text_font.h" + +#include +#include +#include + +// 0x4BC6F0 +int wordWrap(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 (fontGetMonospacedCharacterWidth() > width) { + return -1; + } + + if (fontGetStringWidth(string) < width) { + breakpoints[*breakpointsLengthPtr] = (short)strlen(string); + *breakpointsLengthPtr += 1; + return 0; + } + + int gap = fontGetLetterSpacing(); + + int accum = 0; + const char* prevSpaceOrHyphen = NULL; + const char* pch = string; + while (*pch != '\0') { + accum += gap + fontGetCharacterWidth(*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; +} diff --git a/src/word_wrap.h b/src/word_wrap.h new file mode 100644 index 0000000..46c87f6 --- /dev/null +++ b/src/word_wrap.h @@ -0,0 +1,8 @@ +#ifndef WORD_WRAP_H +#define WORD_WRAP_H + +#define WORD_WRAP_MAX_COUNT (64) + +int wordWrap(const char* string, int width, short* breakpoints, short* breakpointsLengthPtr); + +#endif /* WORD_WRAP_H */ diff --git a/src/world_map.c b/src/world_map.c new file mode 100644 index 0000000..5374b72 --- /dev/null +++ b/src/world_map.c @@ -0,0 +1,6473 @@ +#include "world_map.h" + +#include "animation.h" +#include "color.h" +#include "combat.h" +#include "combat_ai.h" +#include "core.h" +#include "critter.h" +#include "cycle.h" +#include "dbox.h" +#include "debug.h" +#include "display_monitor.h" +#include "draw.h" +#include "game.h" +#include "game_config.h" +#include "game_mouse.h" +#include "game_movie.h" +#include "game_sound.h" +#include "interface.h" +#include "item.h" +#include "memory.h" +#include "object.h" +#include "party_member.h" +#include "perk.h" +#include "proto_instance.h" +#include "queue.h" +#include "random.h" +#include "scripts.h" +#include "skill.h" +#include "stat.h" +#include "string_parsers.h" +#include "text_font.h" +#include "tile.h" +#include "window_manager.h" + +#include +#include +#include + +static_assert(sizeof(Terrain) == 128, "wrong size"); +static_assert(sizeof(ENC_BASE_TYPE) == 3056, "wrong size"); +static_assert(sizeof(EncounterTable) == 7460, "wrong size"); + +#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) + +// 0x4BC860 +const int _can_rest_here[ELEVATION_COUNT] = { + MAP_CAN_REST_ELEVATION_0, + MAP_CAN_REST_ELEVATION_1, + MAP_CAN_REST_ELEVATION_2, +}; + +// 0x4BC86C +const int gDayPartEncounterFrequencyModifiers[DAY_PART_COUNT] = { + 40, + 30, + 0, +}; + +// 0x4BC878 +const char* off_4BC878[2] = { + "You detect something up ahead.", + "Do you wish to encounter it?", +}; + +// 0x4BC880 +MessageListItem stru_4BC880; + +// 0x50EE44 +char _aCricket[] = "cricket"; + +// 0x50EE4C +char _aCricket1[] = "cricket1"; + +// 0x51DD88 +const char* _wmStateStrs[2] = { + "off", + "on" +}; + +// 0x51DD90 +const char* _wmYesNoStrs[2] = { + "no", + "yes", +}; + +// 0x51DD98 +const char* gEncounterFrequencyTypeKeys[ENCOUNTER_FREQUENCY_TYPE_COUNT] = { + "none", + "rare", + "uncommon", + "common", + "frequent", + "forced", +}; + +// 0x51DDB0 +const char* _wmFillStrs[9] = { + "no_fill", + "fill_n", + "fill_s", + "fill_e", + "fill_w", + "fill_nw", + "fill_ne", + "fill_sw", + "fill_se", +}; + +// 0x51DDD4 +const char* _wmSceneryStrs[ENCOUNTER_SCENERY_TYPE_COUNT] = { + "none", + "light", + "normal", + "heavy", +}; + +// 0x51DDE4 +Terrain* gTerrains = NULL; + +// 0x51DDE8 +int gTerrainsLength = 0; + +// 0x51DDEC +TileInfo* gWorldmapTiles = NULL; + +// 0x51DDF0 +int gWorldmapTilesLength = 0; + +// The width of worldmap grid in tiles. +// +// There is no separate variable for grid height, instead its calculated as +// [gWorldmapTilesLength] / [gWorldmapTilesGridWidth]. +// +// num_horizontal_tiles +// 0x51DDF4 +int gWorldmapGridWidth = 0; + +// 0x51DDF8 +CityInfo* gCities = NULL; + +// 0x51DDFC +int gCitiesLength = 0; + +// 0x51DE00 +const char* gCitySizeKeys[CITY_SIZE_COUNT] = { + "small", + "medium", + "large", +}; + +// 0x51DE0C +MapInfo* gMaps = NULL; + +// 0x51DE10 +int gMapsLength = 0; + +// 0x51DE14 +int gWorldmapWindow = -1; + +// 0x51DE18 +CacheEntry* gWorldmapBoxFrmHandle = INVALID_CACHE_ENTRY; + +// 0x51DE1C +int gWorldmapBoxFrmWidth = 0; + +// 0x51DE20 +int gWorldmapBoxFrmHeight = 0; + +// 0x51DE24 +unsigned char* gWorldmapWindowBuffer = NULL; + +// 0x51DE28 +unsigned char* gWorldmapBoxFrmData = NULL; + +// 0x51DE2C +int gWorldmapOffsetX = 0; + +// 0x51DE30 +int gWorldmapOffsetY = 0; + +// +unsigned char* _circleBlendTable = NULL; + +// +int _wmInterfaceWasInitialized = 0; + +// encounter types +const char* _wmEncOpStrs[ENCOUNTER_SITUATION_COUNT] = { + "nothing", + "ambush", + "fighting", + "and", +}; + +// operators +const char* _wmConditionalOpStrs[ENCOUNTER_CONDITIONAL_OPERATOR_COUNT] = { + "_", + "==", + "!=", + "<", + ">", +}; + +// 0x51DE6C +const char* gEncounterFormationTypeKeys[ENCOUNTER_FORMATION_TYPE_COUNT] = { + "surrounding", + "straight_line", + "double_line", + "wedge", + "cone", + "huddle", +}; + +// 0x51DE84 +int gWorldmapEncounterFrmIds[WORLD_MAP_ENCOUNTER_FRM_COUNT] = { + 154, + 155, + 438, + 439, +}; + +// 0x51DE94 +int* gQuickDestinations = NULL; + +// 0x51DE98 +int gQuickDestinationsLength = 0; + +// 0x51DE9C +int _wmTownMapCurArea = -1; + +// 0x51DEA0 +unsigned int _wmLastRndTime = 0; + +// 0x51DEA4 +int _wmRndIndex = 0; + +// 0x51DEA8 +int _wmRndCallCount = 0; + +// 0x51DEAC +int _terrainCounter = 1; + +// 0x51DEB0 +unsigned int _lastTime_2 = 0; + +// 0x51DEB4 +bool _couldScroll = true; + +// 0x51DEB8 +unsigned char* gWorldmapCityMapFrmData = NULL; + +// 0x51DEBC +CacheEntry* gWorldmapCityMapFrmHandle = INVALID_CACHE_ENTRY; + +// 0x51DEC0 +int gWorldmapCityMapFrmWidth = 0; + +// 0x51DEC4 +int gWorldmapCityMapFrmHeight = 0; + +// 0x51DEC8 +char* _wmRemapSfxList[2] = { + _aCricket, + _aCricket1, +}; + +// 0x672DB8 +int _wmRndTileDirs[2]; + +// 0x672DC0 +int _wmRndCenterTiles[2]; + +// 0x672DC8 +int _wmRndCenterRotations[2]; + +// 0x672DD0 +int _wmRndRotOffsets[2]; + +// Buttons for city entrances. +// +// 0x672DD8 +int _wmTownMapButtonId[ENTRANCE_LIST_CAPACITY]; + +// 0x672E00 +int _wmGenData; + +// 0x672E04 +int _Meet_Frank_Horrigan; + +// Current_town. +// +// 0x672E08 +int _WorldMapCurrArea; + +// Current town x. +// +// 0x672E0C +int _world_xpos; + +// Current town y. +// +// 0x672E10 +int _world_ypos; + +// 0x672E14 +SubtileInfo* _world_subtile; + +// 0x672E18 +int dword_672E18; + +// 0x672E1C +bool gWorldmapIsTravelling; + +// 0x672E20 +int gWorldmapTravelDestX; + +// 0x672E24 +int gWorldmapTravelDestY; + +// 0x672E28 +int dword_672E28; + +// 0x672E2C +int dword_672E2C; + +// 0x672E30 +int dword_672E30; + +// 0x672E34 +int dword_672E34; + +// 0x672E38 +int _x_line_inc; + +// 0x672E3C +int dword_672E3C; + +// 0x672E40 +int _y_line_inc; + +// 0x672E44 +int dword_672E44; + +// 0x672E48 +int _wmEncounterIconShow; + +// 0x672E4C +int _EncounterMapID; + +// 0x672E50 +int dword_672E50; + +// 0x672E54 +int dword_672E54; + +// 0x672E58 +int _wmRndCursorFid; + +// 0x672E5C +int _old_world_xpos; + +// 0x672E60 +int _old_world_ypos; + +// 0x672E64 +bool gWorldmapIsInCar; + +// 0x672E68 +int _carCurrentArea; + +// 0x672E6C +int gWorldmapCarFuel; + +// 0x672E70 +CacheEntry* gWorldmapCarFrmHandle; + +// 0x672E74 +Art* gWorldmapCarFrm; + +// 0x672E78 +int gWorldmapCarFrmWidth; + +// 0x672E7C +int gWorldmapCarFrmHeight; + +// 0x672E80 +int gWorldmapCarFrmCurrentFrame; + +// 0x672E84 +CacheEntry* gWorldmapHotspotUpFrmHandle; + +// 0x672E88 +unsigned char* gWorldmapHotspotUpFrmData; + +// 0x672E8C +CacheEntry* gWorldmapHotspotDownFrmHandle; + +// 0x672E90 +unsigned char* gWorldmapHotspotDownFrmData; + +// 0x672E94 +int gWorldmapHotspotUpFrmWidth; + +// 0x672E98 +int gWorldmapHotspotUpFrmHeight; + +// 0x672E9C +CacheEntry* gWorldmapDestinationMarkerFrmHandle; + +// 0x672EA0 +unsigned char* gWorldmapDestinationMarkerFrmData; + +// 0x672EA4 +int gWorldmapDestinationMarkerFrmWidth; + +// 0x672EA8 +int gWorldmapDestinationMarkerFrmHeight; + +// 0x672EAC +CacheEntry* gWorldmapLocationMarkerFrmHandle; + +// 0x672EB0 +unsigned char* gWorldmapLocationMarkerFrmData; + +// 0x672EB4 +int gWorldmapLocationMarkerFrmWidth; + +// 0x672EB8 +int gWorldmapLocationMarkerFrmHeight; + +// 0x672EBC +CacheEntry* gWorldmapEncounterFrmHandles[WORLD_MAP_ENCOUNTER_FRM_COUNT]; + +// 0x672ECC +unsigned char* gWorldmapEncounterFrmData[WORLD_MAP_ENCOUNTER_FRM_COUNT]; + +// 0x672EDC +int gWorldmapEncounterFrmWidths[WORLD_MAP_ENCOUNTER_FRM_COUNT]; + +// 0x672EEC +int gWorldmapEncounterFrmHeights[WORLD_MAP_ENCOUNTER_FRM_COUNT]; + +// 0x672EFC +int _wmViewportRightScrlLimit; + +// 0x672F00 +int _wmViewportBottomtScrlLimit; + +// 0x672F04 +CacheEntry* gWorldmapTownTabsUnderlayFrmHandle; + +// 0x672F08 +int gWorldmapTownTabsUnderlayFrmWidth; + +// 0x672F0C +int gWorldmapTownTabsUnderlayFrmHeight; + +// 0x672F10 +int _LastTabsYOffset; + +// 0x672F14 +unsigned char* gWorldmapTownTabsUnderlayFrmData; + +// 0x672F18 +CacheEntry* gWorldmapTownTabsEdgeFrmHandle; + +// 0x672F1C +unsigned char* gWorldmapTownTabsEdgeFrmData; + +// 0x672F20 +CacheEntry* gWorldmapDialFrmHandle; + +// 0x672F24 +int gWorldmapDialFrmWidth; + +// 0x672F28 +int gWorldmapDialFrmHeight; + +// 0x672F2C +int gWorldmapDialFrmCurrentFrame; + +// 0x672F30 +Art* gWorldmapDialFrm; + +// 0x672F34 +CacheEntry* gWorldmapCarOverlayFrmHandle; + +// 0x672F38 +int gWorldmapCarOverlayFrmWidth; + +// 0x672F3C +int gWorldmapCarOverlayFrmHeight; + +// 0x672F40 +unsigned char* gWorldmapCarOverlayFrmData; + +// 0x672F44 +CacheEntry* gWorldmapGlobeOverlayFrmHandle; + +// 0x672F48 +int gWorldmapGlobeOverlayFrmWidth; + +// 0x672F4C +int gWorldmapGloveOverlayFrmHeight; + +// 0x672F50 +unsigned char* gWorldmapGlobeOverlayFrmData; + +// 0x672F54 +int dword_672F54; + +// 0x672F58 +int _tabsOffset; + +// 0x672F5C +CacheEntry* gWorldmapLittleRedButtonUpFrmHandle; + +// 0x672F60 +CacheEntry* gWorldmapLittleRedButtonDownFrmHandle; + +// 0x672F64 +unsigned char* gWorldmapLittleRedButtonUpFrmData; + +// 0x672F68 +unsigned char* gWorldmapLittleRedButtonDownFrmData; + +// 0x672F6C +CacheEntry* gWorldmapTownListScrollUpFrmHandle[WORLDMAP_ARROW_FRM_COUNT]; + +// 0x672F74 +int gWorldmapTownListScrollUpFrmWidth; + +// 0x672F78 +int gWorldmapTownListScrollUpFrmHeight; + +// 0x672F7C +unsigned char* gWorldmapTownListScrollUpFrmData[WORLDMAP_ARROW_FRM_COUNT]; + +// 0x672F84 +CacheEntry* gWorldmapTownListScrollDownFrmHandle[WORLDMAP_ARROW_FRM_COUNT]; + +// 0x672F8C +int gWorldmapTownListScrollDownFrmWidth; + +// 0x672F90 +int gWorldmapTownListScrollDownFrmHeight; + +// 0x672F94 +unsigned char* gWorldmapTownListScrollDownFrmData[WORLDMAP_ARROW_FRM_COUNT]; + +// 0x672F9C +CacheEntry* gWorldmapMonthsFrmHandle; + +// 0x672FA0 +Art* gWorldmapMonthsFrm; + +// 0x672FA4 +CacheEntry* gWorldmapNumbersFrmHandle; + +// 0x672FA8 +Art* gWorldmapNumbersFrm; + +// 0x672FAC +int _fontnum; + +// worldmap.msg +// +// 0x672FB0 +MessageList gWorldmapMessageList; + +// 0x672FB8 +int _wmFreqValues[6]; + +// 0x672FD0 +int _wmRndOriginalCenterTile; + +// worldmap.txt +// +// 0x672FD4 +Config* gWorldmapConfig; + +// 0x672FD8 +int _wmTownMapSubButtonIds[7]; + +// 0x672FF4 +ENC_BASE_TYPE* _wmEncBaseTypeList; + +// 0x672FF8 +CitySizeDescription gCitySizeDescriptions[CITY_SIZE_COUNT]; + +// 0x673034 +EncounterTable* gEncounterTables; + +// Number of enc_base_types. +// +// 0x673038 +int _wmMaxEncBaseTypes; + +// 0x67303C +int gEncounterTablesLength; + +// wmWorldMap_init +// 0x4BC89C +int worldmapInit() +{ + char path[MAX_PATH]; + + if (_wmGenDataInit() == -1) { + return -1; + } + + if (!messageListInit(&gWorldmapMessageList)) { + return -1; + } + + sprintf(path, "%s%s", asc_5186C8, "worldmap.msg"); + + if (!messageListLoad(&gWorldmapMessageList, path)) { + return -1; + } + + if (worldmapConfigInit() == -1) { + return -1; + } + + _wmViewportRightScrlLimit = WM_TILE_WIDTH * gWorldmapGridWidth - WM_VIEW_WIDTH; + _wmViewportBottomtScrlLimit = WM_TILE_HEIGHT * (gWorldmapTilesLength / gWorldmapGridWidth) - WM_VIEW_HEIGHT; + _circleBlendTable = _getColorBlendTable(_colorTable[992]); + + _wmMarkSubTileRadiusVisited(_world_xpos, _world_ypos); + _wmWorldMapSaveTempData(); + + return 0; +} + +// 0x4BC984 +int _wmGenDataInit() +{ + _Meet_Frank_Horrigan = 0; + _WorldMapCurrArea = -1; + _world_xpos = 173; + _world_ypos = 122; + _world_subtile = 0; + dword_672E18 = 0; + gWorldmapIsTravelling = false; + gWorldmapTravelDestX = -1; + gWorldmapTravelDestY = -1; + dword_672E28 = 0; + dword_672E2C = 0; + dword_672E30 = 0; + dword_672E34 = 0; + _x_line_inc = 0; + _y_line_inc = 0; + dword_672E44 = 0; + _wmEncounterIconShow = 0; + _EncounterMapID = -1; + dword_672E50 = -1; + dword_672E54 = -1; + _wmRndCursorFid = -1; + _old_world_xpos = 0; + _old_world_ypos = 0; + gWorldmapIsInCar = false; + _carCurrentArea = -1; + gWorldmapCarFuel = CAR_FUEL_MAX; + gWorldmapCarFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCarFrmWidth = 0; + gWorldmapCarFrmHeight = 0; + gWorldmapCarFrmCurrentFrame = 0; + gWorldmapHotspotUpFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapHotspotUpFrmData = NULL; + gWorldmapHotspotDownFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapHotspotDownFrmData = NULL; + gWorldmapHotspotUpFrmWidth = 0; + gWorldmapHotspotUpFrmHeight = 0; + gWorldmapDestinationMarkerFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapDestinationMarkerFrmData = NULL; + gWorldmapDestinationMarkerFrmWidth = 0; + gWorldmapDestinationMarkerFrmHeight = 0; + gWorldmapLocationMarkerFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLocationMarkerFrmData = NULL; + gWorldmapLocationMarkerFrmWidth = 0; + _wmGenData = 0; + dword_672E3C = 0; + gWorldmapLocationMarkerFrmHeight = 0; + gWorldmapCarFrm = NULL; + + for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) { + gWorldmapEncounterFrmHandles[index] = INVALID_CACHE_ENTRY; + gWorldmapEncounterFrmData[index] = NULL; + gWorldmapEncounterFrmWidths[index] = 0; + gWorldmapEncounterFrmHeights[index] = 0; + } + + _wmViewportBottomtScrlLimit = 0; + gWorldmapTownTabsUnderlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsUnderlayFrmData = NULL; + gWorldmapTownTabsUnderlayFrmWidth = 0; + gWorldmapTownTabsUnderlayFrmHeight = 0; + _LastTabsYOffset = 0; + gWorldmapTownTabsEdgeFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsEdgeFrmData = 0; + gWorldmapDialFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapDialFrm = NULL; + gWorldmapDialFrmWidth = 0; + gWorldmapDialFrmHeight = 0; + gWorldmapDialFrmCurrentFrame = 0; + gWorldmapCarOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCarOverlayFrmData = NULL; + gWorldmapCarOverlayFrmWidth = 0; + gWorldmapCarOverlayFrmHeight = 0; + gWorldmapGlobeOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapGlobeOverlayFrmData = NULL; + gWorldmapGlobeOverlayFrmWidth = 0; + gWorldmapGloveOverlayFrmHeight = 0; + dword_672F54 = 0; + _tabsOffset = 0; + gWorldmapLittleRedButtonUpFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonDownFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonUpFrmData = NULL; + gWorldmapLittleRedButtonDownFrmData = NULL; + _wmViewportRightScrlLimit = 0; + + for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { + gWorldmapTownListScrollDownFrmHandle[index] = INVALID_CACHE_ENTRY; + gWorldmapTownListScrollUpFrmHandle[index] = INVALID_CACHE_ENTRY; + gWorldmapTownListScrollUpFrmData[index] = NULL; + gWorldmapTownListScrollDownFrmData[index] = NULL; + } + + gWorldmapTownListScrollUpFrmHeight = 0; + gWorldmapTownListScrollDownFrmWidth = 0; + gWorldmapTownListScrollDownFrmHeight = 0; + gWorldmapMonthsFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapMonthsFrm = NULL; + gWorldmapNumbersFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapNumbersFrm = NULL; + gWorldmapTownListScrollUpFrmWidth = 0; + + return 0; +} + +// 0x4BCBFC +int _wmGenDataReset() +{ + _Meet_Frank_Horrigan = 0; + _world_subtile = 0; + dword_672E18 = 0; + gWorldmapIsTravelling = false; + dword_672E28 = 0; + dword_672E2C = 0; + dword_672E30 = 0; + dword_672E34 = 0; + _x_line_inc = 0; + _y_line_inc = 0; + dword_672E44 = 0; + _wmEncounterIconShow = 0; + _wmGenData = 0; + _WorldMapCurrArea = -1; + _world_xpos = 173; + _world_ypos = 122; + gWorldmapTravelDestX = -1; + gWorldmapTravelDestY = -1; + _EncounterMapID = -1; + dword_672E50 = -1; + dword_672E54 = -1; + _wmRndCursorFid = -1; + _carCurrentArea = -1; + gWorldmapCarFuel = CAR_FUEL_MAX; + gWorldmapCarFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsUnderlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsEdgeFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapDialFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCarOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapGlobeOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonUpFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonDownFrmHandle = INVALID_CACHE_ENTRY; + dword_672E3C = 0; + _old_world_xpos = 0; + _old_world_ypos = 0; + gWorldmapIsInCar = false; + gWorldmapCarFrmWidth = 0; + gWorldmapCarFrmHeight = 0; + gWorldmapCarFrmCurrentFrame = 0; + gWorldmapTownTabsUnderlayFrmData = NULL; + gWorldmapTownTabsUnderlayFrmHeight = 0; + _LastTabsYOffset = 0; + gWorldmapTownTabsEdgeFrmData = 0; + gWorldmapDialFrm = NULL; + gWorldmapDialFrmWidth = 0; + gWorldmapDialFrmHeight = 0; + gWorldmapDialFrmCurrentFrame = 0; + gWorldmapCarOverlayFrmData = NULL; + gWorldmapCarOverlayFrmWidth = 0; + gWorldmapCarOverlayFrmHeight = 0; + gWorldmapGlobeOverlayFrmData = NULL; + gWorldmapGlobeOverlayFrmWidth = 0; + gWorldmapGloveOverlayFrmHeight = 0; + dword_672F54 = 0; + _tabsOffset = 0; + gWorldmapLittleRedButtonUpFrmData = NULL; + gWorldmapLittleRedButtonDownFrmData = NULL; + gWorldmapTownTabsUnderlayFrmWidth = 0; + gWorldmapCarFrm = NULL; + + for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { + gWorldmapTownListScrollUpFrmData[index] = NULL; + gWorldmapTownListScrollDownFrmHandle[index] = INVALID_CACHE_ENTRY; + + gWorldmapTownListScrollDownFrmData[index] = NULL; + gWorldmapTownListScrollUpFrmHandle[index] = INVALID_CACHE_ENTRY; + } + + gWorldmapMonthsFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapNumbersFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownListScrollUpFrmWidth = 0; + gWorldmapTownListScrollUpFrmHeight = 0; + gWorldmapTownListScrollDownFrmWidth = 0; + gWorldmapTownListScrollDownFrmHeight = 0; + gWorldmapMonthsFrm = NULL; + gWorldmapNumbersFrm = NULL; + + _wmMarkSubTileRadiusVisited(_world_xpos, _world_ypos); + + return 0; +} + +// 0x4BCE00 +void worldmapExit() +{ + if (gTerrains != NULL) { + internal_free(gTerrains); + gTerrains = NULL; + } + + if (gWorldmapTiles) { + internal_free(gWorldmapTiles); + gWorldmapTiles = NULL; + } + + gWorldmapGridWidth = 0; + gWorldmapTilesLength = 0; + + if (gEncounterTables != NULL) { + internal_free(gEncounterTables); + gEncounterTables = NULL; + } + + gEncounterTablesLength = 0; + + if (_wmEncBaseTypeList != NULL) { + internal_free(_wmEncBaseTypeList); + _wmEncBaseTypeList = NULL; + } + + _wmMaxEncBaseTypes = 0; + + if (gCities != NULL) { + internal_free(gCities); + gCities = NULL; + } + + gCitiesLength = 0; + + if (gMaps != NULL) { + internal_free(gMaps); + } + + gMapsLength = 0; + + if (_circleBlendTable != NULL) { + _freeColorBlendTable(_colorTable[992]); + _circleBlendTable = NULL; + } + + messageListFree(&gWorldmapMessageList); +} + +// 0x4BCEF8 +int worldmapReset() +{ + gWorldmapOffsetX = 0; + gWorldmapOffsetY = 0; + + _wmWorldMapLoadTempData(); + _wmMarkAllSubTiles(0); + + return _wmGenDataReset(); +} + +// 0x4BCF28 +int worldmapSave(File* stream) +{ + int i; + int j; + int k; + EncounterTable* encounter_table; + EncounterEntry* encounter_entry; + + if (fileWriteInt32(stream, _Meet_Frank_Horrigan) == -1) return -1; + if (fileWriteInt32(stream, _WorldMapCurrArea) == -1) return -1; + if (fileWriteInt32(stream, _world_xpos) == -1) return -1; + if (fileWriteInt32(stream, _world_ypos) == -1) return -1; + if (fileWriteInt32(stream, _wmEncounterIconShow) == -1) return -1; + if (fileWriteInt32(stream, _EncounterMapID) == -1) return -1; + if (fileWriteInt32(stream, dword_672E50) == -1) return -1; + if (fileWriteInt32(stream, dword_672E54) == -1) return -1; + if (fileWriteBool(stream, gWorldmapIsInCar) == -1) return -1; + if (fileWriteInt32(stream, _carCurrentArea) == -1) return -1; + if (fileWriteInt32(stream, gWorldmapCarFuel) == -1) return -1; + if (fileWriteInt32(stream, gCitiesLength) == -1) return -1; + + for (int cityIndex = 0; cityIndex < gCitiesLength; cityIndex++) { + CityInfo* cityInfo = &(gCities[cityIndex]); + if (fileWriteInt32(stream, cityInfo->x) == -1) return -1; + if (fileWriteInt32(stream, cityInfo->y) == -1) return -1; + if (fileWriteInt32(stream, cityInfo->state) == -1) return -1; + if (fileWriteInt32(stream, cityInfo->field_40) == -1) return -1; + if (fileWriteInt32(stream, cityInfo->entrancesLength) == -1) return -1; + + for (int entranceIndex = 0; entranceIndex < cityInfo->entrancesLength; entranceIndex++) { + EntranceInfo* entrance = &(cityInfo->entrances[entranceIndex]); + if (fileWriteInt32(stream, entrance->state) == -1) return -1; + } + } + + if (fileWriteInt32(stream, gWorldmapTilesLength) == -1) return -1; + if (fileWriteInt32(stream, gWorldmapGridWidth) == -1) return -1; + + for (int tileIndex = 0; tileIndex < gWorldmapTilesLength; tileIndex++) { + TileInfo* tileInfo = &(gWorldmapTiles[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 (fileWriteInt32(stream, subtile->state) == -1) return -1; + } + } + } + + k = 0; + for (i = 0; i < gEncounterTablesLength; i++) { + encounter_table = &(gEncounterTables[i]); + + for (j = 0; j < encounter_table->entriesLength; j++) { + encounter_entry = &(encounter_table->entries[j]); + + if (encounter_entry->counter != -1) { + k++; + } + } + } + + if (fileWriteInt32(stream, k) == -1) return -1; + + for (i = 0; i < gEncounterTablesLength; i++) { + encounter_table = &(gEncounterTables[i]); + + for (j = 0; j < encounter_table->entriesLength; j++) { + encounter_entry = &(encounter_table->entries[j]); + + if (encounter_entry->counter != -1) { + if (fileWriteInt32(stream, i) == -1) return -1; + if (fileWriteInt32(stream, j) == -1) return -1; + if (fileWriteInt32(stream, encounter_entry->counter) == -1) return -1; + } + } + } + + return 0; +} + +// 0x4BD28C +int worldmapLoad(File* stream) +{ + int i; + int j; + int k; + int cities_count; + int v38; + int v39; + int v35; + EncounterTable* encounter_table; + EncounterEntry* encounter_entry; + + if (fileReadInt32(stream, &(_Meet_Frank_Horrigan)) == -1) return -1; + if (fileReadInt32(stream, &(_WorldMapCurrArea)) == -1) return -1; + if (fileReadInt32(stream, &(_world_xpos)) == -1) return -1; + if (fileReadInt32(stream, &(_world_ypos)) == -1) return -1; + if (fileReadInt32(stream, &(_wmEncounterIconShow)) == -1) return -1; + if (fileReadInt32(stream, &(_EncounterMapID)) == -1) return -1; + if (fileReadInt32(stream, &(dword_672E50)) == -1) return -1; + if (fileReadInt32(stream, &(dword_672E54)) == -1) return -1; + if (fileReadBool(stream, &(gWorldmapIsInCar)) == -1) return -1; + if (fileReadInt32(stream, &(_carCurrentArea)) == -1) return -1; + if (fileReadInt32(stream, &(gWorldmapCarFuel)) == -1) return -1; + if (fileReadInt32(stream, &(cities_count)) == -1) return -1; + + for (int cityIndex = 0; cityIndex < cities_count; cityIndex++) { + CityInfo* city = &(gCities[cityIndex]); + + if (fileReadInt32(stream, &(city->x)) == -1) return -1; + if (fileReadInt32(stream, &(city->y)) == -1) return -1; + if (fileReadInt32(stream, &(city->state)) == -1) return -1; + if (fileReadInt32(stream, &(city->field_40)) == -1) return -1; + + int entranceCount; + if (fileReadInt32(stream, &(entranceCount)) == -1) { + return -1; + } + + for (int entranceIndex = 0; entranceIndex < entranceCount; entranceIndex++) { + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + + if (fileReadInt32(stream, &(entrance->state)) == -1) { + return -1; + } + } + } + + if (fileReadInt32(stream, &(v39)) == -1) return -1; + if (fileReadInt32(stream, &(v38)) == -1) return -1; + + for (int tileIndex = 0; tileIndex < v39; tileIndex++) { + TileInfo* tile = &(gWorldmapTiles[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 (fileReadInt32(stream, &(subtile->state)) == -1) return -1; + } + } + } + + if (fileReadInt32(stream, &(v35)) == -1) return -1; + + for (i = 0; i < v35; i++) { + if (fileReadInt32(stream, &(j)) == -1) return -1; + encounter_table = &(gEncounterTables[j]); + + if (fileReadInt32(stream, &(k)) == -1) return -1; + encounter_entry = &(encounter_table->entries[k]); + + if (fileReadInt32(stream, &(encounter_entry->counter)) == -1) return -1; + } + + _wmInterfaceCenterOnParty(); + + return 0; +} + +// 0x4BD678 +int _wmWorldMapSaveTempData() +{ + File* stream = fileOpen("worldmap.dat", "wb"); + if (stream == NULL) { + return -1; + } + + int rc = 0; + if (worldmapSave(stream) == -1) { + rc = -1; + } + + fileClose(stream); + + return rc; +} + +// 0x4BD6B4 +int _wmWorldMapLoadTempData() +{ + File* stream = fileOpen("worldmap.dat", "rb"); + if (stream == NULL) { + return -1; + } + + int rc = 0; + if (worldmapLoad(stream) == -1) { + rc = -1; + } + + fileClose(stream); + + return rc; +} + +// 0x4BD6F0 +int worldmapConfigInit() +{ + if (cityInit() == -1) { + return -1; + } + + Config config; + if (!configInit(&config)) { + return -1; + } + + if (configRead(&config, "data\\worldmap.txt", true)) { + for (int index = 0; index < ENCOUNTER_FREQUENCY_TYPE_COUNT; index++) { + if (!configGetInt(&config, "data", gEncounterFrequencyTypeKeys[index], &(_wmFreqValues[index]))) { + break; + } + } + + char* terrainTypes; + configGetString(&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 (!configGetString(&config, section, "lookup_name", &lookupName)) { + break; + } + + if (worldmapConfigLoadEncounterTable(&config, lookupName, section) == -1) { + return -1; + } + } + + if (!configGetInt(&config, "Tile Data", "num_horizontal_tiles", &gWorldmapGridWidth)) { + showMesageBox("\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 (!configGetInt(&config, section, "art_idx", &artIndex)) { + break; + } + + gWorldmapTilesLength++; + + TileInfo* worldmapTiles = internal_realloc(gWorldmapTiles, sizeof(*gWorldmapTiles) * gWorldmapTilesLength); + if (worldmapTiles == NULL) { + showMesageBox("\nwmConfigInit::Error loading tiles!"); + exit(1); + } + + gWorldmapTiles = worldmapTiles; + + TileInfo* tile = &(worldmapTiles[gWorldmapTilesLength - 1]); + + // NOTE: Uninline. + worldmapTileInfoInit(tile); + + tile->fid = buildFid(6, artIndex, 0, 0, 0); + + int encounterDifficulty; + if (configGetInt(&config, section, "encounter_difficulty", &encounterDifficulty)) { + tile->encounterDifficultyModifier = encounterDifficulty; + } + + char* walkMaskName; + if (configGetString(&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 (!configGetString(&config, section, key, &subtileProps)) { + showMesageBox("\nwmConfigInit::Error loading tiles!"); + exit(1); + } + + if (worldmapConfigLoadSubtile(tile, row, column, subtileProps) == -1) { + showMesageBox("\nwmConfigInit::Error loading tiles!"); + exit(1); + } + } + } + } + } + + configFree(&config); + + return 0; +} + +// 0x4BD9F0 +int worldmapConfigLoadEncounterTable(Config* config, char* lookupName, char* sectionKey) +{ + gEncounterTablesLength++; + + EncounterTable* encounterTables = internal_realloc(gEncounterTables, sizeof(EncounterTable) * gEncounterTablesLength); + if (encounterTables == NULL) { + showMesageBox("\nwmConfigInit::Error loading Encounter Table!"); + exit(1); + } + + gEncounterTables = encounterTables; + + EncounterTable* encounterTable = &(encounterTables[gEncounterTablesLength - 1]); + + // NOTE: Uninline. + worldmapEncounterTableInit(encounterTable); + + encounterTable->field_28 = gEncounterTablesLength - 1; + strncpy(encounterTable->lookupName, lookupName, 40); + + char* str; + if (configGetString(config, sectionKey, "maps", &str)) { + while (*str != '\0') { + if (encounterTable->mapsLength >= 6) { + break; + } + + if (strParseStrFromFunc(&str, &(encounterTable->maps[encounterTable->mapsLength]), worldmapFindMapByLookupName) == -1) { + break; + } + + encounterTable->mapsLength++; + } + } + + for (;;) { + char key[40]; + sprintf(key, "enc_%02d", encounterTable->entriesLength); + + char* str; + if (!configGetString(config, sectionKey, key, &str)) { + break; + } + + if (encounterTable->entriesLength >= 40) { + showMesageBox("\nwmConfigInit::Error: Encounter Table: Too many table indexes!!"); + exit(1); + } + + gWorldmapConfig = config; + + if (worldmapConfigLoadEncounterEntry(&(encounterTable->entries[encounterTable->entriesLength]), str) == -1) { + return -1; + } + + encounterTable->entriesLength++; + } + + return 0; +} + +// 0x4BDB64 +int worldmapConfigLoadEncounterEntry(EncounterEntry* entry, char* string) +{ + // NOTE: Uninline. + if (worldmapEncounterTableEntryInit(entry) == -1) { + return -1; + } + + while (string != NULL && *string != '\0') { + strParseIntWithKey(&string, "chance", &(entry->chance), ":"); + strParseIntWithKey(&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), worldmapFindMapByLookupName); + } + } + + 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); + } + } + + worldmapConfigParseCondition(&string, "if", &(entry->condition)); + } + + return 0; +} + +// 0x4BDCA8 +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 +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 +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 +int _wmReadEncBaseType(char* name, int* valuePtr) +{ + char section[40]; + sprintf(section, "Encounter: %s", name); + + char key[40]; + sprintf(key, "type_00"); + + char* string; + if (!configGetString(gWorldmapConfig, section, key, &string)) { + return -1; + } + + _wmMaxEncBaseTypes++; + + ENC_BASE_TYPE* arr = internal_realloc(_wmEncBaseTypeList, sizeof(*_wmEncBaseTypeList) * _wmMaxEncBaseTypes); + if (arr == NULL) { + showMesageBox("\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 (!configGetString(gWorldmapConfig, section, key, &string)) { + int team; + configGetInt(gWorldmapConfig, section, "team_num", &team); + + for (int index = 0; index < entry->field_34; index++) { + ENC_BASE_TYPE_38* ptr = &(entry->field_38[index]); + if ((ptr->pid >> 24) == OBJ_TYPE_CRITTER) { + ptr->team = team; + } + } + + if (configGetString(gWorldmapConfig, section, "position", &string)) { + strParseStrFromList(&string, &(entry->position), gEncounterFormationTypeKeys, ENCOUNTER_FORMATION_TYPE_COUNT); + strParseIntWithKey(&string, "spacing", &(entry->spacing), ":"); + strParseIntWithKey(&string, "distance", &(entry->distance), ":"); + } + + *valuePtr = _wmMaxEncBaseTypes - 1; + + return 0; + } + } + + return -1; +} + +// 0x4BE140 +int _wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** stringPtr) +{ + char* string = *stringPtr; + + // NOTE: Uninline. + if (_wmEncBaseSubTypeSlotInit(ptr) == -1) { + return -1; + } + + if (strParseIntWithKey(&string, "ratio", &(ptr->ratio), ":") == 0) { + ptr->field_2C = 0; + } + + if (strstr(string, "dead,") == string) { + ptr->flags |= ENCOUNTER_SUBINFO_DEAD; + string += 5; + } + + strParseIntWithKey(&string, "pid", &(ptr->pid), ":"); + if (ptr->pid == 0) { + ptr->pid = -1; + } + + strParseIntWithKey(&string, "distance", &(ptr->distance), ":"); + strParseIntWithKey(&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), ":"); + } + + strParseIntWithKey(&string, "script", &(ptr->script), ":"); + worldmapConfigParseCondition(&string, "if", &(ptr->condition)); + + return 0; +} + +// NOTE: Inlined. +// +// 0x4BE2A0 +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 +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 worldmapConfigInitEncounterCondition(&(entry->condition)); +} + +// NOTE: Inlined. +// +// 0x4BE32C +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 +int worldmapEncounterTableEntryInit(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 worldmapConfigInitEncounterCondition(&(entry->condition)); +} + +// NOTE: Inlined. +// +// 0x4BE3B8 +int worldmapEncounterTableInit(EncounterTable* encounterTable) +{ + encounterTable->lookupName[0] = '\0'; + encounterTable->mapsLength = 0; + encounterTable->field_48 = 0; + encounterTable->entriesLength = 0; + + return 0; +} + +// NOTE: Inlined. +// +// 0x4BE3D4 +int worldmapTileInfoInit(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 +int worldmapTerrainInfoInit(Terrain* terrain) +{ + terrain->field_0[0] = '\0'; + terrain->field_28 = 0; + terrain->mapsLength = 0; + + return 0; +} + +// 0x4BE378 +int worldmapConfigInitEncounterCondition(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 +int _wmParseTerrainTypes(Config* config, char* string) +{ + if (*string == '\0') { + return -1; + } + + int terrainCount = 1; + + char* pch = string; + while (*pch != '\0') { + if (*pch == ',') { + terrainCount++; + } + pch++; + } + + gTerrainsLength = terrainCount; + + gTerrains = internal_malloc(sizeof(*gTerrains) * terrainCount); + if (gTerrains == NULL) { + return -1; + } + + for (int index = 0; index < gTerrainsLength; index++) { + Terrain* terrain = &(gTerrains[index]); + + // NOTE: Uninline. + worldmapTerrainInfoInit(terrain); + } + + strlwr(string); + + pch = string; + for (int index = 0; index < gTerrainsLength; index++) { + Terrain* terrain = &(gTerrains[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->field_0, pch, 40); + terrain->field_28 = atoi(pch + delimeterPos + 1); + + pch[delimeterPos] = delimeter; + pch[endPos] = end; + + if (end == ',') { + pch += endPos + 1; + } + } + + for (int index = 0; index < gTerrainsLength; index++) { + _wmParseTerrainRndMaps(config, &(gTerrains[index])); + } + + return 0; +} + +// 0x4BE598 +int _wmParseTerrainRndMaps(Config* config, Terrain* terrain) +{ + char section[40]; + sprintf(section, "Random Maps: %s", terrain->field_0); + + for (;;) { + char key[40]; + sprintf(key, "map_%02d", terrain->mapsLength); + + char* string; + if (!configGetString(config, section, key, &string)) { + break; + } + + if (strParseStrFromFunc(&string, &(terrain->maps[terrain->mapsLength]), worldmapFindMapByLookupName) == -1) { + return -1; + } + + terrain->mapsLength++; + + if (terrain->mapsLength >= 20) { + return -1; + } + } + + return 0; +} + +// 0x4BE61C +int worldmapConfigLoadSubtile(TileInfo* tile, int row, int column, char* string) +{ + SubtileInfo* subtile = &(tile->subtiles[column][row]); + subtile->state = SUBTILE_STATE_UNKNOWN; + + if (strParseStrFromFunc(&string, &(subtile->terrain), worldmapFindTerrainByLookupName) == -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]), gEncounterFrequencyTypeKeys, ENCOUNTER_FREQUENCY_TYPE_COUNT) == -1) { + return -1; + } + } + + if (strParseStrFromFunc(&string, &(subtile->encounterType), worldmapFindEncounterTableByLookupName) == -1) { + return -1; + } + + return 0; +} + +// 0x4BE6D4 +int worldmapFindEncounterTableByLookupName(char* string, int* valuePtr) +{ + for (int index = 0; index < gEncounterTablesLength; index++) { + if (stricmp(string, gEncounterTables[index].lookupName) == 0) { + *valuePtr = index; + return 0; + } + } + + debugPrint("WorldMap Error: Couldn't find match for Encounter Type!"); + + *valuePtr = -1; + + return -1; +} + +// 0x4BE73C +int worldmapFindTerrainByLookupName(char* string, int* valuePtr) +{ + for (int index = 0; index < gTerrainsLength; index++) { + Terrain* terrain = &(gTerrains[index]); + if (stricmp(string, terrain->field_0) == 0) { + *valuePtr = index; + return 0; + } + } + + debugPrint("WorldMap Error: Couldn't find match for Terrain Type!"); + + *valuePtr = -1; + + return -1; +} + +// 0x4BE7A4 +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 +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 +int worldmapConfigParseCondition(char** stringPtr, const char* a2, EncounterCondition* condition) +{ + while (condition->entriesLength < 3) { + EncounterConditionEntry* conditionEntry = &(condition->entries[condition->entriesLength]); + if (worldmapConfigParseConditionEntry(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 +int worldmapConfigParseConditionEntry(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 (worldmapConfigParseEncounterConditionalOperator(&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 (worldmapConfigParseEncounterConditionalOperator(&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 (worldmapConfigParseEncounterConditionalOperator(&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 (worldmapConfigParseEncounterConditionalOperator(&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 (worldmapConfigParseEncounterConditionalOperator(&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 +int worldmapConfigParseEncounterConditionalOperator(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 +int worldmapCityInfoInit(CityInfo* area) +{ + area->name[0] = '\0'; + area->field_28 = -1; + area->x = 0; + area->y = 0; + area->size = CITY_SIZE_LARGE; + area->state = 0; + area->field_3C = 0; + area->field_40 = 0; + area->mapFid = -1; + area->labelFid = -1; + area->entrancesLength = 0; + + return 0; +} + +// 0x4BEF68 +int cityInit() +{ + 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 (configInit(&cfg) == -1) { + return -1; + } + + if (configRead(&cfg, "data\\city.txt", true)) { + area_idx = 0; + do { + sprintf(section, "Area %02d", area_idx); + if (!configGetInt(&cfg, section, "townmap_art_idx", &num)) { + break; + } + + gCitiesLength++; + + cities = internal_realloc(gCities, sizeof(CityInfo) * gCitiesLength); + if (cities == NULL) { + showMesageBox("\nwmConfigInit::Error loading areas!"); + exit(1); + } + + gCities = cities; + + city = &(cities[gCitiesLength - 1]); + + // NOTE: Uninline. + worldmapCityInfoInit(city); + + city->field_28 = area_idx; + + if (num != -1) { + num = buildFid(6, num, 0, 0, 0); + } + + city->mapFid = num; + + if (configGetInt(&cfg, section, "townmap_label_art_idx", &num)) { + if (num != -1) { + num = buildFid(6, num, 0, 0, 0); + } + + city->labelFid = num; + } + + if (!configGetString(&cfg, section, "area_name", &str)) { + showMesageBox("\nwmConfigInit::Error loading areas!"); + exit(1); + } + + strncpy(city->name, str, 40); + + if (!configGetString(&cfg, section, "world_pos", &str)) { + showMesageBox("\nwmConfigInit::Error loading areas!"); + exit(1); + } + + if (strParseInt(&str, &(city->x)) == -1) { + return -1; + } + + if (strParseInt(&str, &(city->y)) == -1) { + return -1; + } + + if (!configGetString(&cfg, section, "start_state", &str)) { + showMesageBox("\nwmConfigInit::Error loading areas!"); + exit(1); + } + + if (strParseStrFromList(&str, &(city->state), _wmStateStrs, 2) == -1) { + return -1; + } + + if (configGetString(&cfg, section, "lock_state", &str)) { + if (strParseStrFromList(&str, &(city->field_3C), _wmStateStrs, 2) == -1) { + return -1; + } + } + + if (!configGetString(&cfg, section, "size", &str)) { + showMesageBox("\nwmConfigInit::Error loading areas!"); + exit(1); + } + + if (strParseStrFromList(&str, &(city->size), gCitySizeKeys, 3) == -1) { + return -1; + } + + while (city->entrancesLength < ENTRANCE_LIST_CAPACITY) { + sprintf(key, "entrance_%d", city->entrancesLength); + + if (!configGetString(&cfg, section, key, &str)) { + break; + } + + entrance = &(city->entrances[city->entrancesLength]); + + // NOTE: Uninline. + worldmapCityEntranceInfoInit(entrance); + + if (strParseStrFromList(&str, &(entrance->state), _wmStateStrs, 2) == -1) { + return -1; + } + + if (strParseInt(&str, &(entrance->x)) == -1) { + return -1; + } + + if (strParseInt(&str, &(entrance->y)) == -1) { + return -1; + } + + if (strParseStrFromFunc(&str, &(entrance->map), &worldmapFindMapByLookupName) == -1) { + return -1; + } + + if (strParseInt(&str, &(entrance->elevation)) == -1) { + return -1; + } + + if (strParseInt(&str, &(entrance->tile)) == -1) { + return -1; + } + + if (strParseInt(&str, &(entrance->rotation)) == -1) { + return -1; + } + + city->entrancesLength++; + } + + area_idx++; + } while (area_idx < 5000); + } + + configFree(&cfg); + + if (gCitiesLength != CITY_COUNT) { + showMesageBox("\nwmAreaInit::Error loading Cities!"); + exit(1); + } + + return 0; +} + +// 0x4BF3E0 +int worldmapFindMapByLookupName(char* string, int* valuePtr) +{ + for (int index = 0; index < gMapsLength; index++) { + MapInfo* map = &(gMaps[index]); + if (stricmp(string, map->lookupName) == 0) { + *valuePtr = index; + return 0; + } + } + + debugPrint("\nWorldMap Error: Couldn't find match for Map Index!"); + + *valuePtr = -1; + return -1; +} + +// NOTE: Inlined. +// +// 0x4BF448 +int worldmapCityEntranceInfoInit(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 +int worldmapMapInfoInit(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 +int _wmMapInit() +{ + char* str; + int num; + MapInfo* maps; + MapInfo* map; + int j; + MapAmbientSoundEffectInfo* sfx; + MapStartPointInfo* rsp; + + Config config; + if (!configInit(&config)) { + return -1; + } + + if (configRead(&config, "data\\maps.txt", true)) { + for (int mapIndex = 0;; mapIndex++) { + char section[40]; + sprintf(section, "Map %03d", mapIndex); + + if (!configGetString(&config, section, "lookup_name", &str)) { + break; + } + + gMapsLength++; + + maps = internal_realloc(gMaps, sizeof(*gMaps) * gMapsLength); + if (maps == NULL) { + showMesageBox("\nwmConfigInit::Error loading maps!"); + exit(1); + } + + gMaps = maps; + + map = &(maps[gMapsLength - 1]); + worldmapMapInfoInit(map); + + strncpy(map->lookupName, str, 40); + + if (!configGetString(&config, section, "map_name", &str)) { + showMesageBox("\nwmConfigInit::Error loading maps!"); + exit(1); + } + + strlwr(str); + strncpy(map->mapFileName, str, 40); + + if (configGetString(&config, section, "music", &str)) { + strncpy(map->music, str, 40); + } + + if (configGetString(&config, section, "ambient_sfx", &str)) { + while (str) { + sfx = &(map->ambientSoundEffects[map->ambientSoundEffectsLength]); + if (strParseKeyValue(&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) { + debugPrint("\nwmMapInit::Error reading ambient sfx. Too many! Str: %s, MapIdx: %d", map->lookupName, mapIndex); + str = NULL; + } + } + } + } + + if (configGetString(&config, section, "saved", &str)) { + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_SAVED; + } else { + map->flags &= ~MAP_SAVED; + } + } + + if (configGetString(&config, section, "dead_bodies_age", &str)) { + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_DEAD_BODIES_AGE; + } else { + map->flags &= ~MAP_DEAD_BODIES_AGE; + } + } + + if (configGetString(&config, section, "can_rest_here", &str)) { + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_CAN_REST_ELEVATION_0; + } else { + map->flags &= ~MAP_CAN_REST_ELEVATION_0; + } + + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_CAN_REST_ELEVATION_1; + } else { + map->flags &= ~MAP_CAN_REST_ELEVATION_1; + } + + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_CAN_REST_ELEVATION_2; + } else { + map->flags &= ~MAP_CAN_REST_ELEVATION_2; + } + } + + if (configGetString(&config, section, "pipbody_active", &str)) { + if (strParseStrFromList(&str, &num, _wmYesNoStrs, 2) == -1) { + return -1; + } + + if (num) { + map->flags |= MAP_PIPBOY_ACTIVE; + } else { + map->flags &= ~MAP_PIPBOY_ACTIVE; + } + } + + if (configGetString(&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. + worldmapRandomStartingPointInit(rsp); + + strParseIntWithKey(&str, "elev", &(rsp->elevation), ":"); + strParseIntWithKey(&str, "tile_num", &(rsp->tile), ":"); + + map->startPointsLength++; + } + + char key[40]; + sprintf(key, "random_start_point_%1d", ++j); + + if (!configGetString(&config, section, key, &str)) { + str = NULL; + } + } + } + } + } + + configFree(&config); + + return 0; +} + +// NOTE: Inlined. +// +// 0x4BF954 +int worldmapRandomStartingPointInit(MapStartPointInfo* rsp) +{ + rsp->elevation = 0; + rsp->tile = -1; + rsp->field_8 = -1; + + return 0; +} + +// 0x4BF96C +int mapGetCount() +{ + return gMapsLength; +} + +// 0x4BF974 +int mapGetFileName(int map, char* dest) +{ + if (map == -1 || map > gMapsLength) { + dest[0] = '\0'; + return -1; + } + + sprintf(dest, "%s.MAP", gMaps[map].mapFileName); + return 0; +} + +// 0x4BF9BC +int mapGetIndexByFileName(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 < gMapsLength; index++) { + if (strcmp(gMaps[index].mapFileName, name) == 0) { + map = index; + break; + } + } + + if (truncated) { + *pch = '.'; + } + + return map; +} + +// 0x4BFA44 +bool _wmMapIdxIsSaveable(int map_index) +{ + return (gMaps[map_index].flags & MAP_SAVED) != 0; +} + +// 0x4BFA64 +bool _wmMapIsSaveable() +{ + return (gMaps[gMapHeader.field_34].flags & MAP_SAVED) != 0; +} + +// 0x4BFA90 +bool _wmMapDeadBodiesAge() +{ + return (gMaps[gMapHeader.field_34].flags & MAP_DEAD_BODIES_AGE) != 0; +} + +// 0x4BFABC +bool _wmMapCanRestHere(int elevation) +{ + int flags[3]; + + // NOTE: I'm not sure why they're copied. + static_assert(sizeof(flags) == sizeof(_can_rest_here), "wrong size"); + memcpy(flags, _can_rest_here, sizeof(flags)); + + MapInfo* map = &(gMaps[gMapHeader.field_34]); + + return (map->flags & flags[elevation]) != 0; +} + +// 0x4BFAFC +bool _wmMapPipboyActive() +{ + return gameMovieIsSeen(MOVIE_VSUIT); +} + +// 0x4BFB08 +int _wmMapMarkVisited(int mapIndex) +{ + if (mapIndex < 0 || mapIndex >= gMapsLength) { + return -1; + } + + MapInfo* map = &(gMaps[mapIndex]); + if ((map->flags & MAP_SAVED) == 0) { + return 0; + } + + int cityIndex; + if (_wmMatchAreaContainingMapIdx(mapIndex, &cityIndex) == -1) { + return -1; + } + + _wmAreaMarkVisitedState(cityIndex, 2); + + return 0; +} + +// 0x4BFB64 +int _wmMatchEntranceFromMap(int cityIndex, int mapIndex, int* entranceIndexPtr) +{ + CityInfo* city = &(gCities[cityIndex]); + + for (int entranceIndex = 0; entranceIndex < city->entrancesLength; entranceIndex++) { + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + + if (mapIndex == entrance->map) { + *entranceIndexPtr = entranceIndex; + return 0; + } + } + + *entranceIndexPtr = -1; + return -1; +} + +// 0x4BFBE8 +int _wmMatchEntranceElevFromMap(int cityIndex, int map, int elevation, int* entranceIndexPtr) +{ + CityInfo* city = &(gCities[cityIndex]); + + for (int entranceIndex = 0; entranceIndex < city->entrancesLength; entranceIndex++) { + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + if (entrance->map == map) { + if (elevation == -1 || entrance->elevation == -1 || elevation == entrance->elevation) { + *entranceIndexPtr = entranceIndex; + return 0; + } + } + } + + *entranceIndexPtr = -1; + return -1; +} + +// 0x4BFC7C +int _wmMatchAreaFromMap(int mapIndex, int* cityIndexPtr) +{ + for (int cityIndex = 0; cityIndex < gCitiesLength; cityIndex++) { + CityInfo* city = &(gCities[cityIndex]); + + for (int entranceIndex = 0; entranceIndex < city->entrancesLength; entranceIndex++) { + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + if (mapIndex == entrance->map) { + *cityIndexPtr = cityIndex; + return 0; + } + } + } + + *cityIndexPtr = -1; + return -1; +} + +// Mark map entrance. +// +// 0x4BFD50 +int _wmMapMarkMapEntranceState(int mapIndex, int elevation, int state) +{ + if (mapIndex < 0 || mapIndex >= gMapsLength) { + return -1; + } + + MapInfo* map = &(gMaps[mapIndex]); + if ((map->flags & MAP_SAVED) == 0) { + return -1; + } + + int cityIndex; + if (_wmMatchAreaContainingMapIdx(mapIndex, &cityIndex) == -1) { + return -1; + } + + int entranceIndex; + if (_wmMatchEntranceElevFromMap(cityIndex, mapIndex, elevation, &entranceIndex) == -1) { + return -1; + } + + CityInfo* city = &(gCities[cityIndex]); + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + entrance->state = state; + + return 0; +} + +// 0x4BFE0C +void _wmWorldMap() +{ + _wmWorldMapFunc(0); +} + +// 0x4BFE10 +int _wmWorldMapFunc(int a1) +{ + if (worldmapWindowInit() == -1) { + worldmapWindowFree(); + return -1; + } + + _wmMatchWorldPosToArea(_world_xpos, _world_ypos, &_WorldMapCurrArea); + + 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; + mouseGetPosition(&mouseX, &mouseY); + + int v4 = gWorldmapOffsetX + mouseX - WM_VIEW_X; + int v5 = gWorldmapOffsetY + mouseY - WM_VIEW_Y; + + if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { + showQuitConfirmationDialog(); + } + + _scriptsCheckGameEvents(NULL, gWorldmapWindow); + + if (_game_user_wants_to_quit != 0) { + break; + } + + int mouseEvent = mouseGetEvent(); + + if (gWorldmapIsTravelling) { + worldmapPerformTravel(); + + if (gWorldmapIsInCar) { + worldmapPerformTravel(); + worldmapPerformTravel(); + worldmapPerformTravel(); + + if (gameGetGlobalVar(GVAR_CAR_BLOWER)) { + worldmapPerformTravel(); + } + + if (gameGetGlobalVar(GVAR_NEW_RENO_CAR_UPGRADE)) { + worldmapPerformTravel(); + } + + if (gameGetGlobalVar(GVAR_NEW_RENO_SUPER_CAR)) { + worldmapPerformTravel(); + worldmapPerformTravel(); + worldmapPerformTravel(); + } + + gWorldmapCarFrmCurrentFrame++; + if (gWorldmapCarFrmCurrentFrame >= artGetFrameCount(gWorldmapCarFrm)) { + gWorldmapCarFrmCurrentFrame = 0; + } + + carConsumeFuel(100); + + if (gWorldmapCarFuel <= 0) { + gWorldmapTravelDestX = 0; + gWorldmapTravelDestY = 0; + gWorldmapIsTravelling = false; + + _wmMatchWorldPosToArea(v4, v5, &_WorldMapCurrArea); + + gWorldmapIsInCar = false; + + if (_WorldMapCurrArea == -1) { + _carCurrentArea = CITY_CAR_OUT_OF_GAS; + + CityInfo* city = &(gCities[CITY_CAR_OUT_OF_GAS]); + + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[city->size]); + int worldmapX = _world_xpos + gWorldmapHotspotUpFrmWidth / 2 + citySizeDescription->width / 2; + int worldmapY = _world_ypos + gWorldmapHotspotUpFrmHeight / 2 + citySizeDescription->height / 2; + worldmapCitySetPos(CITY_CAR_OUT_OF_GAS, worldmapX, worldmapY); + + city->state = 1; + city->field_40 = 1; + + _WorldMapCurrArea = CITY_CAR_OUT_OF_GAS; + } else { + _carCurrentArea = _WorldMapCurrArea; + } + + debugPrint("\nRan outta gas!"); + } + } + + worldmapWindowRefresh(); + + if (getTicksBetween(tick, v24) > 1000) { + if (_partyMemberRestingHeal(3)) { + interfaceRenderHitPoints(false); + v24 = tick; + } + } + + _wmMarkSubTileRadiusVisited(_world_xpos, _world_ypos); + + if (dword_672E28 <= 0) { + gWorldmapIsTravelling = false; + _wmMatchWorldPosToArea(_world_xpos, _world_ypos, &_WorldMapCurrArea); + } + + worldmapWindowRefresh(); + + if (_wmGameTimeIncrement(18000)) { + if (_game_user_wants_to_quit != 0) { + break; + } + } + + if (gWorldmapIsTravelling) { + if (_wmRndEncounterOccurred()) { + if (_EncounterMapID != -1) { + if (gWorldmapIsInCar) { + _wmMatchAreaContainingMapIdx(_EncounterMapID, &_carCurrentArea); + } + mapLoadById(_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 (!gWorldmapIsTravelling && !_wmGenData && abs(_world_xpos - v4) < 5 && abs(_world_ypos - v5) < 5) { + _wmGenData = true; + worldmapWindowRefresh(); + } + } else { + continue; + } + } + + if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { + if (_wmGenData) { + _wmGenData = false; + worldmapWindowRefresh(); + + if (abs(_world_xpos - v4) < 5 && abs(_world_ypos - v5) < 5) { + if (_WorldMapCurrArea != -1) { + CityInfo* city = &(gCities[_WorldMapCurrArea]); + if (city->field_40 == 2 && city->mapFid != -1) { + if (worldmapCityMapViewSelect(&map) == -1) { + v25 = -1; + break; + } + } else { + if (_wmAreaFindFirstValidMap(&map) == -1) { + v25 = -1; + break; + } + + city->field_40 = 2; + } + } else { + map = 0; + } + + if (map != -1) { + if (gWorldmapIsInCar) { + gWorldmapIsInCar = false; + if (_WorldMapCurrArea == -1) { + _wmMatchAreaContainingMapIdx(map, &_carCurrentArea); + } else { + _carCurrentArea = _WorldMapCurrArea; + } + } + mapLoadById(map); + break; + } + } + } else { + if (_mouse_click_in(WM_VIEW_X, WM_VIEW_Y, 472, 465)) { + _wmPartyInitWalking(v4, v5); + } + + _wmGenData = 0; + } + } + + if (_tabsOffset) { + _LastTabsYOffset += _tabsOffset; + worldmapWindowRenderChrome(true); + + if (_tabsOffset > -1) { + if (dword_672F54 <= _LastTabsYOffset) { + _wmInterfaceScrollTabsStop(); + } + } else { + if (dword_672F54 >= _LastTabsYOffset) { + _wmInterfaceScrollTabsStop(); + } + } + } + + if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T) { + if (!gWorldmapIsTravelling && _WorldMapCurrArea != -1) { + CityInfo* city = &(gCities[_WorldMapCurrArea]); + if (city->field_40 == 2 && city->mapFid != -1) { + if (worldmapCityMapViewSelect(&map) == -1) { + rc = -1; + } + + if (map != -1) { + if (gWorldmapIsInCar) { + _wmMatchAreaContainingMapIdx(map, &_carCurrentArea); + } + + mapLoadById(map); + } + } + } + } else if (keyCode == KEY_HOME) { + _wmInterfaceCenterOnParty(); + } else if (keyCode == KEY_ARROW_UP) { + worldmapWindowScroll(20, 20, 0, -1, 0, 1); + } else if (keyCode == KEY_ARROW_LEFT) { + worldmapWindowScroll(20, 20, -1, 0, 0, 1); + } else if (keyCode == KEY_ARROW_DOWN) { + worldmapWindowScroll(20, 20, 0, 1, 0, 1); + } else if (keyCode == KEY_ARROW_RIGHT) { + worldmapWindowScroll(20, 20, 1, 0, 0, 1); + } 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 = _LastTabsYOffset / 27 + (keyCode - KEY_CTRL_F1); + if (quickDestinationIndex < gQuickDestinationsLength) { + int cityIndex = gQuickDestinations[quickDestinationIndex]; + CityInfo* city = &(gCities[cityIndex]); + if (_wmAreaIsKnown(city->field_28)) { + if (_WorldMapCurrArea != cityIndex) { + _wmPartyInitWalking(city->x, city->y); + _wmGenData = 0; + } + } + } + } + + if (map != -1 || v25 == -1) { + break; + } + } + + if (worldmapWindowFree() == -1) { + return -1; + } + + return rc; +} + +// 0x4C056C +int _wmCheckGameAreaEvents() +{ + if (_WorldMapCurrArea == CITY_FAKE_VAULT_13_A) { + if (_WorldMapCurrArea < gCitiesLength) { + gCities[CITY_FAKE_VAULT_13_A].state = 0; + } + + if (gCitiesLength > CITY_FAKE_VAULT_13_B) { + gCities[CITY_FAKE_VAULT_13_B].state = 1; + } + + _wmAreaMarkVisitedState(CITY_FAKE_VAULT_13_B, 2); + } + + return 0; +} + +// 0x4C05C4 +int _wmInterfaceCenterOnParty() +{ + int v0; + int v1; + + v0 = _world_xpos - 203; + if ((v0 & 0x80000000) == 0) { + if (v0 > _wmViewportRightScrlLimit) { + v0 = _wmViewportRightScrlLimit; + } + } else { + v0 = 0; + } + + v1 = _world_ypos - 200; + if ((v1 & 0x80000000) == 0) { + if (v1 > _wmViewportBottomtScrlLimit) { + v1 = _wmViewportBottomtScrlLimit; + } + } else { + v1 = 0; + } + + gWorldmapOffsetX = v0; + gWorldmapOffsetY = v1; + + worldmapWindowRefresh(); + + return 0; +} + +// 0x4C0634 +int _wmRndEncounterOccurred() +{ + unsigned int v0 = _get_time(); + if (getTicksBetween(v0, _wmLastRndTime) < 1500) { + return 0; + } + + _wmLastRndTime = v0; + + if (abs(_old_world_xpos - _world_xpos) < 3) { + return 0; + } + + if (abs(_old_world_ypos - _world_ypos) < 3) { + return 0; + } + + int v26; + _wmMatchWorldPosToArea(_world_xpos, _world_ypos, &v26); + if (v26 != -1) { + return 0; + } + + if (!_Meet_Frank_Horrigan) { + unsigned int gameTime = gameTimeGetTime(); + if (gameTime / GAME_TIME_TICKS_PER_DAY > 35) { + _EncounterMapID = v26; + _Meet_Frank_Horrigan = true; + if (gWorldmapIsInCar) { + _wmMatchAreaContainingMapIdx(MAP_IN_GAME_MOVIE1, &_carCurrentArea); + } + mapLoadById(MAP_IN_GAME_MOVIE1); + return 1; + } + } + + // NOTE: Uninline. + _wmPartyFindCurSubTile(); + + int dayPart; + int gameTimeHour = gameTimeGetHour(); + 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[_world_subtile->encounterChance[dayPart]]; + if (frequency > 0 && frequency < 100) { + int gameDifficulty = GAME_DIFFICULTY_NORMAL; + if (configGetInt(&gGameConfig, 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 = randomBetween(0, 100); + if (chance >= frequency) { + return 0; + } + + _wmRndEncounterPick(); + + int v8 = 1; + _wmEncounterIconShow = 1; + _wmRndCursorFid = 0; + + EncounterTable* encounterTable = &(gEncounterTables[dword_672E50]); + EncounterEntry* encounter = &(encounterTable->entries[dword_672E54]); + if ((encounter->flags & ENCOUNTER_ENTRY_SPECIAL) != 0) { + _wmRndCursorFid = 2; + _wmMatchAreaContainingMapIdx(_EncounterMapID, &v26); + + CityInfo* city = &(gCities[v26]); + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[city->size]); + int worldmapX = _world_xpos + gWorldmapHotspotUpFrmWidth / 2 + citySizeDescription->width / 2; + int worldmapY = _world_ypos + gWorldmapHotspotUpFrmHeight / 2 + citySizeDescription->height / 2; + worldmapCitySetPos(v26, worldmapX, worldmapY); + v8 = 3; + + if (v26 >= 0 && v26 < gCitiesLength) { + CityInfo* city = &(gCities[v26]); + if (city->field_3C != 1) { + city->state = 1; + } + } + } + + // Blinking. + for (int index = 0; index < 7; index++) { + _wmRndCursorFid = v8 - _wmRndCursorFid; + + if (worldmapWindowRefresh() == -1) { + return -1; + } + + coreDelay(200); + } + + if (gWorldmapIsInCar) { + int modifiers[DAY_PART_COUNT]; + + // NOTE: I'm not sure why they're copied. + static_assert(sizeof(modifiers) == sizeof(gDayPartEncounterFrequencyModifiers), "wrong size"); + memcpy(modifiers, gDayPartEncounterFrequencyModifiers, sizeof(gDayPartEncounterFrequencyModifiers)); + + frequency -= modifiers[dayPart]; + } + + bool randomEncounterIsDetected = false; + if (frequency > chance) { + int outdoorsman = partyGetBestSkillValue(SKILL_OUTDOORSMAN); + Object* scanner = objectGetCarriedObjectByPid(gDude, PROTO_ID_MOTION_SENSOR); + if (scanner != NULL) { + if (gDude == scanner->owner) { + outdoorsman += 20; + } + } + + if (outdoorsman > 95) { + outdoorsman = 95; + } + + TileInfo* tile; + // NOTE: Uninline. + _wmFindCurTileFromPos(_world_xpos, _world_ypos, &tile); + debugPrint("\nEncounter Difficulty Mod: %d", tile->encounterDifficultyModifier); + + outdoorsman += tile->encounterDifficultyModifier; + + if (randomBetween(1, 100) < outdoorsman) { + randomEncounterIsDetected = true; + + int xp = 100 - outdoorsman; + if (xp > 0) { + MessageListItem messageListItem; + char* text = getmsg(&gMiscMessageList, &messageListItem, 8500); + if (strlen(text) < 110) { + char formattedText[120]; + sprintf(formattedText, text, xp); + displayMonitorAddMessage(formattedText); + } else { + debugPrint("WorldMap: Error: Rnd Encounter string too long!"); + } + + debugPrint("WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!", xp); + + if (xp < 100) { + pcAddExperience(xp); + } + } + } + } else { + randomEncounterIsDetected = true; + } + + _old_world_xpos = _world_xpos; + _old_world_ypos = _world_ypos; + + if (randomEncounterIsDetected) { + MessageListItem messageListItem; + + const char* title = off_4BC878[0]; + const char* body = off_4BC878[1]; + + title = getmsg(&gWorldmapMessageList, &messageListItem, 2999); + body = getmsg(&gWorldmapMessageList, &messageListItem, 3000 + 50 * dword_672E50 + dword_672E54); + if (showDialogBox(title, &body, 1, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE | DIALOG_BOX_YES_NO) == 0) { + _wmEncounterIconShow = 0; + _EncounterMapID = -1; + dword_672E50 = -1; + dword_672E54 = -1; + return 0; + } + } + + return 1; +} + +// NOTE: Inlined. +// +// 0x4C0BE4 +int _wmPartyFindCurSubTile() +{ + return _wmFindCurSubTileFromPos(_world_xpos, _world_ypos, &_world_subtile); +} + +// 0x4C0C00 +int _wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtile) +{ + int tileIndex = y / WM_TILE_HEIGHT * gWorldmapGridWidth + x / WM_TILE_WIDTH % gWorldmapGridWidth; + TileInfo* tile = &(gWorldmapTiles[tileIndex]); + + int column = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; + int row = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; + *subtile = &(tile->subtiles[column][row]); + + return 0; +} + +// NOTE: Inlined. +// +// 0x4C0CA8 +int _wmFindCurTileFromPos(int x, int y, TileInfo** tile) +{ + int tileIndex = y / WM_TILE_HEIGHT * gWorldmapGridWidth + x / WM_TILE_WIDTH % gWorldmapGridWidth; + *tile = &(gWorldmapTiles[tileIndex]); + + return 0; +} + +// 0x4C0CF4 +int _wmRndEncounterPick() +{ + if (_world_subtile == NULL) { + // NOTE: Uninline. + _wmPartyFindCurSubTile(); + } + + dword_672E50 = _world_subtile->encounterType; + + EncounterTable* encounterTable = &(gEncounterTables[dword_672E50]); + + 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(gDude, STAT_LUCK) - 5; + int v2 = randomBetween(0, totalChance) + v1; + + if (perkHasRank(gDude, PERK_EXPLORER)) { + v2 += 2; + } + + if (perkHasRank(gDude, PERK_RANGER)) { + v2++; + } + + if (perkHasRank(gDude, PERK_SCOUT)) { + v2++; + } + + int gameDifficulty; + if (configGetInt(&gGameConfig, 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; + } + + dword_672E54 = candidates[index]; + + EncounterEntry* encounterTableEntry = &(encounterTable->entries[dword_672E54]); + if (encounterTableEntry->counter > 0) { + encounterTableEntry->counter--; + } + + if (encounterTableEntry->map == -1) { + if (encounterTable->mapsLength <= 0) { + Terrain* terrain = &(gTerrains[_world_subtile->terrain]); + int randomMapIndex = randomBetween(0, terrain->mapsLength - 1); + _EncounterMapID = terrain->maps[randomMapIndex]; + } else { + int randomMapIndex = randomBetween(0, encounterTable->mapsLength - 1); + _EncounterMapID = encounterTable->maps[randomMapIndex]; + } + } else { + _EncounterMapID = encounterTableEntry->map; + } + + return 0; +} + +// wmSetupRandomEncounter +// 0x4C0FA4 +int worldmapSetupRandomEncounter() +{ + MessageListItem messageListItem; + char* msg; + + if (_EncounterMapID == -1) { + return 0; + } + + EncounterTable* encounterTable = &(gEncounterTables[dword_672E50]); + EncounterEntry* encounterTableEntry = &(encounterTable->entries[dword_672E54]); + + // You encounter: + msg = getmsg(&gWorldmapMessageList, &messageListItem, 2998); + displayMonitorAddMessage(msg); + + msg = getmsg(&gWorldmapMessageList, &messageListItem, 3000 + 50 * dword_672E50 + dword_672E54); + displayMonitorAddMessage(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: + debugPrint("\nwmSetupRandomEncounter: Scenery Type: %s", _wmSceneryStrs[encounterTableEntry->scenery]); + configGetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty); + break; + default: + debugPrint("\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 = randomBetween(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 (worldmapSetupCritters(v3->field_8, &v35, v9) == -1) { + scriptsRequestWorldMap(); + 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 = gDude; + + STRUCT_664980 combat; + combat.attacker = v0; + combat.defender = gDude; + combat.actionPointsBonus = 0; + combat.accuracyBonus = 0; + combat.damageBonus = 0; + combat.minDamage = 0; + combat.maxDamage = 500; + combat.field_1C = 0; + + _caiSetupTeamCombat(gDude, v0); + _scripts_request_combat_locked(&combat); + } + } + } + } + } + + v0 = v35; + } + } + + return 0; +} + +// wmSetupCritterObjs +// 0x4C11FC +int worldmapSetupCritters(int type_idx, Object** critterPtr, int critterCount) +{ + if (type_idx == -1) { + return 0; + } + + *critterPtr = 0; + + ENC_BASE_TYPE* v25 = &(_wmEncBaseTypeList[type_idx]); + + debugPrint("\nwmSetupCritterObjs: typeIdx: %d, Formation: %s", type_idx, gEncounterFormationTypeKeys[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) { + debugPrint("\nERROR: wmSetupCritterObjs: wmSetupRndNextTileNum:"); + continue; + } + + if (v5->pid == -1) { + continue; + } + + Object* object; + if (objectCreateWithPid(&object, v5->pid) == -1) { + return -1; + } + + if (*critterPtr == NULL) { + if ((v5->pid >> 24) == OBJ_TYPE_CRITTER) { + *critterPtr = object; + } + } + + if (v5->team != -1) { + if ((object->pid >> 24) == OBJ_TYPE_CRITTER) { + object->data.critter.combat.team = v5->team; + } + } + + if (v5->script != -1) { + if (object->sid != -1) { + scriptRemove(object->sid); + object->sid = -1; + } + + _obj_new_sid_inst(object, SCRIPT_TYPE_CRITTER, v5->script - 1); + } + + if (v25->position != ENCOUNTER_FORMATION_TYPE_SURROUNDING) { + objectSetLocation(object, tile, gElevation, NULL); + } else { + _obj_attempt_placement(object, tile, 0, 0); + } + + int direction = tileGetRotationTo(tile, gDude->tile); + objectSetRotation(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 = randomBetween(v10->minimumQuantity, v10->maximumQuantity); + } + + if (quantity == 0) { + continue; + } + + Object* item; + if (objectCreateWithPid(&item, v10->pid) == -1) { + return -1; + } + + if (v10->pid == PROTO_ID_MONEY) { + if (perkHasRank(gDude, PERK_FORTUNE_FINDER)) { + quantity *= 2; + } + } + + if (itemAdd(object, item, quantity) == -1) { + return -1; + } + + _obj_disconnect(item, NULL); + + if (v10->isEquipped) { + if (_inven_wield(object, item, 1) == -1) { + debugPrint("\nERROR: wmSetupCritterObjs: Inven Wield Failed: %d on %s: Critter Fid: %d", item->pid, critterGetName(object), object->fid); + } + } + } + } + } + + return 0; +} + +// 0x4C155C +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] = gDude->tile; + _wmRndTileDirs[0] = randomBetween(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 = &(gMaps[gMapHeader.field_34]); + if (map->startPointsLength != 0) { + int rspIndex = randomBetween(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] = gDude->tile; + _wmRndCenterTiles[1] = gDude->tile; + } + + _wmRndTileDirs[0] = tileGetRotationTo(_wmRndCenterTiles[0], gDude->tile); + _wmRndTileDirs[1] = tileGetRotationTo(_wmRndCenterTiles[1], gDude->tile); + + _wmRndOriginalCenterTile = _wmRndCenterTiles[0]; + + return 0; + } + default: + debugPrint("\nERROR: wmSetupCritterObjs: invalid Formation Type!"); + + return -1; + } +} + +// wmSetupRndNextTileNum +// 0x4C16F0 +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 = randomBetween(-2, 2); + + distance += critterGetStat(gDude, STAT_PERCEPTION); + + if (perkHasRank(gDude, PERK_CAUTIOUS_NATURE)) { + distance += 3; + } + } + + if (distance < 0) { + distance = 0; + } + + int origin = a2->tile; + if (origin == -1) { + origin = tileGetTileInDirection(gDude->tile, _wmRndTileDirs[0], distance); + } + + if (++_wmRndTileDirs[0] >= ROTATION_COUNT) { + _wmRndTileDirs[0] = 0; + } + + int randomizedDistance = randomBetween(0, distance / 2); + int randomizedRotation = randomBetween(0, ROTATION_COUNT - 1); + tile_num = tileGetTileInDirection(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 = tileGetTileInDirection(_wmRndCenterTiles[_wmRndIndex], rotation, a1->spacing); + int v13 = tileGetTileInDirection(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 = tileGetTileInDirection(_wmRndCenterTiles[_wmRndIndex], rotation, a1->spacing); + int v17 = tileGetTileInDirection(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 = tileGetTileInDirection(_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 = tileGetTileInDirection(_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 = tileGetTileInDirection(_wmRndCenterTiles[0], _wmRndTileDirs[0], a1->spacing); + _wmRndCenterTiles[0] = tile_num; + } + break; + default: + assert(false && "Should be unreachable"); + } + + ++attempt; + ++_wmRndCallCount; + + if (_wmEvalTileNumForPlacement(tile_num)) { + break; + } + + debugPrint("\nWARNING: EVAL-TILE-NUM FAILED!"); + + if (tileDistanceBetween(_wmRndOriginalCenterTile, _wmRndCenterTiles[_wmRndIndex]) > 25) { + return -1; + } + + if (attempt > 25) { + return -1; + } + } + + debugPrint("\nwmSetupRndNextTileNum:TileNum: %d", tile_num); + + *out_tile_num = tile_num; + + return 0; +} + +// 0x4C1A64 +bool _wmEvalTileNumForPlacement(int tile) +{ + if (_obj_blocking_at(gDude, tile, gElevation) != NULL) { + return false; + } + + if (pathfinderFindPath(gDude, gDude->tile, tile, NULL, 0, _obj_shoot_blocking_at) == 0) { + return false; + } + + return true; +} + +// 0x4C1AC8 +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 = gameGetGlobalVar(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 = randomBetween(0, 100); + if (value > ptr->param) { + matches = false; + } + break; + case ENCOUNTER_CONDITION_TYPE_PLAYER: + value = pcGetStat(PC_STAT_LEVEL); + if (!_wmEvalSubConditional(value, ptr->conditionalOperator, ptr->value)) { + matches = false; + } + break; + case ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED: + value = gameTimeGetTime(); + if (!_wmEvalSubConditional(value / GAME_TIME_TICKS_PER_DAY, ptr->conditionalOperator, ptr->value)) { + matches = false; + } + break; + case ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY: + value = gameTimeGetHour(); + 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 +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 +bool _wmGameTimeIncrement(int a1) +{ + if (a1 == 0) { + return false; + } + + while (a1 != 0) { + unsigned int gameTime = gameTimeGetTime(); + unsigned int nextEventTime = queueGetNextEventTime(); + int v1 = nextEventTime >= gameTime ? a1 : nextEventTime - gameTime; + a1 -= v1; + + gameTimeAddTicks(v1); + + int hour = gameTimeGetHour() / 100; + + int frameCount = artGetFrameCount(gWorldmapDialFrm); + int frame = (hour + 12) % frameCount; + if (gWorldmapDialFrmCurrentFrame != frame) { + gWorldmapDialFrmCurrentFrame = frame; + worldmapWindowRenderDial(true); + } + + worldmapWindowRenderDate(true); + + if (queueProcessEvents()) { + break; + } + } + + return true; +} + +// Reads .msk file if needed. +// +// 0x4C1CE8 +int _wmGrabTileWalkMask(int tile) +{ + TileInfo* tileInfo = &(gWorldmapTiles[tile]); + if (tileInfo->walkMaskData != NULL) { + return 0; + } + + if (*tileInfo->walkMaskName == '\0') { + return 0; + } + + tileInfo->walkMaskData = internal_malloc(13200); + if (tileInfo->walkMaskData == NULL) { + return -1; + } + + char path[MAX_PATH]; + sprintf(path, "data\\%s.msk", tileInfo->walkMaskName); + + File* stream = fileOpen(path, "rb"); + if (stream == NULL) { + return -1; + } + + int rc = 0; + + if (fileReadUInt8List(stream, tileInfo->walkMaskData, 13200) == -1) { + rc = -1; + } + + fileClose(stream); + + return rc; +} + +// 0x4C1D9C +bool _wmWorldPosInvalid(int a1, int a2) +{ + int v3 = a2 / WM_TILE_HEIGHT * gWorldmapGridWidth + a1 / WM_TILE_WIDTH % gWorldmapGridWidth; + if (_wmGrabTileWalkMask(v3) == -1) { + return false; + } + + TileInfo* tileDescription = &(gWorldmapTiles[v3]); + 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 = (a2 % WM_TILE_HEIGHT) * 44 + (a1 % WM_TILE_WIDTH) / 8; + int bit = 1 << (((a1 % WM_TILE_WIDTH) / 8) & 3); + return (mask[pos] & bit) != 0; +} + +// 0x4C1E54 +void _wmPartyInitWalking(int x, int y) +{ + gWorldmapTravelDestX = x; + gWorldmapTravelDestY = y; + _WorldMapCurrArea = -1; + gWorldmapIsTravelling = true; + + int dx = abs(x - _world_xpos); + int dy = abs(y - _world_ypos); + + if (dx < dy) { + dword_672E28 = dy; + dword_672E30 = 2 * dx; + _x_line_inc = 0; + dword_672E2C = 2 * dx - dy; + dword_672E34 = 2 * (dx - dy); + dword_672E3C = 1; + _y_line_inc = 1; + dword_672E44 = 1; + } else { + dword_672E28 = dx; + dword_672E30 = 2 * dy; + _y_line_inc = 0; + dword_672E2C = 2 * dy - dx; + dword_672E34 = 2 * (dy - dx); + _x_line_inc = 1; + dword_672E3C = 1; + dword_672E44 = 1; + } + + if (gWorldmapTravelDestX < _world_xpos) { + dword_672E3C = -dword_672E3C; + _x_line_inc = -_x_line_inc; + } + + if (gWorldmapTravelDestY < _world_ypos) { + dword_672E44 = -dword_672E44; + _y_line_inc = -_y_line_inc; + } + + if (!_wmCursorIsVisible()) { + _wmInterfaceCenterOnParty(); + } +} + +// 0x4C1F90 +void worldmapPerformTravel() +{ + if (dword_672E28 <= 0) { + return; + } + + _terrainCounter++; + if (_terrainCounter > 4) { + _terrainCounter = 1; + } + + // NOTE: Uninline. + _wmPartyFindCurSubTile(); + + Terrain* terrain = &(gTerrains[_world_subtile->terrain]); + int v1 = terrain->field_28 - perkGetRank(gDude, PERK_PATHFINDER); + if (v1 < 1) { + v1 = 1; + } + + if (_terrainCounter / v1 >= 1) { + int v3; + int v4; + if (dword_672E2C >= 0) { + if (_wmWorldPosInvalid(dword_672E3C + _world_xpos, dword_672E44 + _world_ypos)) { + gWorldmapTravelDestX = 0; + gWorldmapTravelDestY = 0; + gWorldmapIsTravelling = false; + _wmMatchWorldPosToArea(_world_xpos, _world_xpos, &_WorldMapCurrArea); + dword_672E28 = 0; + return; + } + + v3 = dword_672E3C; + dword_672E2C += dword_672E34; + _world_xpos += dword_672E3C; + v4 = dword_672E44; + _world_ypos += dword_672E44; + } else { + if (_wmWorldPosInvalid(_x_line_inc + _world_xpos, _y_line_inc + _world_ypos) == 1) { + gWorldmapTravelDestX = 0; + gWorldmapTravelDestY = 0; + gWorldmapIsTravelling = false; + _wmMatchWorldPosToArea(_world_xpos, _world_xpos, &_WorldMapCurrArea); + dword_672E28 = 0; + return; + } + + v3 = _x_line_inc; + dword_672E2C += dword_672E30; + _world_ypos += _y_line_inc; + v4 = _y_line_inc; + _world_xpos += _x_line_inc; + } + + worldmapWindowScroll(1, 1, v3, v4, NULL, false); + + dword_672E28 -= 1; + if (dword_672E28 == 0) { + gWorldmapTravelDestY = 0; + gWorldmapIsTravelling = false; + gWorldmapTravelDestX = 0; + } + } +} + +// 0x4C219C +void _wmInterfaceScrollTabsStart(int a1) +{ + int i; + int v3; + + for (i = 0; i < 7; i++) { + buttonDisable(_wmTownMapSubButtonIds[i]); + } + + dword_672F54 = _LastTabsYOffset; + + v3 = _LastTabsYOffset + 7 * a1; + + if (a1 >= 0) { + if (gWorldmapTownTabsUnderlayFrmHeight - 230 <= dword_672F54) { + goto L11; + } else { + dword_672F54 = _LastTabsYOffset + 7 * a1; + if (v3 > gWorldmapTownTabsUnderlayFrmHeight - 230) { + } + } + } else { + if (_LastTabsYOffset <= 0) { + goto L11; + } else { + dword_672F54 = _LastTabsYOffset + 7 * a1; + if (v3 < 0) { + dword_672F54 = 0; + } + } + } + + _tabsOffset = a1; + +L11: + + if (!_tabsOffset) { + return; + } + + _LastTabsYOffset += _tabsOffset; + + worldmapWindowRenderChrome(true); + + if (_tabsOffset > -1) { + if (dword_672F54 > _LastTabsYOffset) { + return; + } + } else if (dword_672F54 < _LastTabsYOffset) { + return; + } + + _wmInterfaceScrollTabsStop(); +} + +// 0x4C2270 +void _wmInterfaceScrollTabsStop() +{ + int i; + + _tabsOffset = 0; + + for (i = 0; i < 7; i++) { + buttonEnable(_wmTownMapSubButtonIds[i]); + } +} + +// 0x4C2324 +int worldmapWindowInit() +{ + int fid; + Art* frm; + CacheEntry* frmHandle; + + _wmLastRndTime = _get_time(); + _fontnum = fontGetCurrent(); + fontSetCurrent(0); + + _map_save_in_game(true); + + const char* backgroundSoundFileName = gWorldmapIsInCar ? "20car" : "23world"; + _gsound_background_play_level_music(backgroundSoundFileName, 12); + + indicatorBarHide(); + isoDisable(); + colorCycleDisable(); + gameMouseSetCursor(MOUSE_CURSOR_ARROW); + + gWorldmapWindow = windowCreate(0, 0, WM_WINDOW_WIDTH, WM_WINDOW_HEIGHT, _colorTable[0], WINDOW_FLAG_0x04); + if (gWorldmapWindow == -1) { + return -1; + } + + fid = buildFid(6, 136, 0, 0, 0); + frm = artLock(fid, &gWorldmapBoxFrmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapBoxFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapBoxFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(gWorldmapBoxFrmHandle); + gWorldmapBoxFrmHandle = INVALID_CACHE_ENTRY; + + fid = buildFid(6, 136, 0, 0, 0); + gWorldmapBoxFrmData = artLockFrameData(fid, 0, 0, &gWorldmapBoxFrmHandle); + if (gWorldmapBoxFrmData == NULL) { + return -1; + } + + gWorldmapWindowBuffer = windowGetBuffer(gWorldmapWindow); + if (gWorldmapWindowBuffer == NULL) { + return -1; + } + + blitBufferToBuffer(gWorldmapBoxFrmData, gWorldmapBoxFrmWidth, gWorldmapBoxFrmHeight, gWorldmapBoxFrmWidth, gWorldmapWindowBuffer, WM_WINDOW_WIDTH); + + for (int citySize = 0; citySize < CITY_SIZE_COUNT; citySize++) { + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[citySize]); + + fid = buildFid(6, 336 + citySize, 0, 0, 0); + citySizeDescription->fid = fid; + + frm = artLock(fid, &(citySizeDescription->handle)); + if (frm == NULL) { + return -1; + } + + citySizeDescription->width = artGetWidth(frm, 0, 0); + citySizeDescription->height = artGetHeight(frm, 0, 0); + + artUnlock(citySizeDescription->handle); + citySizeDescription->handle = INVALID_CACHE_ENTRY; + + citySizeDescription->data = artLockFrameData(fid, 0, 0, &(citySizeDescription->handle)); + // FIXME: check is obviously wrong, should be citySizeDescription->data. + if (frm == NULL) { + return -1; + } + } + + fid = buildFid(6, 168, 0, 0, 0); + frm = artLock(fid, &gWorldmapHotspotUpFrmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapHotspotUpFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapHotspotUpFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(gWorldmapHotspotUpFrmHandle); + gWorldmapHotspotUpFrmHandle = INVALID_CACHE_ENTRY; + + // hotspot1.frm - town map selector shape #1 + fid = buildFid(6, 168, 0, 0, 0); + gWorldmapHotspotUpFrmData = artLockFrameData(fid, 0, 0, &gWorldmapHotspotUpFrmHandle); + + // hotspot2.frm - town map selector shape #2 + fid = buildFid(6, 223, 0, 0, 0); + gWorldmapHotspotDownFrmData = artLockFrameData(fid, 0, 0, &gWorldmapHotspotDownFrmHandle); + if (gWorldmapHotspotDownFrmData == NULL) { + return -1; + } + + // wmaptarg.frm - world map move target maker #1 + fid = buildFid(6, 139, 0, 0, 0); + frm = artLock(fid, &gWorldmapDestinationMarkerFrmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapDestinationMarkerFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapDestinationMarkerFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(gWorldmapDestinationMarkerFrmHandle); + gWorldmapDestinationMarkerFrmHandle = INVALID_CACHE_ENTRY; + + // wmaploc.frm - world map location marker + fid = buildFid(6, 138, 0, 0, 0); + frm = artLock(fid, &gWorldmapLocationMarkerFrmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapLocationMarkerFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapLocationMarkerFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(gWorldmapLocationMarkerFrmHandle); + gWorldmapLocationMarkerFrmHandle = INVALID_CACHE_ENTRY; + + // wmaptarg.frm - world map move target maker #1 + fid = buildFid(6, 139, 0, 0, 0); + gWorldmapDestinationMarkerFrmData = artLockFrameData(fid, 0, 0, &gWorldmapDestinationMarkerFrmHandle); + if (gWorldmapDestinationMarkerFrmData == NULL) { + return -1; + } + + // wmaploc.frm - world map location marker + fid = buildFid(6, 138, 0, 0, 0); + gWorldmapLocationMarkerFrmData = artLockFrameData(fid, 0, 0, &gWorldmapLocationMarkerFrmHandle); + if (gWorldmapLocationMarkerFrmData == NULL) { + return -1; + } + + for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) { + fid = buildFid(6, gWorldmapEncounterFrmIds[index], 0, 0, 0); + frm = artLock(fid, &(gWorldmapEncounterFrmHandles[index])); + if (frm == NULL) { + return -1; + } + + gWorldmapEncounterFrmWidths[index] = artGetWidth(frm, 0, 0); + gWorldmapEncounterFrmHeights[index] = artGetHeight(frm, 0, 0); + + artUnlock(gWorldmapEncounterFrmHandles[index]); + + gWorldmapEncounterFrmHandles[index] = INVALID_CACHE_ENTRY; + gWorldmapEncounterFrmData[index] = artLockFrameData(fid, 0, 0, &(gWorldmapEncounterFrmHandles[index])); + } + + for (int index = 0; index < gWorldmapTilesLength; index++) { + gWorldmapTiles[index].handle = INVALID_CACHE_ENTRY; + } + + // wmtabs.frm - worldmap town tabs underlay + fid = buildFid(6, 364, 0, 0, 0); + frm = artLock(fid, &frmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapTownTabsUnderlayFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapTownTabsUnderlayFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(frmHandle); + + gWorldmapTownTabsUnderlayFrmData = artLockFrameData(fid, 0, 0, &gWorldmapTownTabsUnderlayFrmHandle) + gWorldmapTownTabsUnderlayFrmWidth * 27; + if (gWorldmapTownTabsUnderlayFrmData == NULL) { + return -1; + } + + // wmtbedge.frm - worldmap town tabs edging overlay + fid = buildFid(6, 367, 0, 0, 0); + gWorldmapTownTabsEdgeFrmData = artLockFrameData(fid, 0, 0, &gWorldmapTownTabsEdgeFrmHandle); + if (gWorldmapTownTabsEdgeFrmData == NULL) { + return -1; + } + + // wmdial.frm - worldmap night/day dial + fid = buildFid(6, 365, 0, 0, 0); + gWorldmapDialFrm = artLock(fid, &gWorldmapDialFrmHandle); + if (gWorldmapDialFrm == NULL) { + return -1; + } + + gWorldmapDialFrmWidth = artGetWidth(gWorldmapDialFrm, 0, 0); + gWorldmapDialFrmHeight = artGetHeight(gWorldmapDialFrm, 0, 0); + + // wmscreen - worldmap overlay screen + fid = buildFid(6, 363, 0, 0, 0); + frm = artLock(fid, &frmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapCarOverlayFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapCarOverlayFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(frmHandle); + + gWorldmapCarOverlayFrmData = artLockFrameData(fid, 0, 0, &gWorldmapCarOverlayFrmHandle); + if (gWorldmapCarOverlayFrmData == NULL) { + return -1; + } + + // wmglobe.frm - worldmap globe stamp overlay + fid = buildFid(6, 366, 0, 0, 0); + frm = artLock(fid, &frmHandle); + if (frm == NULL) { + return -1; + } + + gWorldmapGlobeOverlayFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapGloveOverlayFrmHeight = artGetHeight(frm, 0, 0); + + artUnlock(frmHandle); + + gWorldmapGlobeOverlayFrmData = artLockFrameData(fid, 0, 0, &gWorldmapGlobeOverlayFrmHandle); + if (gWorldmapGlobeOverlayFrmData == NULL) { + return -1; + } + + // lilredup.frm - little red button up + fid = buildFid(6, 8, 0, 0, 0); + frm = artLock(fid, &frmHandle); + if (frm == NULL) { + return -1; + } + + int littleRedButtonUpWidth = artGetWidth(frm, 0, 0); + int littleRedButtonUpHeight = artGetHeight(frm, 0, 0); + + artUnlock(frmHandle); + + gWorldmapLittleRedButtonUpFrmData = artLockFrameData(fid, 0, 0, &gWorldmapLittleRedButtonUpFrmHandle); + + // lilreddn.frm - little red button down + fid = buildFid(6, 9, 0, 0, 0); + gWorldmapLittleRedButtonDownFrmData = artLockFrameData(fid, 0, 0, &gWorldmapLittleRedButtonDownFrmHandle); + + // months.frm - month strings for pip boy + fid = buildFid(6, 129, 0, 0, 0); + gWorldmapMonthsFrm = artLock(fid, &gWorldmapMonthsFrmHandle); + if (gWorldmapMonthsFrm == NULL) { + return -1; + } + + // numbers.frm - numbers for the hit points and fatigue counters + fid = buildFid(6, 82, 0, 0, 0); + gWorldmapNumbersFrm = artLock(fid, &gWorldmapNumbersFrmHandle); + if (gWorldmapNumbersFrm == NULL) { + return -1; + } + + // create town/world switch button + buttonCreate(gWorldmapWindow, + WM_TOWN_WORLD_SWITCH_X, + WM_TOWN_WORLD_SWITCH_Y, + littleRedButtonUpWidth, + littleRedButtonUpHeight, + -1, + -1, + -1, + KEY_UPPERCASE_T, + gWorldmapLittleRedButtonUpFrmData, + gWorldmapLittleRedButtonDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + + for (int index = 0; index < 7; index++) { + _wmTownMapSubButtonIds[index] = buttonCreate(gWorldmapWindow, + 508, + 138 + 27 * index, + littleRedButtonUpWidth, + littleRedButtonUpHeight, + -1, + -1, + -1, + KEY_CTRL_F1 + index, + gWorldmapLittleRedButtonUpFrmData, + gWorldmapLittleRedButtonDownFrmData, + 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 = buildFid(6, 200 - index, 0, 0, 0); + frm = artLock(fid, &(gWorldmapTownListScrollUpFrmHandle[index])); + if (frm == NULL) { + return -1; + } + + gWorldmapTownListScrollUpFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapTownListScrollUpFrmHeight = artGetHeight(frm, 0, 0); + gWorldmapTownListScrollUpFrmData[index] = artGetFrameData(frm, 0, 0); + } + + for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { + // 182 - dnarwon.frm - character editor + // 181 - dnarwoff.frm - character editor + fid = buildFid(6, 182 - index, 0, 0, 0); + frm = artLock(fid, &(gWorldmapTownListScrollDownFrmHandle[index])); + if (frm == NULL) { + return -1; + } + + gWorldmapTownListScrollDownFrmWidth = artGetWidth(frm, 0, 0); + gWorldmapTownListScrollDownFrmHeight = artGetHeight(frm, 0, 0); + gWorldmapTownListScrollDownFrmData[index] = artGetFrameData(frm, 0, 0); + } + + // Scroll up button. + buttonCreate(gWorldmapWindow, + WM_TOWN_LIST_SCROLL_UP_X, + WM_TOWN_LIST_SCROLL_UP_Y, + gWorldmapTownListScrollUpFrmWidth, + gWorldmapTownListScrollUpFrmHeight, + -1, + -1, + -1, + KEY_CTRL_ARROW_UP, + gWorldmapTownListScrollUpFrmData[WORLDMAP_ARROW_FRM_NORMAL], + gWorldmapTownListScrollUpFrmData[WORLDMAP_ARROW_FRM_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + + // Scroll down button. + buttonCreate(gWorldmapWindow, + WM_TOWN_LIST_SCROLL_DOWN_X, + WM_TOWN_LIST_SCROLL_DOWN_Y, + gWorldmapTownListScrollDownFrmWidth, + gWorldmapTownListScrollDownFrmHeight, + -1, + -1, + -1, + KEY_CTRL_ARROW_DOWN, + gWorldmapTownListScrollDownFrmData[WORLDMAP_ARROW_FRM_NORMAL], + gWorldmapTownListScrollDownFrmData[WORLDMAP_ARROW_FRM_PRESSED], + NULL, + BUTTON_FLAG_TRANSPARENT); + + if (gWorldmapIsInCar) { + // wmcarmve.frm - worldmap car movie + fid = buildFid(6, 433, 0, 0, 0); + gWorldmapCarFrm = artLock(fid, &gWorldmapCarFrmHandle); + if (gWorldmapCarFrm == NULL) { + return -1; + } + + gWorldmapCarFrmWidth = artGetWidth(gWorldmapCarFrm, 0, 0); + gWorldmapCarFrmHeight = artGetHeight(gWorldmapCarFrm, 0, 0); + } + + tickersAdd(worldmapWindowHandleMouseScrolling); + + if (_wmMakeTabsLabelList(&gQuickDestinations, &gQuickDestinationsLength) == -1) { + return -1; + } + + _wmInterfaceWasInitialized = 1; + + if (worldmapWindowRefresh() == -1) { + return -1; + } + + windowRefresh(gWorldmapWindow); + scriptsDisable(); + _scr_remove_all(); + + return 0; +} + +// 0x4C2E44 +int worldmapWindowFree() +{ + int i; + TileInfo* tile; + + tickersRemove(worldmapWindowHandleMouseScrolling); + + if (gWorldmapBoxFrmData != NULL) { + artUnlock(gWorldmapBoxFrmHandle); + gWorldmapBoxFrmData = NULL; + } + gWorldmapBoxFrmHandle = INVALID_CACHE_ENTRY; + + if (gWorldmapWindow != -1) { + windowDestroy(gWorldmapWindow); + gWorldmapWindow = -1; + } + + if (gWorldmapHotspotUpFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapHotspotUpFrmHandle); + } + gWorldmapHotspotUpFrmData = NULL; + + if (gWorldmapHotspotDownFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapHotspotDownFrmHandle); + } + gWorldmapHotspotDownFrmData = NULL; + + if (gWorldmapDestinationMarkerFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapDestinationMarkerFrmHandle); + } + gWorldmapDestinationMarkerFrmData = NULL; + + if (gWorldmapLocationMarkerFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapLocationMarkerFrmHandle); + } + gWorldmapLocationMarkerFrmData = NULL; + + for (i = 0; i < 4; i++) { + if (gWorldmapEncounterFrmHandles[i] != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapEncounterFrmHandles[i]); + } + gWorldmapEncounterFrmData[i] = NULL; + } + + for (i = 0; i < CITY_SIZE_COUNT; i++) { + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[i]); + // FIXME: probably unsafe code, no check for -1 + artUnlock(citySizeDescription->handle); + citySizeDescription->handle = INVALID_CACHE_ENTRY; + citySizeDescription->data = NULL; + } + + for (i = 0; i < gWorldmapTilesLength; i++) { + tile = &(gWorldmapTiles[i]); + if (tile->handle != INVALID_CACHE_ENTRY) { + artUnlock(tile->handle); + tile->handle = INVALID_CACHE_ENTRY; + tile->data = NULL; + + if (tile->walkMaskData != NULL) { + internal_free(tile->walkMaskData); + tile->walkMaskData = NULL; + } + } + } + + if (gWorldmapTownTabsUnderlayFrmData != NULL) { + artUnlock(gWorldmapTownTabsUnderlayFrmHandle); + gWorldmapTownTabsUnderlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsUnderlayFrmData = NULL; + } + + if (gWorldmapTownTabsEdgeFrmData != NULL) { + artUnlock(gWorldmapTownTabsEdgeFrmHandle); + gWorldmapTownTabsEdgeFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapTownTabsEdgeFrmData = NULL; + } + + if (gWorldmapDialFrm != NULL) { + artUnlock(gWorldmapDialFrmHandle); + gWorldmapDialFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapDialFrm = NULL; + } + + if (gWorldmapCarOverlayFrmData != NULL) { + artUnlock(gWorldmapCarOverlayFrmHandle); + gWorldmapCarOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCarOverlayFrmData = NULL; + } + if (gWorldmapGlobeOverlayFrmData != NULL) { + artUnlock(gWorldmapGlobeOverlayFrmHandle); + gWorldmapGlobeOverlayFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapGlobeOverlayFrmData = NULL; + } + + if (gWorldmapLittleRedButtonUpFrmData != NULL) { + artUnlock(gWorldmapLittleRedButtonUpFrmHandle); + gWorldmapLittleRedButtonUpFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonUpFrmData = NULL; + } + + if (gWorldmapLittleRedButtonDownFrmData != NULL) { + artUnlock(gWorldmapLittleRedButtonDownFrmHandle); + gWorldmapLittleRedButtonDownFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapLittleRedButtonDownFrmData = NULL; + } + + for (i = 0; i < 2; i++) { + artUnlock(gWorldmapTownListScrollUpFrmHandle[i]); + gWorldmapTownListScrollUpFrmHandle[i] = INVALID_CACHE_ENTRY; + gWorldmapTownListScrollUpFrmData[i] = NULL; + + artUnlock(gWorldmapTownListScrollDownFrmHandle[i]); + gWorldmapTownListScrollDownFrmHandle[i] = INVALID_CACHE_ENTRY; + gWorldmapTownListScrollDownFrmData[i] = NULL; + } + + gWorldmapTownListScrollUpFrmHeight = 0; + gWorldmapTownListScrollDownFrmWidth = 0; + gWorldmapTownListScrollDownFrmHeight = 0; + gWorldmapTownListScrollUpFrmWidth = 0; + + if (gWorldmapMonthsFrm != NULL) { + artUnlock(gWorldmapMonthsFrmHandle); + gWorldmapMonthsFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapMonthsFrm = NULL; + } + + if (gWorldmapNumbersFrm != NULL) { + artUnlock(gWorldmapNumbersFrmHandle); + gWorldmapNumbersFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapNumbersFrm = NULL; + } + + if (gWorldmapCarFrm != NULL) { + artUnlock(gWorldmapCarFrmHandle); + gWorldmapCarFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCarFrm = NULL; + + gWorldmapCarFrmWidth = 0; + gWorldmapCarFrmHeight = 0; + } + + _wmEncounterIconShow = 0; + _EncounterMapID = -1; + dword_672E50 = -1; + dword_672E54 = -1; + + indicatorBarShow(); + isoEnable(); + colorCycleEnable(); + + fontSetCurrent(_fontnum); + + if (gQuickDestinations != NULL) { + internal_free(gQuickDestinations); + gQuickDestinations = NULL; + } + + gQuickDestinationsLength = 0; + + _wmInterfaceWasInitialized = 0; + + scriptsEnable(); + + return 0; +} + +// 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 +int worldmapWindowScroll(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh) +{ + int v6 = gWorldmapOffsetY; + int v7 = gWorldmapOffsetX; + + 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 < _wmViewportBottomtScrlLimit) { + v6 += stepY; + if (v6 > _wmViewportBottomtScrlLimit) { + v6 = _wmViewportBottomtScrlLimit; + } + } 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 < _wmViewportRightScrlLimit) { + v7 += stepX; + if (v7 > _wmViewportRightScrlLimit) { + v7 = _wmViewportRightScrlLimit; + } + } else { + if (success != NULL) { + *success = false; + } + } + } + + gWorldmapOffsetY = v6; + gWorldmapOffsetX = v7; + + if (shouldRefresh) { + if (worldmapWindowRefresh() == -1) { + return -1; + } + } + + return 0; +} + +// 0x4C32EC +void worldmapWindowHandleMouseScrolling() +{ + int x; + int y; + mouseGetPosition(&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 = gameMouseGetCursor(); + 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 (getTicksBetween(tick, _lastTime_2) > 50) { + _lastTime_2 = _get_bk_time(); + worldmapWindowScroll(20, 20, dx, dy, &_couldScroll, true); + } + + if (!_couldScroll) { + newMouseCursor += 8; + } + } else { + if (oldMouseCursor != MOUSE_CURSOR_ARROW) { + newMouseCursor = MOUSE_CURSOR_ARROW; + } + } + + if (oldMouseCursor != newMouseCursor) { + gameMouseSetCursor(newMouseCursor); + } +} + +// 0x4C3434 +int _wmMarkSubTileOffsetVisitedFunc(int a1, int a2, int a3, int a4, int a5, int a6) +{ + int v7; + int v8; + int v9; + int* v; + + v7 = a2 + a4; + v8 = a1; + v9 = a3 + a5; + + if (v7 >= 0) { + if (v7 >= 7) { + if (a1 % gWorldmapGridWidth == gWorldmapGridWidth - 1) { + return -1; + } + + v8 = a1 + 1; + v7 %= 7; + } + } else { + if (!(a1 % gWorldmapGridWidth)) { + return -1; + } + + v7 += 7; + v8 = a1 - 1; + } + + if (v9 >= 0) { + if (v9 >= 6) { + if (v8 > gWorldmapTilesLength - gWorldmapGridWidth - 1) { + return -1; + } + + v8 += gWorldmapGridWidth; + v9 %= 6; + } + } else { + if (v8 < gWorldmapGridWidth) { + return -1; + } + + v9 += 6; + v8 -= gWorldmapGridWidth; + } + + TileInfo* tile = &(gWorldmapTiles[v8]); + SubtileInfo* subtile = &(tile->subtiles[v9][v7]); + v = &(subtile->state); + if (a6 != 1 || *v == 0) { + *v = a6; + } + + return 0; +} + +// 0x4C3550 +void _wmMarkSubTileRadiusVisited(int x, int y) +{ + int radius = 1; + + if (perkHasRank(gDude, PERK_SCOUT)) { + radius = 2; + } + + _wmSubTileMarkRadiusVisited(x, y, radius); +} + +// Mark worldmap tile as visible?/visited? +// +// 0x4C35A8 +int _wmSubTileMarkRadiusVisited(int x, int y, int radius) +{ + int v4, v5; + + int tile = x / WM_TILE_WIDTH % gWorldmapGridWidth + y / WM_TILE_HEIGHT * gWorldmapGridWidth; + v4 = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; + v5 = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; + + for (int i = -radius; i <= radius; i++) { + for (int v6 = -radius; v6 <= radius; v6++) { + _wmMarkSubTileOffsetVisitedFunc(tile, v4, v5, v6, i, SUBTILE_STATE_KNOWN); + } + } + + SubtileInfo* subtile = &(gWorldmapTiles[tile].subtiles[v5][v4]); + subtile->state = SUBTILE_STATE_VISITED; + + switch (subtile->field_4) { + case 2: + while (v5-- > 0) { + _wmMarkSubTileOffsetVisitedFunc(tile, v4, 0, v5, 0, SUBTILE_STATE_VISITED); + } + break; + case 4: + while (v4-- > -1) { + _wmMarkSubTileOffsetVisitedFunc(tile, v4, 0, v5, 0, SUBTILE_STATE_VISITED); + } + + if (tile % gWorldmapGridWidth > 0) { + for (int i = 0; i < 7; i++) { + _wmMarkSubTileOffsetVisitedFunc(tile - 1, i + 1, v5, 0, 0, SUBTILE_STATE_VISITED); + } + } + break; + } + + return 0; +} + +// 0x4C3740 +int _wmSubTileGetVisitedState(int x, int y, int* a3) +{ + TileInfo* tile; + SubtileInfo* ptr; + + tile = &(gWorldmapTiles[y / WM_TILE_HEIGHT * gWorldmapGridWidth + x / WM_TILE_WIDTH % gWorldmapGridWidth]); + ptr = &(tile->subtiles[y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE][x % WM_TILE_WIDTH / WM_SUBTILE_SIZE]); + *a3 = ptr->state; + + return 0; +} + +// Load tile art if needed. +// +// 0x4C37EC +int _wmTileGrabArt(int tile_index) +{ + TileInfo* tile = &(gWorldmapTiles[tile_index]); + if (tile->data != NULL) { + return 0; + } + + tile->data = artLockFrameData(tile->fid, 0, 0, &(tile->handle)); + if (tile->data != NULL) { + return 0; + } + + worldmapWindowFree(); + + return -1; +} + +// 0x4C3830 +int worldmapWindowRefresh() +{ + if (_wmInterfaceWasInitialized != 1) { + return 0; + } + + int v17 = gWorldmapOffsetX % WM_TILE_WIDTH; + int v18 = gWorldmapOffsetY % 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 = gWorldmapOffsetY / WM_TILE_HEIGHT * gWorldmapGridWidth + gWorldmapOffsetX / WM_TILE_WIDTH % gWorldmapGridWidth; + 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 = &(gWorldmapTiles[v0]); + blitBufferToBuffer(tileInfo->data + srcX, + width, + height, + WM_TILE_WIDTH, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * (y + WM_VIEW_Y) + WM_VIEW_X + x, + WM_WINDOW_WIDTH); + v0++; + + x += width; + v23++; + } + + v0 += gWorldmapGridWidth - v23; + y += height; + } + + // Render cities. + for (int index = 0; index < gCitiesLength; index++) { + CityInfo* cityInfo = &(gCities[index]); + if (cityInfo->state != CITY_STATE_UNKNOWN) { + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[cityInfo->size]); + int cityX = cityInfo->x - gWorldmapOffsetX; + int cityY = cityInfo->y - gWorldmapOffsetY; + if (cityX >= 0 && cityX <= 472 - citySizeDescription->width + && cityY >= 0 && cityY <= 465 - citySizeDescription->height) { + worldmapWindowRenderCity(cityInfo, citySizeDescription, gWorldmapWindowBuffer, cityX, cityY); + } + } + } + + // Hide unknown subtiles, dim unvisited. + int v25 = gWorldmapOffsetX / WM_TILE_WIDTH % gWorldmapGridWidth + gWorldmapOffsetY / WM_TILE_HEIGHT * gWorldmapGridWidth; + 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 = &(gWorldmapTiles[v25]); + worldmapWindowDimSubtile(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 += gWorldmapGridWidth - v24; + v30 += v29; + } + + _wmDrawCursorStopped(); + + worldmapWindowRenderChrome(true); + + return 0; +} + +// 0x4C3C9C +void worldmapWindowRenderDate(bool shouldRefreshWindow) +{ + int month; + int day; + int year; + gameTimeGetDate(&month, &day, &year); + + month--; + + unsigned char* dest = gWorldmapWindowBuffer; + + int numbersFrmWidth = artGetWidth(gWorldmapNumbersFrm, 0, 0); + int numbersFrmHeight = artGetHeight(gWorldmapNumbersFrm, 0, 0); + unsigned char* numbersFrmData = artGetFrameData(gWorldmapNumbersFrm, 0, 0); + + dest += WM_WINDOW_WIDTH * 12 + 487; + blitBufferToBuffer(numbersFrmData + 9 * (day / 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); + blitBufferToBuffer(numbersFrmData + 9 * (day % 10), 9, numbersFrmHeight, numbersFrmWidth, dest + 9, WM_WINDOW_WIDTH); + + int monthsFrmWidth = artGetWidth(gWorldmapMonthsFrm, 0, 0); + unsigned char* monthsFrmData = artGetFrameData(gWorldmapMonthsFrm, 0, 0); + blitBufferToBuffer(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; + blitBufferToBuffer(numbersFrmData + 9 * (year % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); + year /= 10; + } + + int gameTimeHour = gameTimeGetHour(); + dest += 72; + for (int index = 0; index < 4; index++) { + blitBufferToBuffer(numbersFrmData + 9 * (gameTimeHour % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); + dest -= 9; + gameTimeHour /= 10; + } + + if (shouldRefreshWindow) { + Rect rect; + rect.left = 487; + rect.top = 12; + rect.bottom = numbersFrmHeight + 12; + rect.right = 630; + windowRefreshRect(gWorldmapWindow, &rect); + } +} + +// 0x4C3F00 +int _wmMatchWorldPosToArea(int a1, int a2, int* a3) +{ + int v3 = a2 + WM_VIEW_Y; + int v4 = a1 + WM_VIEW_X; + + int index; + for (index = 0; index < gCitiesLength; index++) { + CityInfo* city = &(gCities[index]); + if (city->state) { + if (v4 >= city->x && v3 >= city->y) { + CitySizeDescription* citySizeDescription = &(gCitySizeDescriptions[city->size]); + if (v4 <= city->x + citySizeDescription->width && v3 <= city->y + citySizeDescription->height) { + break; + } + } + } + } + + if (index == gCitiesLength) { + *a3 = -1; + } else { + *a3 = 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 +int worldmapWindowRenderCity(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y) +{ + _dark_translucent_trans_buf_to_buf(citySizeDescription->data, + citySizeDescription->width, + citySizeDescription->height, + citySizeDescription->width, + dest, + x, + y, + WM_WINDOW_WIDTH, + 0x10000, + _circleBlendTable, + _commonGrayTable); + + int nameY = y + citySizeDescription->height + 1; + int maxY = 464 - fontGetLineHeight(); + if (nameY < maxY) { + MessageListItem messageListItem; + const char* name; + if (_wmAreaIsKnown(city->field_28)) { + name = getmsg(&gMapMessageList, &messageListItem, 1500 + city->field_28); + } else { + name = getmsg(&gWorldmapMessageList, &messageListItem, 1004); + } + + char text[40]; + strncpy(text, name, 40); + + int width = fontGetStringWidth(text); + fontDrawText(dest + WM_WINDOW_WIDTH * nameY + x + citySizeDescription->width / 2 - width / 2, text, 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 +void worldmapWindowDimRect(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; + unsigned int index = (byte << 8) + 75; + *dest++ = _intensityColorTable[index]; + } + dest += skipY; + } +} + +// 0x4C40E4 +int worldmapWindowDimSubtile(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 = gWorldmapWindowBuffer + WM_WINDOW_WIDTH * destY + destX; + switch (subtileInfo->state) { + case SUBTILE_STATE_UNKNOWN: + bufferFill(dest, width, height, WM_WINDOW_WIDTH, _colorTable[0]); + break; + case SUBTILE_STATE_KNOWN: + worldmapWindowDimRect(dest, width, height, WM_WINDOW_WIDTH); + break; + } + } + + return 0; +} + +// 0x4C41EC +int _wmDrawCursorStopped() +{ + unsigned char* src; + int width; + int height; + + if (gWorldmapTravelDestX >= 1 || gWorldmapTravelDestY >= 1) { + + if (_wmEncounterIconShow == 1) { + src = gWorldmapEncounterFrmData[_wmRndCursorFid]; + width = gWorldmapEncounterFrmWidths[_wmRndCursorFid]; + height = gWorldmapEncounterFrmHeights[_wmRndCursorFid]; + } else { + src = gWorldmapLocationMarkerFrmData; + width = gWorldmapLocationMarkerFrmWidth; + height = gWorldmapLocationMarkerFrmHeight; + } + + if (_world_xpos >= gWorldmapOffsetX && _world_xpos < gWorldmapOffsetX + WM_VIEW_WIDTH + && _world_ypos >= gWorldmapOffsetY && _world_ypos < gWorldmapOffsetY + WM_VIEW_HEIGHT) { + blitBufferToBufferTrans(src, width, height, width, gWorldmapWindowBuffer + WM_WINDOW_WIDTH * (WM_VIEW_Y - gWorldmapOffsetY + _world_ypos - height / 2) + WM_VIEW_X - gWorldmapOffsetX + _world_xpos - width / 2, WM_WINDOW_WIDTH); + } + + if (gWorldmapTravelDestX >= gWorldmapOffsetX && gWorldmapTravelDestX < gWorldmapOffsetX + WM_VIEW_WIDTH + && gWorldmapTravelDestY >= gWorldmapOffsetY && gWorldmapTravelDestY < gWorldmapOffsetY + WM_VIEW_HEIGHT) { + blitBufferToBufferTrans(gWorldmapDestinationMarkerFrmData, gWorldmapDestinationMarkerFrmWidth, gWorldmapDestinationMarkerFrmHeight, gWorldmapDestinationMarkerFrmWidth, gWorldmapWindowBuffer + WM_WINDOW_WIDTH * (WM_VIEW_Y - gWorldmapOffsetY + gWorldmapTravelDestY - gWorldmapDestinationMarkerFrmHeight / 2) + WM_VIEW_X - gWorldmapOffsetX + gWorldmapTravelDestX - gWorldmapDestinationMarkerFrmWidth / 2, WM_WINDOW_WIDTH); + } + } else { + if (_wmEncounterIconShow == 1) { + src = gWorldmapEncounterFrmData[_wmRndCursorFid]; + width = gWorldmapEncounterFrmWidths[_wmRndCursorFid]; + height = gWorldmapEncounterFrmHeights[_wmRndCursorFid]; + } else { + src = _wmGenData ? gWorldmapHotspotDownFrmData : gWorldmapHotspotUpFrmData; + width = gWorldmapHotspotUpFrmWidth; + height = gWorldmapHotspotUpFrmHeight; + } + + if (_world_xpos >= gWorldmapOffsetX && _world_xpos < gWorldmapOffsetX + WM_VIEW_WIDTH + && _world_ypos >= gWorldmapOffsetY && _world_ypos < gWorldmapOffsetY + WM_VIEW_HEIGHT) { + blitBufferToBufferTrans(src, width, height, width, gWorldmapWindowBuffer + WM_WINDOW_WIDTH * (WM_VIEW_Y - gWorldmapOffsetY + _world_ypos - height / 2) + WM_VIEW_X - gWorldmapOffsetX + _world_xpos - width / 2, WM_WINDOW_WIDTH); + } + } + + return 0; +} + +// 0x4C4490 +bool _wmCursorIsVisible() +{ + return _world_xpos >= gWorldmapOffsetX + && _world_ypos >= gWorldmapOffsetY + && _world_xpos < gWorldmapOffsetX + WM_VIEW_WIDTH + && _world_ypos < gWorldmapOffsetY + WM_VIEW_HEIGHT; +} + +// Copy city short name. +// +// 0x4C450C +int _wmGetAreaIdxName(int index, char* name) +{ + MessageListItem messageListItem; + + getmsg(&gMapMessageList, &messageListItem, 1500 + index); + strncpy(name, messageListItem.text, 40); + + return 0; +} + +// Returns true if world area is known. +// +// 0x4C453C +bool _wmAreaIsKnown(int cityIndex) +{ + if (!cityIsValid(cityIndex)) { + return false; + } + + CityInfo* city = &(gCities[cityIndex]); + if (city->field_40) { + if (city->state == CITY_STATE_KNOWN) { + return true; + } + } + + return false; +} + +// 0x4C457C +int _wmAreaVisitedState(int area) +{ + if (!cityIsValid(area)) { + return 0; + } + + CityInfo* city = &(gCities[area]); + if (city->field_40 && city->state == 1) { + return city->field_40; + } + + return 0; +} + +// 0x4C45BC +bool _wmMapIsKnown(int mapIndex) +{ + int cityIndex; + if (_wmMatchAreaFromMap(mapIndex, &cityIndex) != 0) { + return false; + } + + int entranceIndex; + if (_wmMatchEntranceFromMap(cityIndex, mapIndex, &entranceIndex) != 0) { + return false; + } + + CityInfo* city = &(gCities[cityIndex]); + EntranceInfo* entrance = &(city->entrances[entranceIndex]); + + if (entrance->state != 1) { + return false; + } + + return true; +} + +// 0x4C4634 +bool _wmAreaMarkVisitedState(int cityIndex, int a2) +{ + if (!cityIsValid(cityIndex)) { + return false; + } + + CityInfo* city = &(gCities[cityIndex]); + int v5 = city->field_40; + if (city->state == 1 && a2 != 0) { + _wmMarkSubTileRadiusVisited(city->x, city->y); + } + + city->field_40 = a2; + + SubtileInfo* subtile; + if (_wmFindCurSubTileFromPos(city->x, city->y, &subtile) == -1) { + return false; + } + + if (a2 == 1) { + subtile->state = SUBTILE_STATE_KNOWN; + } else if (a2 == 2 && v5 == 0) { + city->field_40 = 1; + } + + return true; +} + +// 0x4C46CC +bool _wmAreaSetVisibleState(int cityIndex, int a2, int a3) +{ + if (!cityIsValid(cityIndex)) { + return false; + } + + CityInfo* city = &(gCities[cityIndex]); + if (city->field_3C != 1 || a3) { + city->state = a2; + return true; + } + + return false; +} + +// wm_area_set_pos +// 0x4C4710 +int worldmapCitySetPos(int cityIndex, int x, int y) +{ + if (!cityIsValid(cityIndex)) { + return -1; + } + + if (x < 0 || x >= WM_TILE_WIDTH * gWorldmapGridWidth) { + return -1; + } + + if (y < 0 || y >= WM_TILE_HEIGHT * (gWorldmapTilesLength / gWorldmapGridWidth)) { + return -1; + } + + CityInfo* city = &(gCities[cityIndex]); + city->x = x; + city->y = y; + + return 0; +} + +// Returns current town x/y. +// +// 0x4C47A4 +int _wmGetPartyWorldPos(int* out_x, int* out_y) +{ + if (out_x != NULL) { + *out_x = _world_xpos; + } + + if (out_y != NULL) { + *out_y = _world_ypos; + } + + return 0; +} + +// Returns current town. +// +// 0x4C47C0 +int _wmGetPartyCurArea(int* a1) +{ + if (a1) { + *a1 = _WorldMapCurrArea; + return 0; + } + + return -1; +} + +// 0x4C47D8 +void _wmMarkAllSubTiles(int a1) +{ + for (int tileIndex = 0; tileIndex < gWorldmapTilesLength; tileIndex++) { + TileInfo* tile = &(gWorldmapTiles[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 = a1; + } + } + } +} + +// 0x4C4850 +void _wmTownMap() +{ + _wmWorldMapFunc(1); +} + +// 0x4C485C +int worldmapCityMapViewSelect(int* mapIndexPtr) +{ + *mapIndexPtr = -1; + + if (worldmapCityMapViewInit() == -1) { + worldmapCityMapViewFree(); + return -1; + } + + if (_WorldMapCurrArea == -1) { + return -1; + } + + CityInfo* city = &(gCities[_WorldMapCurrArea]); + + for (;;) { + int keyCode = _get_input(); + if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { + showQuitConfirmationDialog(); + } + + 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]); + + *mapIndexPtr = entrance->map; + + mapSetEnteringLocation(entrance->elevation, entrance->tile, entrance->rotation); + + break; + } + + if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) { + int v10 = _LastTabsYOffset / 27 + keyCode - KEY_CTRL_F1; + if (v10 < gQuickDestinationsLength) { + int v11 = gQuickDestinations[v10]; + CityInfo* v12 = &(gCities[v11]); + if (!_wmAreaIsKnown(v12->field_28)) { + break; + } + + if (v11 != _WorldMapCurrArea) { + _wmPartyInitWalking(v12->x, v12->y); + + _wmGenData = 0; + + break; + } + } + } else { + if (keyCode == KEY_CTRL_ARROW_UP) { + _wmInterfaceScrollTabsStart(-27); + } else if (keyCode == KEY_CTRL_ARROW_DOWN) { + _wmInterfaceScrollTabsStart(27); + } else if (keyCode == 2069) { + if (worldmapCityMapViewRefresh() == -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 (worldmapCityMapViewFree() == -1) { + return -1; + } + + return 0; +} + +// 0x4C4A6C +int worldmapCityMapViewInit() +{ + _wmTownMapCurArea = _WorldMapCurrArea; + + CityInfo* city = &(gCities[_WorldMapCurrArea]); + + Art* mapFrm = artLock(city->mapFid, &gWorldmapCityMapFrmHandle); + if (mapFrm == NULL) { + return -1; + } + + gWorldmapCityMapFrmWidth = artGetWidth(mapFrm, 0, 0); + gWorldmapCityMapFrmHeight = artGetHeight(mapFrm, 0, 0); + + artUnlock(gWorldmapCityMapFrmHandle); + gWorldmapCityMapFrmHandle = INVALID_CACHE_ENTRY; + + gWorldmapCityMapFrmData = artLockFrameData(city->mapFid, 0, 0, &gWorldmapCityMapFrmHandle); + if (gWorldmapCityMapFrmData == 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] = buttonCreate(gWorldmapWindow, + entrance->x, + entrance->y, + gWorldmapHotspotUpFrmWidth, + gWorldmapHotspotUpFrmHeight, + -1, + 2069, + -1, + KEY_1 + index, + gWorldmapHotspotUpFrmData, + gWorldmapHotspotDownFrmData, + NULL, + BUTTON_FLAG_TRANSPARENT); + + if (_wmTownMapButtonId[index] == -1) { + return -1; + } + } + + tickersRemove(worldmapWindowHandleMouseScrolling); + + if (worldmapCityMapViewRefresh() == -1) { + return -1; + } + + return 0; +} + +// 0x4C4BD0 +int worldmapCityMapViewRefresh() +{ + blitBufferToBuffer(gWorldmapCityMapFrmData, + gWorldmapCityMapFrmWidth, + gWorldmapCityMapFrmHeight, + gWorldmapCityMapFrmWidth, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * WM_VIEW_Y + WM_VIEW_X, + WM_WINDOW_WIDTH); + + worldmapWindowRenderChrome(false); + + CityInfo* city = &(gCities[_WorldMapCurrArea]); + + 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 (messageListGetItem(&gWorldmapMessageList, &messageListItem)) { + if (messageListItem.text != NULL) { + int width = fontGetStringWidth(messageListItem.text); + windowDrawText(gWorldmapWindow, messageListItem.text, width, gWorldmapHotspotUpFrmWidth / 2 + entrance->x - width / 2, gWorldmapHotspotUpFrmHeight + entrance->y + 2, _colorTable[992] | 0x2010000); + } + } + } + + windowRefresh(gWorldmapWindow); + + return 0; +} + +// 0x4C4D00 +int worldmapCityMapViewFree() +{ + if (gWorldmapCityMapFrmHandle != INVALID_CACHE_ENTRY) { + artUnlock(gWorldmapCityMapFrmHandle); + gWorldmapCityMapFrmHandle = INVALID_CACHE_ENTRY; + gWorldmapCityMapFrmData = NULL; + gWorldmapCityMapFrmWidth = 0; + gWorldmapCityMapFrmHeight = 0; + } + + if (_wmTownMapCurArea != -1) { + CityInfo* city = &(gCities[_wmTownMapCurArea]); + for (int index = 0; index < city->entrancesLength; index++) { + if (_wmTownMapButtonId[index] != -1) { + buttonDestroy(_wmTownMapButtonId[index]); + _wmTownMapButtonId[index] = -1; + } + } + } + + if (worldmapWindowRefresh() == -1) { + return -1; + } + + tickersAdd(worldmapWindowHandleMouseScrolling); + + return 0; +} + +// 0x4C4DA4 +int carConsumeFuel(int amount) +{ + if (gameGetGlobalVar(GVAR_NEW_RENO_SUPER_CAR) != 0) { + amount -= amount * 90 / 100; + } + + if (gameGetGlobalVar(GVAR_NEW_RENO_CAR_UPGRADE) != 0) { + amount -= amount * 10 / 100; + } + + if (gameGetGlobalVar(GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR) != 0) { + amount /= 2; + } + + gWorldmapCarFuel -= amount; + + if (gWorldmapCarFuel < 0) { + gWorldmapCarFuel = 0; + } + + return 0; +} + +// Returns amount of fuel that does not fit into tank. +// +// 0x4C4E34 +int carAddFuel(int amount) +{ + if ((amount + gWorldmapCarFuel) <= CAR_FUEL_MAX) { + gWorldmapCarFuel += amount; + return 0; + } + + int remaining = CAR_FUEL_MAX - gWorldmapCarFuel; + + gWorldmapCarFuel = CAR_FUEL_MAX; + + return remaining; +} + +// 0x4C4E74 +int carGetFuel() +{ + return gWorldmapCarFuel; +} + +// 0x4C4E7C +bool carIsEmpty() +{ + return gWorldmapCarFuel <= 0; +} + +// 0x4C4E8C +int carGetCity() +{ + return _carCurrentArea; +} + +// 0x4C4E94 +int _wmCarGiveToParty() +{ + MessageListItem messageListItem; + static_assert(sizeof(messageListItem) == sizeof(stru_4BC880), "wrong size"); + memcpy(&messageListItem, &stru_4BC880, sizeof(MessageListItem)); + + if (gWorldmapCarFuel <= 0) { + // The car is out of power. + char* msg = getmsg(&gWorldmapMessageList, &messageListItem, 1502); + displayMonitorAddMessage(msg); + return -1; + } + + gWorldmapIsInCar = true; + + MapTransition transition; + memset(&transition, 0, sizeof(transition)); + + transition.map = -2; + mapSetTransition(&transition); + + CityInfo* city = &(gCities[CITY_CAR_OUT_OF_GAS]); + city->state = 0; + city->field_40 = 0; + + return 0; +} + +// 0x4C4F28 +int ambientSoundEffectGetLength() +{ + int mapIndex = mapGetCurrentMap(); + if (mapIndex < 0 || mapIndex >= gMapsLength) { + return -1; + } + + MapInfo* map = &(gMaps[mapIndex]); + return map->ambientSoundEffectsLength; +} + +// 0x4C4F5C +int ambientSoundEffectGetRandom() +{ + int mapIndex = mapGetCurrentMap(); + if (mapIndex < 0 || mapIndex >= gMapsLength) { + return -1; + } + + MapInfo* map = &(gMaps[mapIndex]); + + int totalChances = 0; + for (int index = 0; index < map->ambientSoundEffectsLength; index++) { + MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]); + totalChances += sfx->chance; + } + + int chance = randomBetween(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 ambientSoundEffectGetName(int ambientSoundEffectIndex, char** namePtr) +{ + if (namePtr == NULL) { + return -1; + } + + *namePtr = NULL; + + int mapIndex = mapGetCurrentMap(); + if (mapIndex < 0 || mapIndex >= gMapsLength) { + return -1; + } + + MapInfo* map = &(gMaps[mapIndex]); + if (ambientSoundEffectIndex < 0 || ambientSoundEffectIndex >= map->ambientSoundEffectsLength) { + return -1; + } + + MapAmbientSoundEffectInfo* ambientSoundEffectInfo = &(map->ambientSoundEffects[ambientSoundEffectIndex]); + *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 = gameTimeGetHour(); + 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 +int worldmapWindowRenderChrome(bool shouldRefreshWindow) +{ + blitBufferToBufferTrans(gWorldmapBoxFrmData, + gWorldmapBoxFrmWidth, + gWorldmapBoxFrmHeight, + gWorldmapBoxFrmWidth, + gWorldmapWindowBuffer, + WM_WINDOW_WIDTH); + + worldmapRenderQuickDestinations(); + + int v1 = gameTimeGetHour(); + v1 /= 100; + + int frameCount = artGetFrameCount(gWorldmapDialFrm); + int newFrame = (v1 + 12) % frameCount; + if (gWorldmapDialFrmCurrentFrame != newFrame) { + gWorldmapDialFrmCurrentFrame = newFrame; + worldmapWindowRenderDial(false); + } + + worldmapWindowRenderDial(false); + + if (gWorldmapIsInCar) { + unsigned char* data = artGetFrameData(gWorldmapCarFrm, gWorldmapCarFrmCurrentFrame, 0); + if (data == NULL) { + return -1; + } + + blitBufferToBuffer(data, + gWorldmapCarFrmWidth, + gWorldmapCarFrmHeight, + gWorldmapCarFrmWidth, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * WM_WINDOW_CAR_Y + WM_WINDOW_CAR_X, + WM_WINDOW_WIDTH); + + blitBufferToBufferTrans(gWorldmapCarOverlayFrmData, + gWorldmapCarOverlayFrmWidth, + gWorldmapCarOverlayFrmHeight, + gWorldmapCarOverlayFrmWidth, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * WM_WINDOW_CAR_OVERLAY_Y + WM_WINDOW_CAR_OVERLAY_X, + WM_WINDOW_WIDTH); + + worldmapWindowRenderCarFuelBar(); + } else { + blitBufferToBufferTrans(gWorldmapGlobeOverlayFrmData, + gWorldmapGlobeOverlayFrmWidth, + gWorldmapGloveOverlayFrmHeight, + gWorldmapGlobeOverlayFrmWidth, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * WM_WINDOW_GLOBE_OVERLAY_Y + WM_WINDOW_GLOBE_OVERLAY_X, + WM_WINDOW_WIDTH); + } + + worldmapWindowRenderDate(false); + + if (shouldRefreshWindow) { + windowRefresh(gWorldmapWindow); + } + + return 0; +} + +// 0x4C5244 +void worldmapWindowRenderCarFuelBar() +{ + int ratio = (WM_WINDOW_CAR_FUEL_BAR_HEIGHT * gWorldmapCarFuel) / CAR_FUEL_MAX; + if ((ratio & 1) != 0) { + ratio -= 1; + } + + unsigned char* dest = gWorldmapWindowBuffer + 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 +int worldmapRenderQuickDestinations() +{ + 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; + + blitBufferToBufferTrans(gWorldmapTownTabsUnderlayFrmData + gWorldmapTownTabsUnderlayFrmWidth * _LastTabsYOffset + 9, 119, 178, gWorldmapTownTabsUnderlayFrmWidth, gWorldmapWindowBuffer + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); + + v30 = gWorldmapWindowBuffer + WM_WINDOW_WIDTH * 138 + 530; + v0 = gWorldmapWindowBuffer + WM_WINDOW_WIDTH * 138 + 530 - WM_WINDOW_WIDTH * (_LastTabsYOffset % 27); + v31 = _LastTabsYOffset / 27; + + if (v31 < gQuickDestinationsLength) { + city = &(gCities[gQuickDestinations[v31]]); + if (city->labelFid != -1) { + art = artLock(city->labelFid, &cache_entry); + if (art == NULL) { + return -1; + } + + width = artGetWidth(art, 0, 0); + height = artGetHeight(art, 0, 0); + buf = artGetFrameData(art, 0, 0); + if (buf == NULL) { + return -1; + } + + v10 = height - _LastTabsYOffset % 27; + v11 = buf + width * (_LastTabsYOffset % 27); + + v12 = v0; + if (v0 < v30 - WM_WINDOW_WIDTH) { + v12 = v30 - WM_WINDOW_WIDTH; + } + + blitBufferToBuffer(v11, width, v10, width, v12, WM_WINDOW_WIDTH); + artUnlock(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 < gQuickDestinationsLength) { + city = &(gCities[gQuickDestinations[v14]]); + if (city->labelFid != -1) { + art = artLock(city->labelFid, &cache_entry); + if (art == NULL) { + return -1; + } + + width = artGetWidth(art, 0, 0); + height = artGetHeight(art, 0, 0); + buf = artGetFrameData(art, 0, 0); + if (buf == NULL) { + return -1; + } + + blitBufferToBuffer(buf, width, height, width, v13, WM_WINDOW_WIDTH); + artUnlock(cache_entry); + + cache_entry = INVALID_CACHE_ENTRY; + } + } + v13 += WM_WINDOW_WIDTH * 27; + } + + if (v31 + 6 < gQuickDestinationsLength) { + city = &(gCities[gQuickDestinations[v31 + 6]]); + if (city->labelFid != -1) { + art = artLock(city->labelFid, &cache_entry); + if (art == NULL) { + return -1; + } + + width = artGetWidth(art, 0, 0); + height = artGetHeight(art, 0, 0); + buf = artGetFrameData(art, 0, 0); + if (buf == NULL) { + return -1; + } + + blitBufferToBuffer(buf, width, height, width, v13, WM_WINDOW_WIDTH); + artUnlock(cache_entry); + + cache_entry = INVALID_CACHE_ENTRY; + } + } + + blitBufferToBufferTrans(gWorldmapTownTabsEdgeFrmData, 119, 178, 119, gWorldmapWindowBuffer + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); + + return 0; +} + +// Creates array of cities available as quick destinations. +// +// 0x4C55D4 +int _wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr) +{ + int* quickDestinations = *quickDestinationsPtr; + + if (quickDestinations != NULL) { + internal_free(quickDestinations); + quickDestinations = NULL; + } + + *quickDestinationsPtr = NULL; + *quickDestinationsLengthPtr = 0; + + int capacity = 10; + + quickDestinations = internal_malloc(sizeof(*quickDestinations) * capacity); + *quickDestinationsPtr = quickDestinations; + + if (quickDestinations == NULL) { + return -1; + } + + int quickDestinationsLength = *quickDestinationsLengthPtr; + for (int index = 0; index < gCitiesLength; index++) { + if (_wmAreaIsKnown(index) && gCities[index].labelFid != -1) { + quickDestinationsLength++; + *quickDestinationsLengthPtr = quickDestinationsLength; + + if (capacity <= quickDestinationsLength) { + capacity += 10; + + quickDestinations = internal_realloc(quickDestinations, sizeof(*quickDestinations) * capacity); + if (quickDestinations == NULL) { + return -1; + } + + *quickDestinationsPtr = quickDestinations; + } + + quickDestinations[quickDestinationsLength - 1] = index; + } + } + + qsort(quickDestinations, quickDestinationsLength, sizeof(*quickDestinations), worldmapCompareCitiesByName); + + return 0; +} + +// 0x4C56C8 +int worldmapCompareCitiesByName(const void* a1, const void* a2) +{ + int v1 = *(int*)a1; + int v2 = *(int*)a2; + + CityInfo* city1 = &(gCities[v1]); + CityInfo* city2 = &(gCities[v2]); + + return stricmp(city1->name, city2->name); +} + +// 0x4C5734 +void worldmapWindowRenderDial(bool shouldRefreshWindow) +{ + unsigned char* data = artGetFrameData(gWorldmapDialFrm, gWorldmapDialFrmCurrentFrame, 0); + blitBufferToBufferTrans(data, + gWorldmapDialFrmWidth, + gWorldmapDialFrmHeight, + gWorldmapDialFrmWidth, + gWorldmapWindowBuffer + WM_WINDOW_WIDTH * WM_WINDOW_DIAL_Y + WM_WINDOW_DIAL_X, + WM_WINDOW_WIDTH); + + if (shouldRefreshWindow) { + Rect rect; + rect.left = WM_WINDOW_DIAL_X; + rect.top = WM_WINDOW_DIAL_Y - 1; + rect.right = rect.left + gWorldmapDialFrmWidth; + rect.bottom = rect.top + gWorldmapDialFrmHeight; + windowRefreshRect(gWorldmapWindow, &rect); + } +} + +// 0x4C5804 +int _wmAreaFindFirstValidMap(int* out_a1) +{ + *out_a1 = -1; + + if (_WorldMapCurrArea == -1) { + return -1; + } + + CityInfo* city = &(gCities[_WorldMapCurrArea]); + if (city->entrancesLength == 0) { + return -1; + } + + for (int index = 0; index < city->entrancesLength; index++) { + EntranceInfo* entrance = &(city->entrances[index]); + if (entrance->state != 0) { + *out_a1 = entrance->map; + return 0; + } + } + + EntranceInfo* entrance = &(city->entrances[0]); + entrance->state = 1; + + *out_a1 = entrance->map; + return 0; +} + +// 0x4C58C0 +int worldmapStartMapMusic() +{ + do { + int mapIndex = mapGetCurrentMap(); + if (mapIndex == -1 || mapIndex >= gMapsLength) { + break; + } + + MapInfo* map = &(gMaps[mapIndex]); + if (strlen(map->music) == 0) { + break; + } + + if (_gsound_background_play_level_music(map->music, 12) == -1) { + break; + } + + return 0; + } while (0); + + debugPrint("\nWorldMap Error: Couldn't start map Music!"); + + return -1; +} + +// wmSetMapMusic +// 0x4C5928 +int worldmapSetMapMusic(int mapIndex, const char* name) +{ + if (mapIndex == -1 || mapIndex >= gMapsLength) { + return -1; + } + + if (name == NULL) { + return -1; + } + + debugPrint("\nwmSetMapMusic: %d, %s", mapIndex, name); + + MapInfo* map = &(gMaps[mapIndex]); + + strncpy(map->music, name, 40); + map->music[39] = '\0'; + + if (mapGetCurrentMap() == mapIndex) { + backgroundSoundDelete(); + worldmapStartMapMusic(); + } + + return 0; +} + +// 0x4C59A4 +int _wmMatchAreaContainingMapIdx(int mapIndex, int* cityIndexPtr) +{ + *cityIndexPtr = 0; + + for (int cityIndex = 0; cityIndex < gCitiesLength; cityIndex++) { + CityInfo* cityInfo = &(gCities[cityIndex]); + for (int entranceIndex = 0; entranceIndex < cityInfo->entrancesLength; entranceIndex++) { + EntranceInfo* entranceInfo = &(cityInfo->entrances[entranceIndex]); + if (entranceInfo->map == mapIndex) { + *cityIndexPtr = cityIndex; + return 0; + } + } + } + + return -1; +} + +// 0x4C5A1C +int _wmTeleportToArea(int cityIndex) +{ + if (!cityIsValid(cityIndex)) { + return -1; + } + + _WorldMapCurrArea = cityIndex; + gWorldmapTravelDestX = 0; + gWorldmapTravelDestY = 0; + gWorldmapIsTravelling = false; + + CityInfo* city = &(gCities[cityIndex]); + _world_xpos = city->x; + _world_ypos = city->y; + + return 0; +} diff --git a/src/world_map.h b/src/world_map.h new file mode 100644 index 0000000..65576ba --- /dev/null +++ b/src/world_map.h @@ -0,0 +1,826 @@ +#ifndef WORLD_MAP_H +#define WORLD_MAP_H + +#include "art.h" +#include "config.h" +#include "db.h" +#include "map_defs.h" +#include "message.h" +#include "obj_types.h" + +#include + +#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 CAR_FUEL_MAX (80000) + +#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) + +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 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 CityState { + CITY_STATE_UNKNOWN, + CITY_STATE_KNOWN, + CITY_STATE_VISITED, + CITY_STATE_INVISIBLE = -66, +} CityState; + +typedef enum SubtileState { + SUBTILE_STATE_UNKNOWN, + SUBTILE_STATE_KNOWN, + SUBTILE_STATE_VISITED, +} SubtileState; + +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; + +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 field_28; + int x; + int y; + int size; + int state; + // lock state + int field_3C; + int field_40; + 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 field_0[40]; + int field_28; + 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 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; + +extern const int _can_rest_here[ELEVATION_COUNT]; +extern const int gDayPartEncounterFrequencyModifiers[DAY_PART_COUNT]; +extern const char* off_4BC878[2]; +extern MessageListItem stru_4BC880; + +extern char _aCricket[]; +extern char _aCricket1[]; + +extern const char* _wmStateStrs[2]; +extern const char* _wmYesNoStrs[2]; +extern const char* gEncounterFrequencyTypeKeys[ENCOUNTER_FREQUENCY_TYPE_COUNT]; +extern const char* _wmFillStrs[9]; +extern const char* _wmSceneryStrs[ENCOUNTER_SCENERY_TYPE_COUNT]; +extern Terrain* gTerrains; +extern int gTerrainsLength; +extern TileInfo* gWorldmapTiles; +extern int gWorldmapTilesLength; +extern int gWorldmapGridWidth; +extern CityInfo* gCities; +extern int gCitiesLength; +extern const char* gCitySizeKeys[CITY_SIZE_COUNT]; +extern MapInfo* gMaps; +extern int gMapsLength; +extern int gWorldmapWindow; +extern CacheEntry* gWorldmapBoxFrmHandle; +extern int gWorldmapBoxFrmWidth; +extern int gWorldmapBoxFrmHeight; +extern unsigned char* gWorldmapWindowBuffer; +extern unsigned char* gWorldmapBoxFrmData; +extern int gWorldmapOffsetX; +extern int gWorldmapOffsetY; +extern unsigned char* _circleBlendTable; +extern int _wmInterfaceWasInitialized; +extern const char* _wmEncOpStrs[ENCOUNTER_SITUATION_COUNT]; +extern const char* _wmConditionalOpStrs[ENCOUNTER_CONDITIONAL_OPERATOR_COUNT]; +extern const char* gEncounterFormationTypeKeys[ENCOUNTER_FORMATION_TYPE_COUNT]; +extern int gWorldmapEncounterFrmIds[WORLD_MAP_ENCOUNTER_FRM_COUNT]; +extern int* gQuickDestinations; +extern int gQuickDestinationsLength; +extern int _wmTownMapCurArea; +extern unsigned int _wmLastRndTime; +extern int _wmRndIndex; +extern int _wmRndCallCount; +extern int _terrainCounter; +extern unsigned int _lastTime_2; +extern bool _couldScroll; +extern unsigned char* gWorldmapCurrentCityMapFrmData; +extern CacheEntry* gWorldmapCityMapFrmHandle; +extern int gWorldmapCityMapFrmWidth; +extern int gWorldmapCityMapFrmHeight; +extern char* _wmRemapSfxList[2]; + +extern int _wmRndTileDirs[2]; +extern int _wmRndCenterTiles[2]; +extern int _wmRndCenterRotations[2]; +extern int _wmRndRotOffsets[2]; +extern int _wmTownMapButtonId[ENTRANCE_LIST_CAPACITY]; +extern int _wmGenData; +extern int _Meet_Frank_Horrigan; +extern int _WorldMapCurrArea; +extern int _world_xpos; +extern int _world_ypos; +extern SubtileInfo* _world_subtile; +extern int dword_672E18; +extern bool gWorldmapIsTravelling; +extern int gWorldmapTravelDestX; +extern int gWorldmapTravelDestY; +extern int dword_672E28; +extern int dword_672E2C; +extern int dword_672E30; +extern int dword_672E34; +extern int _x_line_inc; +extern int dword_672E3C; +extern int _y_line_inc; +extern int dword_672E44; +extern int _wmEncounterIconShow; +extern int _EncounterMapID; +extern int dword_672E50; +extern int dword_672E54; +extern int _wmRndCursorFid; +extern int _old_world_xpos; +extern int _old_world_ypos; +extern bool gWorldmapIsInCar; +extern int _carCurrentArea; +extern int gWorldmapCarFuel; +extern CacheEntry* gWorldmapCarFrmHandle; +extern Art* gWorldmapCarFrm; +extern int gWorldmapCarFrmWidth; +extern int gWorldmapCarFrmHeight; +extern int gWorldmapCarFrmCurrentFrame; +extern CacheEntry* gWorldmapHotspotUpFrmHandle; +extern unsigned char* gWorldmapHotspotUpFrmData; +extern CacheEntry* gWorldmapHotspotDownFrmHandle; +extern unsigned char* gWorldmapHotspotDownFrmData; +extern int gWorldmapHotspotUpFrmWidth; +extern int gWorldmapHotspotUpFrmHeight; +extern CacheEntry* gWorldmapDestinationMarkerFrmHandle; +extern unsigned char* gWorldmapDestinationMarkerFrmData; +extern int gWorldmapDestinationMarkerFrmWidth; +extern int gWorldmapDestinationMarkerFrmHeight; +extern CacheEntry* gWorldmapLocationMarkerFrmHandle; +extern unsigned char* gWorldmapLocationMarkerFrmData; +extern int gWorldmapLocationMarkerFrmWidth; +extern int gWorldmapLocationMarkerFrmHeight; +extern CacheEntry* gWorldmapEncounterFrmHandles[WORLD_MAP_ENCOUNTER_FRM_COUNT]; +extern unsigned char* gWorldmapEncounterFrmData[WORLD_MAP_ENCOUNTER_FRM_COUNT]; +extern int gWorldmapEncounterFrmWidths[WORLD_MAP_ENCOUNTER_FRM_COUNT]; +extern int gWorldmapEncounterFrmHeights[WORLD_MAP_ENCOUNTER_FRM_COUNT]; +extern int _wmViewportRightScrlLimit; +extern int _wmViewportBottomtScrlLimit; +extern CacheEntry* gWorldmapTownTabsUnderlayFrmHandle; +extern int gWorldmapTownTabsUnderlayFrmWidth; +extern int gWorldmapTownTabsUnderlayFrmHeight; +extern int _LastTabsYOffset; +extern unsigned char* gWorldmapTownTabsUnderlayFrmData; +extern CacheEntry* gWorldmapTownTabsEdgeFrmHandle; +extern unsigned char* gWorldmapTownTabsEdgeFrmData; +extern CacheEntry* gWorldmapDialFrmHandle; +extern int gWorldmapDialWidth; +extern int gWorldmapDialHeight; +extern int gWorldmapDialFrmCurrentFrame; +extern Art* gWorldmapDialFrm; +extern CacheEntry* gWorldmapCarOverlayFrmHandle; +extern int gWorldmapCarOverlayFrmWidth; +extern int gWorldmapCarOverlayFrmHeight; +extern unsigned char* gWorldmapOverlayFrmData; +extern CacheEntry* gWorldmapGlobeOverlayFrmHandle; +extern int gWorldmapGlobeOverlayFrmWidth; +extern int gWorldmapGloveOverlayFrmHeight; +extern unsigned char* gWorldmapGlobeOverlayFrmData; +extern int dword_672F54; +extern int _tabsOffset; +extern CacheEntry* gWorldmapLittleRedButtonUpFrmHandle; +extern CacheEntry* gWorldmapLittleRedButtonDownFrmHandle; +extern unsigned char* gWorldmapLittleRedButtonUpFrmData; +extern unsigned char* gWorldmapLittleRedButtonDownFrmData; +extern CacheEntry* gWorldmapTownListScrollUpFrmHandle[2]; +extern int gWorldmapTownListScrollUpFrmWidth; +extern int gWorldmapTownListScrollUpFrmHeight; +extern unsigned char* gWorldmapTownListScrollUpFrmData[2]; +extern CacheEntry* gWorldmapTownListScrollDownFrmHandle[2]; +extern int gWorldmapTownListScrollDownFrmWidth; +extern int gWorldmapTownListScrollDownFrmHeight; +extern unsigned char* gWorldmapTownListScrollDownFrmData[2]; +extern CacheEntry* gWorldmapMonthsFrmHandle; +extern Art* gWorldmapMonthsFrm; +extern CacheEntry* gWorldmapNumbersFrmHandle; +extern Art* gWorldmapNumbersFrm; +extern int _fontnum; +extern MessageList gWorldmapMessageList; +extern int _wmFreqValues[6]; +extern int _wmRndOriginalCenterTile; +extern Config* gWorldmapConfig; +extern int _wmTownMapSubButtonIds[7]; +extern ENC_BASE_TYPE* _wmEncBaseTypeList; +extern CitySizeDescription gCitySizeDescriptions[CITY_SIZE_COUNT]; +extern EncounterTable* gEncounterTables; +extern int _wmMaxEncBaseTypes; +extern int gEncounterTablesLength; + +int worldmapInit(); +int _wmGenDataInit(); +int _wmGenDataReset(); +void worldmapExit(); +int worldmapReset(); +int worldmapSave(File* stream); +int worldmapLoad(File* stream); +int _wmWorldMapSaveTempData(); +int _wmWorldMapLoadTempData(); +int worldmapConfigInit(); +int worldmapConfigLoadEncounterTable(Config* config, char* lookup_name, char* section); +int worldmapConfigLoadEncounterEntry(EncounterEntry* entry, char* str); +int _wmParseEncounterSubEncStr(EncounterEntry* entry, char** str_ptr); +int _wmParseFindSubEncTypeMatch(char* str, int* out_value); +int _wmFindEncBaseTypeMatch(char* str, int* out_value); +int _wmReadEncBaseType(char* str, int* out_value); +int _wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** str_ptr); +int _wmEncBaseTypeSlotInit(ENC_BASE_TYPE* entry); +int _wmEncBaseSubTypeSlotInit(ENC_BASE_TYPE_38* entry); +int _wmEncounterSubEncSlotInit(ENCOUNTER_ENTRY_ENC* entry); +int worldmapEncounterTableEntryInit(EncounterEntry* entry); +int worldmapEncounterTableInit(EncounterTable* encounterTable); +int worldmapTileInfoInit(TileInfo* tile); +int worldmapTerrainInfoInit(Terrain* terrain); +int worldmapConfigInitEncounterCondition(EncounterCondition* condition); +int _wmParseTerrainTypes(Config* config, char* str); +int _wmParseTerrainRndMaps(Config* config, Terrain* terrain); +int worldmapConfigLoadSubtile(TileInfo* tile, int x, int y, char* str); +int worldmapFindEncounterTableByLookupName(char* str, int* out_value); +int worldmapFindTerrainByLookupName(char* str, int* out_value); +int _wmParseEncounterItemType(char** str_ptr, ENC_BASE_TYPE_38_48* a2, int* a3, const char* delim); +int _wmParseItemType(char* str, ENC_BASE_TYPE_38_48* ptr); +int worldmapConfigParseCondition(char** stringPtr, const char* a2, EncounterCondition* condition); +int worldmapConfigParseConditionEntry(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr); +int worldmapConfigParseEncounterConditionalOperator(char** str_ptr, int* out_op); +int worldmapCityInfoInit(CityInfo* area); +int cityInit(); +int worldmapFindMapByLookupName(char* str, int* out_value); +int worldmapCityEntranceInfoInit(EntranceInfo* entrance); +int worldmapMapInfoInit(MapInfo* map); +int _wmMapInit(); +int worldmapRandomStartingPointInit(MapStartPointInfo* rsp); +int mapGetCount(); +int mapGetFileName(int map_index, char* dest); +int mapGetIndexByFileName(char* name); +bool _wmMapIdxIsSaveable(int map_index); +bool _wmMapIsSaveable(); +bool _wmMapDeadBodiesAge(); +bool _wmMapCanRestHere(int elevation); +bool _wmMapPipboyActive(); +int _wmMapMarkVisited(int map_index); +int _wmMatchEntranceFromMap(int cityIndex, int mapIndex, int* entranceIndexPtr); +int _wmMatchEntranceElevFromMap(int cityIndex, int map, int elevation, int* entranceIndexPtr); +int _wmMatchAreaFromMap(int a1, int* out_a2); +int _wmMapMarkMapEntranceState(int a1, int a2, int a3); +void _wmWorldMap(); +int _wmWorldMapFunc(int a1); +int _wmCheckGameAreaEvents(); +int _wmInterfaceCenterOnParty(); +int _wmRndEncounterOccurred(); +int _wmPartyFindCurSubTile(); +int _wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtile); +int _wmFindCurTileFromPos(int x, int y, TileInfo** tile); +int _wmRndEncounterPick(); +int worldmapSetupRandomEncounter(); +int worldmapSetupRandomEncounter(); +int worldmapSetupCritters(int type_idx, Object** out_critter, int a3); +int _wmSetupRndNextTileNumInit(ENC_BASE_TYPE* a1); +int _wmSetupRndNextTileNum(ENC_BASE_TYPE* a1, ENC_BASE_TYPE_38* a2, int* out_tile_num); +bool _wmEvalTileNumForPlacement(int tile); +bool _wmEvalConditional(EncounterCondition* a1, int* a2); +bool _wmEvalSubConditional(int a1, int a2, int a3); +bool _wmGameTimeIncrement(int a1); +int _wmGrabTileWalkMask(int tile_index); +bool _wmWorldPosInvalid(int a1, int a2); +void _wmPartyInitWalking(int x, int y); +void worldmapPerformTravel(); +void _wmInterfaceScrollTabsStart(int a1); +void _wmInterfaceScrollTabsStop(); +int worldmapWindowInit(); +int worldmapWindowFree(); +int worldmapWindowScroll(int a1, int a2, int a3, int a4, bool* a5, bool a6); +void worldmapWindowHandleMouseScrolling(); +int _wmMarkSubTileOffsetVisitedFunc(int a1, int a2, int a3, int a4, int a5, int a6); +void _wmMarkSubTileRadiusVisited(int x, int y); +int _wmSubTileMarkRadiusVisited(int x, int y, int radius); +int _wmSubTileGetVisitedState(int a1, int a2, int* a3); +int _wmTileGrabArt(int tile_index); +int worldmapWindowRefresh(); +void worldmapWindowRenderDate(bool shouldRefreshWindow); +int _wmMatchWorldPosToArea(int a1, int a2, int* a3); +int worldmapWindowRenderCity(CityInfo* cityInfo, CitySizeDescription* citySizeInfo, unsigned char* buffer, int x, int y); +void worldmapWindowDimRect(unsigned char* dest, int width, int height, int pitch); +int worldmapWindowDimSubtile(TileInfo* tileInfo, int a2, int a3, int a4, int a5, int a6); +int _wmDrawCursorStopped(); +bool _wmCursorIsVisible(); +int _wmGetAreaIdxName(int index, char* name); +bool _wmAreaIsKnown(int city_index); +int _wmAreaVisitedState(int a1); +bool _wmMapIsKnown(int map_index); +bool _wmAreaMarkVisitedState(int a1, int a2); +bool _wmAreaSetVisibleState(int a1, int a2, int a3); +int worldmapCitySetPos(int index, int x, int y); +int _wmGetPartyWorldPos(int* out_x, int* out_y); +int _wmGetPartyCurArea(int* a1); +void _wmMarkAllSubTiles(int a1); +void _wmTownMap(); +int worldmapCityMapViewSelect(int* mapIndexPtr); +int worldmapCityMapViewInit(); +int worldmapCityMapViewRefresh(); +int worldmapCityMapViewFree(); +int carConsumeFuel(int a1); +int carAddFuel(int a1); +int carGetFuel(); +bool carIsEmpty(); +int carGetCity(); +int _wmCarGiveToParty(); +int ambientSoundEffectGetLength(); +int ambientSoundEffectGetRandom(); +int ambientSoundEffectGetName(int ambientSoundEffectIndex, char** namePtr); +int worldmapWindowRenderChrome(bool shouldRefreshWindow); +void worldmapWindowRenderCarFuelBar(); +int worldmapRenderQuickDestinations(); +int _wmMakeTabsLabelList(int** out_cities, int* out_len); +int worldmapCompareCitiesByName(const void* a1, const void* a2); +void worldmapWindowRenderDial(bool shouldRefreshWindow); +int _wmAreaFindFirstValidMap(int* out_a1); +int worldmapStartMapMusic(); +int worldmapSetMapMusic(int a1, const char* name); +int _wmMatchAreaContainingMapIdx(int map_index, int* out_city_index); +int _wmTeleportToArea(int a1); + +static inline bool cityIsValid(int city) +{ + return city >= 0 && city < gCitiesLength; +} + +#endif /* WORLD_MAP_H */ diff --git a/src/xfile.c b/src/xfile.c new file mode 100644 index 0000000..96070e7 --- /dev/null +++ b/src/xfile.c @@ -0,0 +1,829 @@ +#include "xfile.h" + +#include "file_find.h" + +#include +#include +#include +#include +#include +#include + +// 0x6B24D0 +XBase* gXbaseHead; + +// 0x6B24D4 +bool gXbaseExitHandlerRegistered; + +// 0x4DED6C +int xfileClose(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 112 + + int rc; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + rc = dfileClose(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* xfileOpen(const char* filePath, const char* mode) +{ + assert(filePath); // "filename", "xfile.c", 162 + assert(mode); // "mode", "xfile.c", 163 + + XFile* stream = 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 = gXbaseHead; + while (curr != NULL) { + if (curr->isDbase) { + // Attempt to open dfile stream from dbase. + stream->dfile = dfileOpen(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 xfilePrintFormatted(XFile* stream, const char* format, ...) +{ + assert(format); // "format", "xfile.c", 305 + + va_list args; + va_start(args, format); + + int rc = xfilePrintFormattedArgs(stream, format, args); + + va_end(args); + + return rc; +} + +// [vfprintf]. +// +// 0x4DF1AC +int xfilePrintFormattedArgs(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 = dfilePrintFormattedArgs(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 xfileReadChar(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 354 + + int ch; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + ch = dfileReadChar(stream->dfile); + break; + case XFILE_TYPE_GZFILE: + ch = gzgetc(stream->gzfile); + break; + default: + ch = fgetc(stream->file); + break; + } + + return ch; +} + +// 0x4DF280 +char* xfileReadString(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 = dfileReadString(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 xfileWriteChar(int ch, XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 399 + + int rc; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + rc = dfileWriteChar(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 xfileWriteString(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 = dfileWriteString(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 xfileRead(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 = dfileRead(ptr, size, count, stream->dfile); + break; + case XFILE_TYPE_GZFILE: + // FIXME: There is a bug in the return value. Both [dfileRead] 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 xfileWrite(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 = dfileWrite(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 [dfileWrite] 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 xfileSeek(XFile* stream, long offset, int origin) +{ + assert(stream); // "stream", "xfile.c", 547 + + int result; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + result = dfileSeek(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 xfileTell(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 588 + + long pos; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + pos = dfileTell(stream->dfile); + break; + case XFILE_TYPE_GZFILE: + pos = gztell(stream->gzfile); + break; + default: + pos = ftell(stream->file); + break; + } + + return pos; +} + +// 0x4DF6E4 +void xfileRewind(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 608 + + switch (stream->type) { + case XFILE_TYPE_DFILE: + dfileRewind(stream->dfile); + break; + case XFILE_TYPE_GZFILE: + gzrewind(stream->gzfile); + break; + default: + rewind(stream->file); + break; + } +} + +// 0x4DF780 +int xfileEof(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 648 + + int rc; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + rc = dfileEof(stream->dfile); + break; + case XFILE_TYPE_GZFILE: + rc = gzeof(stream->gzfile); + break; + default: + rc = feof(stream->file); + break; + } + + return rc; +} + +// 0x4DF828 +long xfileGetSize(XFile* stream) +{ + assert(stream); // "stream", "xfile.c", 690 + + long fileSize; + + switch (stream->type) { + case XFILE_TYPE_DFILE: + fileSize = dfileGetSize(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 xbaseReopenAll(char* paths) +{ + // NOTE: Uninline. + xbaseCloseAll(); + + if (paths != NULL) { + char* tok = strtok(paths, ";"); + while (tok != NULL) { + if (!xbaseOpen(tok)) { + return false; + } + tok = strtok(NULL, ";"); + } + } + + return true; +} + +// 0x4DF938 +bool xbaseOpen(const char* path) +{ + assert(path); // "path", "xfile.c", 747 + + // Register atexit handler so that underlying dbase (if any) can be + // gracefully closed. + if (!gXbaseExitHandlerRegistered) { + atexit(xbaseExitHandler); + gXbaseExitHandlerRegistered = true; + } + + XBase* curr = gXbaseHead; + 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 = gXbaseHead; + gXbaseHead = curr; + } + return true; + } + + 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 = dbaseOpen(path); + if (dbase != NULL) { + xbase->isDbase = true; + xbase->dbase = dbase; + xbase->next = gXbaseHead; + gXbaseHead = 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 = gXbaseHead; + gXbaseHead = xbase; + return true; + } + + if (xbaseMakeDirectory(path) != 0) { + // FIXME: Leaking xbase and path. + return false; + } + + chdir(workingDirectory); + + xbase->next = gXbaseHead; + gXbaseHead = xbase; + + return true; +} + +// 0x4DFB3C +bool xlistEnumerate(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 (fileFindFirst(pattern, &directoryFileFindData)) { + do { + bool isDirectory; + char* entryName; + +#if defined(_MSC_VER) + isDirectory = (directoryFileFindData.ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + entryName = directoryFileFindData.ffd.cFileName; +#elif defined(__WATCOMC__) + isDirectory = (directoryFileFindData.entry->d_attr & _A_SUBDIR) != 0; + entryName = directoryFileFindData.entry->d_name; +#else +#error Not implemented +#endif + + 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 (fileFindNext(&directoryFileFindData)); + } + return findFindClose(&directoryFileFindData); + } + + XBase* xbase = gXbaseHead; + while (xbase != NULL) { + if (xbase->isDbase) { + DFileFindData dbaseFindData; + if (dbaseFindFirstEntry(xbase->dbase, &dbaseFindData, pattern)) { + context.type = XFILE_ENUMERATION_ENTRY_TYPE_DFILE; + + do { + strcpy(context.name, dbaseFindData.fileName); + if (!handler(&context)) { + return dbaseFindClose(xbase->dbase, &dbaseFindData); + } + } while (dbaseFindNextEntry(xbase->dbase, &dbaseFindData)); + + dbaseFindClose(xbase->dbase, &dbaseFindData); + } + } else { + char path[FILENAME_MAX]; + sprintf(path, "%s\\%s", xbase->path, pattern); + + if (fileFindFirst(path, &directoryFileFindData)) { + do { + bool isDirectory; + char* entryName; + +#if defined(_MSC_VER) + isDirectory = (directoryFileFindData.ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + entryName = directoryFileFindData.ffd.cFileName; +#elif defined(__WATCOMC__) + isDirectory = (directoryFileFindData.entry->d_attr & _A_SUBDIR) != 0; + entryName = directoryFileFindData.entry->d_name; +#else +#error Not implemented +#endif + + 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 (fileFindNext(&directoryFileFindData)); + } + return findFindClose(&directoryFileFindData); + } + xbase = xbase->next; + } + + _splitpath(pattern, drive, dir, fileName, extension); + if (fileFindFirst(pattern, &directoryFileFindData)) { + do { + bool isDirectory; + char* entryName; + +#if defined(_MSC_VER) + isDirectory = (directoryFileFindData.ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + entryName = directoryFileFindData.ffd.cFileName; +#elif defined(__WATCOMC__) + isDirectory = (directoryFileFindData.entry->d_attr & _A_SUBDIR) != 0; + entryName = directoryFileFindData.entry->d_name; +#else +#error Not implemented +#endif + + 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 (fileFindNext(&directoryFileFindData)); + } + return findFindClose(&directoryFileFindData); +} + +// 0x4DFF28 +bool xlistInit(const char* pattern, XList* xlist) +{ + xlistEnumerate(pattern, xlistEnumerateHandler, xlist); + return xlist->fileNamesLength != -1; +} + +// 0x4DFF48 +void xlistFree(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 xbaseMakeDirectory(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 = gXbaseHead; + 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 +void xbaseCloseAll() +{ + XBase* curr = gXbaseHead; + gXbaseHead = NULL; + + while (curr != NULL) { + XBase* next = curr->next; + + if (curr->isDbase) { + dbaseClose(curr->dbase); + } + + free(curr->path); + free(curr); + + curr = next; + } +} + +// xbase atexit +void xbaseExitHandler(void) +{ + // NOTE: Uninline. + xbaseCloseAll(); +} + +// 0x4E0278 +bool xlistEnumerateHandler(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) { + xlistFree(xlist); + xlist->fileNamesLength = -1; + return false; + } + + xlist->fileNames = fileNames; + + fileNames[xlist->fileNamesLength] = strdup(context->name); + if (fileNames[xlist->fileNamesLength] == NULL) { + xlistFree(xlist); + xlist->fileNamesLength = -1; + return false; + } + + xlist->fileNamesLength++; + + return true; +} diff --git a/src/xfile.h b/src/xfile.h new file mode 100644 index 0000000..4c19d53 --- /dev/null +++ b/src/xfile.h @@ -0,0 +1,90 @@ +#ifndef XFILE_H +#define XFILE_H + +#include "dfile.h" + +#include +#include +#include + +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); + +extern XBase* gXbaseHead; +extern bool gXbaseExitHandlerRegistered; + +int xfileClose(XFile* stream); +XFile* xfileOpen(const char* filename, const char* mode); +int xfilePrintFormatted(XFile* xfile, const char* format, ...); +int xfilePrintFormattedArgs(XFile* stream, const char* format, va_list args); +int xfileReadChar(XFile* stream); +char* xfileReadString(char* string, int size, XFile* stream); +int xfileWriteChar(int ch, XFile* stream); +int xfileWriteString(const char* s, XFile* stream); +size_t xfileRead(void* ptr, size_t size, size_t count, XFile* stream); +size_t xfileWrite(const void* buf, size_t size, size_t count, XFile* stream); +int xfileSeek(XFile* stream, long offset, int origin); +long xfileTell(XFile* stream); +void xfileRewind(XFile* stream); +int xfileEof(XFile* stream); +long xfileGetSize(XFile* stream); +bool xbaseReopenAll(char* paths); +bool xbaseOpen(const char* path); +bool xlistEnumerate(const char* pattern, XListEnumerationHandler* handler, XList* xlist); +bool xlistInit(const char* pattern, XList* xlist); +void xlistFree(XList* xlist); +int xbaseMakeDirectory(const char* path); +void xbaseCloseAll(); +void xbaseExitHandler(void); +bool xlistEnumerateHandler(XListEnumerationContext* context); + +#endif /* XFILE_H */ diff --git a/third_party/fpattern/CMakeLists.txt b/third_party/fpattern/CMakeLists.txt new file mode 100644 index 0000000..1bb90aa --- /dev/null +++ b/third_party/fpattern/CMakeLists.txt @@ -0,0 +1,19 @@ +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() + +target_sources(fallout2-ce PUBLIC + "${fpattern_SOURCE_DIR}/debug.h" + "${fpattern_SOURCE_DIR}/fpattern.c" + "${fpattern_SOURCE_DIR}/fpattern.h" +) + +target_include_directories(fallout2-ce PUBLIC ${fpattern_SOURCE_DIR}) diff --git a/third_party/fpattern/LICENSE b/third_party/fpattern/LICENSE new file mode 100644 index 0000000..4b9d083 --- /dev/null +++ b/third_party/fpattern/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/third_party/fpattern/README.md b/third_party/fpattern/README.md new file mode 100644 index 0000000..5a55460 --- /dev/null +++ b/third_party/fpattern/README.md @@ -0,0 +1,3 @@ +# 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). diff --git a/third_party/zlib/CMakeLists.txt b/third_party/zlib/CMakeLists.txt new file mode 100644 index 0000000..f7a8acf --- /dev/null +++ b/third_party/zlib/CMakeLists.txt @@ -0,0 +1,15 @@ +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) +target_include_directories(fallout2-ce PUBLIC ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) +target_link_libraries(fallout2-ce zlibstatic) diff --git a/third_party/zlib/LICENSE b/third_party/zlib/LICENSE new file mode 100644 index 0000000..b0e9418 --- /dev/null +++ b/third_party/zlib/LICENSE @@ -0,0 +1,20 @@ +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 diff --git a/third_party/zlib/README.md b/third_party/zlib/README.md new file mode 100644 index 0000000..caf4357 --- /dev/null +++ b/third_party/zlib/README.md @@ -0,0 +1,3 @@ +# 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.