Skip to content

Commit

Permalink
WorldMapHealingRatePatch & set_world_map_heal_time metarule (#539)
Browse files Browse the repository at this point in the history
Fixes bug: healing time on worldmap is tied to real time clock, instead of game time.
  • Loading branch information
phobos2077 authored May 27, 2024
1 parent 6d5b08f commit d21225b
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 2 deletions.
8 changes: 8 additions & 0 deletions artifacts/scripting/functions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,14 @@
detail: void set_rest_heal_time(int time)
doc: 'Sets the time interval in minutes for healing during resting. The default is 180. Note: The interval will be reset each time the player reloads the game.'
macro: sfall.h
- name: set_worldmap_heal_time
detail: void set_worldmap_heal_time(int time)
doc: |
Sets the time interval in minutes for healing during worldmap travel.
- passing 0 will revert to 1 second per real time (vanilla behavior)
- passing -1 will disable healing during travel
- the interval will be reset each time the player reloads the game
macro: sfall.h
- name: set_rest_mode
detail: void set_rest_mode(int flags)
doc: 'Sets the bit flags for the rest mode (see `RESTMODE_*` constants in **sfall.h**). Passing 0 will reset the rest mode. It will also be reset each time the player reloads the game.'
Expand Down
1 change: 1 addition & 0 deletions artifacts/scripting/headers/sfall.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
#define set_unique_id(obj) sfall_func1("set_unique_id", obj)
#define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time)
#define set_window_flag(winID, flag, value) sfall_func3("set_window_flag", winID, flag, value)
#define set_worldmap_heal_time(time) sfall_func1("set_worldmap_heal_time", time)
#define show_win sfall_func0("show_window")
#define show_window(winName) sfall_func1("show_window", winName)
#define signal_close_game sfall_func0("signal_close_game")
Expand Down
1 change: 1 addition & 0 deletions sfall/Modules/Scripting/Handlers/Metarule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ static const SfallMetarule metarules[] = {
{"set_outline", mf_set_outline, 2, 2, -1, {ARG_OBJECT, ARG_INT}},
{"set_quest_failure_value", mf_set_quest_failure_value, 2, 2, -1, {ARG_INT, ARG_INT}},
{"set_rest_heal_time", mf_set_rest_heal_time, 1, 1, -1, {ARG_INT}},
{"set_worldmap_heal_time", mf_set_worldmap_heal_time, 1, 1, -1, {ARG_INT}},
{"set_rest_mode", mf_set_rest_mode, 1, 1, -1, {ARG_INT}},
{"set_scr_name", mf_set_scr_name, 0, 1, -1, {ARG_STRING}},
{"set_selectable_perk_npc", mf_set_selectable_perk_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}},
Expand Down
4 changes: 4 additions & 0 deletions sfall/Modules/Scripting/Handlers/Worldmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ void mf_set_rest_heal_time(OpcodeContext& ctx) {
Worldmap::SetRestHealTime(ctx.arg(0).rawValue());
}

void mf_set_worldmap_heal_time(OpcodeContext& ctx) {
Worldmap::SetWorldMapHealTime(ctx.arg(0).rawValue());
}

void mf_set_rest_mode(OpcodeContext& ctx) {
Worldmap::SetRestMode(ctx.arg(0).rawValue());
}
Expand Down
2 changes: 2 additions & 0 deletions sfall/Modules/Scripting/Handlers/Worldmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ void mf_get_map_enter_position(OpcodeContext&);

void mf_set_rest_heal_time(OpcodeContext&);

void mf_set_worldmap_heal_time(OpcodeContext&);

void mf_set_rest_mode(OpcodeContext&);

void mf_set_rest_on_map(OpcodeContext&);
Expand Down
51 changes: 49 additions & 2 deletions sfall/Modules/Worldmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ static DWORD worldMapTicks;

static DWORD WorldMapEncounterRate;

static constexpr long WorldMapHealingDefaultInterval = 0;
// Healing interval in game time
static long worldMapHealingInterval = WorldMapHealingDefaultInterval;
static DWORD worldMapLastHealTime;

static double tickFract = 0.0;
static double mapMultiMod = 1.0;
static float scriptMapMulti = 1.0f;
Expand Down Expand Up @@ -454,6 +459,40 @@ static void WorldmapFpsPatch() {
}
}

// Original function checks how many system Ticks (not game ticks) passed since last WM loop iteration and if more than 1000 ms,
// applies one healing to the whole party according to their healing rate.
// So we hijack it and return number bigger than 1000 to force the event, or 0 to skip it.
static DWORD __fastcall wmWorldMap_HealingTimeElapsed(DWORD elapsedTocks) {
if (worldMapHealingInterval == 0) return elapsedTocks;
if (worldMapHealingInterval > 0 && ((long)(fo::var::fallout_game_time - worldMapLastHealTime) > worldMapHealingInterval)) {
worldMapLastHealTime = fo::var::fallout_game_time;
return 10000; // force healing
}
return 0; // skip healing
}

static void __declspec(naked) wmWorldMap_elapsed_tocks_hook() {
__asm {
push ecx;
call fo::funcoffs::elapsed_tocks_;
mov ecx, eax;
call wmWorldMap_HealingTimeElapsed;
pop ecx;
retn;
}
}

static void WorldmapHealingRatePatch() {
dlogr("Applying world map healing rate patch.", DL_INIT);
HookCall(0x4C0085, wmWorldMap_elapsed_tocks_hook);
LoadGameHook::OnGameModeChange() += [](DWORD state) {
if (InWorldMap()) worldMapLastHealTime = fo::var::fallout_game_time;
};
LoadGameHook::OnGameReset() += []() {
worldMapHealingInterval = WorldMapHealingDefaultInterval;
};
}

static void PathfinderFixInit() {
//if (IniReader::GetConfigInt("Misc", "PathfinderFix", 0)) {
dlogr("Applying Pathfinder patch.", DL_INIT);
Expand Down Expand Up @@ -585,6 +624,13 @@ void Worldmap::SetRestHealTime(DWORD minutes) {
}
}

void Worldmap::SetWorldMapHealTime(long minutes) {
worldMapHealingInterval = minutes >= 0
? minutes * 60 * 10
: -1;
worldMapLastHealTime = fo::var::fallout_game_time;
}

void Worldmap::SetRestMode(DWORD mode) {
RestRestore(); // restore default

Expand All @@ -601,8 +647,8 @@ void Worldmap::SetRestMode(DWORD mode) {
SafeWrite32(0x42E588, 0x94); // jmp 0x42E620
}
if (mode & 4) { // bit3 - disable healing during resting
SafeWrite16(0x499FD4, 0x9090);
SafeWrite16(0x499E93, 0x8FEB); // jmp 0x499E24
SafeWrite16(0x499FD4, 0x9090); // Check4Health_
SafeWrite16(0x499E93, 0x8FEB); // TimedRest_: jmp 0x499E24
}
}

Expand Down Expand Up @@ -691,6 +737,7 @@ void Worldmap::init() {
MapLimitsPatches();
WorldmapFpsPatch();
PipBoyAutomapsPatch();
WorldmapHealingRatePatch();

// Add a flashing icon to the Horrigan encounter
HookCall(0x4C071C, wmRndEncounterOccurred_hook);
Expand Down
1 change: 1 addition & 0 deletions sfall/Modules/Worldmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Worldmap : public Module {

static void SetCarInterfaceArt(DWORD artIndex);
static void SetRestHealTime(DWORD minutes);
static void SetWorldMapHealTime(long minutes);
static void SetRestMode(DWORD mode);
static void SetRestMapLevel(int mapId, long elev, bool canRest);
static long __fastcall GetRestMapLevel(long elev, int mapId);
Expand Down

0 comments on commit d21225b

Please sign in to comment.