Skip to content

Commit 3d2cc40

Browse files
committed
Added forceFutureCheckpoint cheat for coop
1 parent 35dcb1e commit 3d2cc40

13 files changed

+239
-26
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#include "pch.h"
2+
#include "ForceFutureCheckpoint.h"
3+
#include "IMessagesGUI.h"
4+
#include "RuntimeExceptionHandler.h"
5+
#include "SettingsStateAndEvents.h"
6+
#include "IMCCStateHook.h"
7+
#include "GameTickEventHook.h"
8+
#include "IMakeOrGetCheat.h"
9+
10+
class ForceFutureCheckpoint::ForceFutureCheckpointImpl
11+
{
12+
private:
13+
GameState mGame;
14+
// injected services
15+
std::weak_ptr<IMessagesGUI> messagesGUIWeak;
16+
std::shared_ptr<RuntimeExceptionHandler> runtimeExceptions;
17+
std::weak_ptr<GameTickEventHook> gameTickEventHookWeak;
18+
std::weak_ptr<SettingsStateAndEvents> settingsWeak;
19+
std::weak_ptr<IMCCStateHook> mccStateHookWeak;
20+
21+
// event callbacks
22+
ScopedCallback <ToggleEvent> forceFutureCheckpointToggleCallback;
23+
void onForceFutureCheckpointToggle(bool& newValue)
24+
{
25+
try
26+
{
27+
lockOrThrow(gameTickEventHookWeak, gameTickEventHook);
28+
29+
if (newValue)
30+
{
31+
gameTickEventCallback = std::make_unique<ScopedCallback<eventpp::CallbackList<void(int)>>>(gameTickEventHook->getGameTickEvent(), [this](int i) {onGameTickEventCallback(i); });
32+
}
33+
else
34+
{
35+
gameTickEventCallback.reset();
36+
}
37+
38+
}
39+
catch (HCMRuntimeException ex)
40+
{
41+
ex.prepend("Force Future Checkpoint service encountered an error: ");
42+
runtimeExceptions->handleMessage(ex);
43+
}
44+
}
45+
46+
// runs every tick while armed, if current tick is the user-selected tick then disarm the service and force a checkpoint.
47+
std::unique_ptr<ScopedCallback<eventpp::CallbackList<void(int)>>> gameTickEventCallback;
48+
void onGameTickEventCallback(int tickCount)
49+
{
50+
try
51+
{
52+
lockOrThrow(settingsWeak, settings)
53+
if (tickCount == settings->forceFutureCheckpointTick->GetValue())
54+
{ // current tick is the tick to fire the checkpoint!
55+
56+
// begin by disabling the toggle setting (which will disable this tick event callback)
57+
settings->forceFutureCheckpointToggle->GetValueDisplay() = false;
58+
settings->forceFutureCheckpointToggle->UpdateValueWithInput();
59+
60+
// force the checkpoint (fire the event; if it fails, not our problem)
61+
settings->forceCheckpointEvent->operator()();
62+
}
63+
}
64+
catch (HCMRuntimeException ex)
65+
{
66+
ex.prepend("Force Future Checkpoint service encountered an error: ");
67+
runtimeExceptions->handleMessage(ex);
68+
}
69+
}
70+
71+
72+
// fill the which-tick-to-force-checkpoint-on with a value 5 seconds from the current game tick.
73+
ScopedCallback <ActionEvent> forceFutureCheckpointFillCallback;
74+
void onForceFutureCheckpointFill()
75+
{
76+
PLOG_VERBOSE << "onForceFutureCheckpointFill";
77+
try
78+
{
79+
lockOrThrow(mccStateHookWeak, mccStateHook);
80+
if (mccStateHook->isGameCurrentlyPlaying(mGame) == false) return;
81+
82+
83+
lockOrThrow(gameTickEventHookWeak, gameTickEventHook);
84+
int currentTick = gameTickEventHook->getCurrentGameTick();
85+
86+
int ticksPerSecond = (mGame.operator GameState::Value() == GameState::Value::Halo1) ? 30 : 60;
87+
constexpr int secondsInTheFuture = 5;
88+
89+
int futureTick = currentTick + (ticksPerSecond * secondsInTheFuture);
90+
91+
lockOrThrow(settingsWeak, settings);
92+
settings->forceFutureCheckpointTick->GetValueDisplay() = futureTick;
93+
settings->forceFutureCheckpointTick->UpdateValueWithInput();
94+
}
95+
catch (HCMRuntimeException ex)
96+
{
97+
ex.prepend("Force Future Checkpoint service encountered an error: ");
98+
runtimeExceptions->handleMessage(ex);
99+
}
100+
101+
}
102+
103+
104+
105+
106+
public:
107+
ForceFutureCheckpointImpl(GameState game, IDIContainer& dicon)
108+
: mGame(game),
109+
forceFutureCheckpointToggleCallback(dicon.Resolve<SettingsStateAndEvents>().lock()->forceFutureCheckpointToggle->valueChangedEvent, [this](bool& n) { onForceFutureCheckpointToggle(n); }),
110+
forceFutureCheckpointFillCallback(dicon.Resolve<SettingsStateAndEvents>().lock()->forceFutureCheckpointFillEvent, [this]() { onForceFutureCheckpointFill(); }),
111+
mccStateHookWeak(dicon.Resolve<IMCCStateHook>()),
112+
messagesGUIWeak(dicon.Resolve<IMessagesGUI>()),
113+
runtimeExceptions(dicon.Resolve<RuntimeExceptionHandler>()),
114+
settingsWeak(dicon.Resolve<SettingsStateAndEvents>()),
115+
gameTickEventHookWeak(resolveDependentCheat(GameTickEventHook))
116+
{
117+
118+
}
119+
120+
};
121+
122+
123+
124+
125+
ForceFutureCheckpoint::ForceFutureCheckpoint(GameState gameImpl, IDIContainer& dicon)
126+
: pimpl(std::make_unique<ForceFutureCheckpointImpl>(gameImpl, dicon))
127+
{
128+
}
129+
130+
ForceFutureCheckpoint::~ForceFutureCheckpoint() = default;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
#include "pch.h"
3+
#include "IOptionalCheat.h"
4+
#include "DIContainer.h"
5+
#include "GameState.h"
6+
7+
// Forces a checkpoint but at a specific tick in the future chosen by the user.
8+
class ForceFutureCheckpoint : public IOptionalCheat
9+
{
10+
private:
11+
class ForceFutureCheckpointImpl;
12+
std::unique_ptr<ForceFutureCheckpointImpl> pimpl;
13+
14+
public:
15+
ForceFutureCheckpoint(GameState gameImpl, IDIContainer& dicon);
16+
~ForceFutureCheckpoint();
17+
18+
virtual std::string_view getName() override { return nameof(ForceFutureCheckpoint); }
19+
};
20+

HCMInternal/GUIElementConstructor.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUIFloat>
228228
createNestedElement(GUIElementEnum::dumpCoreGUI),
229229
createNestedElement(GUIElementEnum::dumpCoreSettingsSubheading),
230230
createNestedElement(GUIElementEnum::naturalCheckpointDisableGUI),
231+
createNestedElement(GUIElementEnum::forceFutureCheckpointGUI),
231232
}));
232233

233234
case GUIElementEnum::forceCheckpointGUI:
@@ -360,6 +361,29 @@ return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUIFloat>
360361
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUISimpleToggle<true>>
361362
(game, ToolTipCollection("Disables naturally occuring checkpoints (will break cutscenes)"), RebindableHotkeyEnum::naturalCheckpointDisable, "Disable Natural Checkpoints", settings->naturalCheckpointDisable));
362363

364+
case GUIElementEnum::forceFutureCheckpointGUI:
365+
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUISubHeading<false>>
366+
(game, ToolTipCollection("Useful for forcing checkpoints in coop, as to avoid a desync both players need to force a checkpoint on the exact same game tick"), "Force Future Checkpoint (co-op)", headerChildElements
367+
{
368+
createNestedElement(GUIElementEnum::forceFutureCheckpointToggle),
369+
createNestedElement(GUIElementEnum::forceFutureCheckpointTick),
370+
createNestedElement(GUIElementEnum::forceFutureCheckpointFill)
371+
}));
372+
373+
case GUIElementEnum::forceFutureCheckpointToggle:
374+
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUISimpleToggle<false>>
375+
(game, ToolTipCollection("When ticked, HCM will force a checkpoint on the below tick"), std::nullopt, "Force future checkpoint", settings->forceFutureCheckpointToggle));
376+
377+
case GUIElementEnum::forceFutureCheckpointTick:
378+
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUIInputInt>
379+
(game, ToolTipCollection("Which tick to force a checkpoint on (make sure you use the same value as your coop partner)"), "on tick:", settings->forceFutureCheckpointTick));
380+
381+
case GUIElementEnum::forceFutureCheckpointFill:
382+
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUISimpleButton<false>>
383+
(game, ToolTipCollection("Sets the tick for 5 seconds from now, giving you enough time to communicate with your coop partner which tick to use"), std::nullopt, "Set for 5s from now", settings->forceFutureCheckpointFillEvent));
384+
385+
386+
363387
case GUIElementEnum::cheatsHeadingGUI:
364388
return std::optional<std::shared_ptr<IGUIElement>>(std::make_shared<GUIHeading>
365389
(game, ToolTipCollection("Stuff like invulnerabiltiy, speedhack, teleport, ai disable etc"), "Useful Cheats", headerChildElements

HCMInternal/GUIRequiredServices.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ const std::map <GUIElementEnum, std::vector<OptionalCheatEnum>> GUIRequiredServi
103103
{GUIElementEnum::dumpCoreForcesSave,
104104
{OptionalCheatEnum::DumpCore, OptionalCheatEnum::ForceCoreSave}
105105
},
106+
107+
{ GUIElementEnum::forceFutureCheckpointGUI,
108+
{OptionalCheatEnum::ForceFutureCheckpoint, OptionalCheatEnum::ForceCheckpoint, OptionalCheatEnum::GameTickEventHook}
109+
},
110+
111+
106112
{GUIElementEnum::speedhackGUI,
107113
{OptionalCheatEnum::Speedhack}
108114
},

HCMInternal/GameTickEventHook.cpp

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,7 @@ class GameTickEventHookTemplated : public GameTickEventHook::GameTickEventHookIm
2323
{
2424
if (!instance) { PLOG_ERROR << "null GameTickEventHookTemplated instance"; return; }
2525
ScopedAtomicBool lock(gameTickHookRunning);
26-
int tickCount;
27-
if (!instance->tickCounter->readData(&tickCount))
28-
{
29-
LOG_ONCE(PLOG_ERROR << "tickIncrementHookFunction failed to resolve tickCounter");
30-
instance->gameTickEvent->operator()(0);
31-
}
32-
else
33-
{
34-
instance->gameTickEvent->operator()(tickCount);
35-
}
26+
instance->gameTickEvent->operator()(instance->getCurrentGameTick());
3627

3728
}
3829

@@ -55,6 +46,22 @@ class GameTickEventHookTemplated : public GameTickEventHook::GameTickEventHookIm
5546
return gameTickEvent;
5647
}
5748

49+
virtual int getCurrentGameTick() override
50+
{
51+
if (!instance) { PLOG_ERROR << "null GameTickEventHookTemplated instance"; return 0; }
52+
int tickCount;
53+
if (!instance->tickCounter->readData(&tickCount))
54+
{
55+
LOG_ONCE_CAPTURE(PLOG_ERROR << "getCurrentGameTick failed to resolve tickCounter, impl:" << game, game = instance->mGame.toString());
56+
LOG_ONCE_CAPTURE(PLOG_ERROR << " error: " << error, error = MultilevelPointer::GetLastError());
57+
return 0;
58+
}
59+
else
60+
{
61+
return tickCount;
62+
}
63+
}
64+
5865
~GameTickEventHookTemplated()
5966
{
6067
PLOG_DEBUG << "~GameTickEventHookTemplated";
@@ -94,4 +101,6 @@ GameTickEventHook::~GameTickEventHook()
94101
PLOG_DEBUG << "~" << getName();
95102
}
96103

97-
std::shared_ptr<eventpp::CallbackList<void(int)>> GameTickEventHook::getGameTickEvent() { return pimpl->getGameTickEvent(); }
104+
std::shared_ptr<eventpp::CallbackList<void(int)>> GameTickEventHook::getGameTickEvent() { return pimpl->getGameTickEvent(); }
105+
106+
int GameTickEventHook::getCurrentGameTick() { return pimpl->getCurrentGameTick(); }

HCMInternal/GameTickEventHook.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class GameTickEventHook : public IOptionalCheat
1111
public:
1212
virtual ~GameTickEventHookImpl() = default;
1313
virtual std::shared_ptr<eventpp::CallbackList<void(int)>> getGameTickEvent() = 0;
14+
virtual int getCurrentGameTick() = 0;
1415
};
1516
private:
1617
std::unique_ptr<GameTickEventHookImpl> pimpl;
@@ -20,6 +21,7 @@ class GameTickEventHook : public IOptionalCheat
2021
GameTickEventHook(GameState game, IDIContainer& dicon);
2122
~GameTickEventHook();
2223
std::shared_ptr<eventpp::CallbackList<void(int)>> getGameTickEvent();
24+
int getCurrentGameTick();
2325

2426
std::string_view getName() override { return nameof(GameTickEventHook); }
2527
};

HCMInternal/GuiElementEnum.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@
8181
((dumpCoreForcesSave, (Halo1)))\
8282
((dumpCoreGUI, (Halo1)))\
8383
((naturalCheckpointDisableGUI, (ALL_SUPPORTED_GAMES)))\
84+
((forceFutureCheckpointGUI, (ALL_SUPPORTED_GAMES)))\
85+
((forceFutureCheckpointToggle, (ALL_SUPPORTED_GAMES)))\
86+
((forceFutureCheckpointTick, (ALL_SUPPORTED_GAMES)))\
87+
((forceFutureCheckpointFill, (ALL_SUPPORTED_GAMES)))\
8488
((cheatsHeadingGUI, (ALL_SUPPORTED_GAMES)))\
8589
((speedhackGUI, (ALL_SUPPORTED_GAMES)))\
8690
((invulnGUI, (ALL_SUPPORTED_GAMES)))\

HCMInternal/HCMInternal.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ IF EXIST "C:\Program Files\LockHunter\LockHunter.exe" (
240240
<ClInclude Include="CheckpointInjectionLogger.h" />
241241
<ClInclude Include="DisableScreenEffects.h" />
242242
<ClInclude Include="EditPlayerViewAngle.h" />
243+
<ClInclude Include="ForceFutureCheckpoint.h" />
243244
<ClInclude Include="GetCurrentBSP.h" />
244245
<ClInclude Include="GetCurrentRNG.h" />
245246
<ClInclude Include="GUIComboEnumWithChildren.h" />
@@ -424,6 +425,7 @@ IF EXIST "C:\Program Files\LockHunter\LockHunter.exe" (
424425
<ClCompile Include="CheckpointInjectionLogger.cpp" />
425426
<ClCompile Include="DisableScreenEffects.cpp" />
426427
<ClCompile Include="EditPlayerViewAngle.cpp" />
428+
<ClCompile Include="ForceFutureCheckpoint.cpp" />
427429
<ClCompile Include="GetCurrentBSP.cpp" />
428430
<ClCompile Include="GetCurrentRNG.cpp" />
429431
<ClCompile Include="HideHUD.cpp" />

HCMInternal/HCMInternal.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@
614614
<ClInclude Include="CarrierBumpAnalyser.h">
615615
<Filter>Services\OptionalCheats\Implementations</Filter>
616616
</ClInclude>
617+
<ClInclude Include="ForceFutureCheckpoint.h">
618+
<Filter>Services\OptionalCheats\Implementations</Filter>
619+
</ClInclude>
617620
</ItemGroup>
618621
<ItemGroup>
619622
<ClCompile Include="dllmain.cpp">
@@ -868,6 +871,9 @@
868871
<ClCompile Include="CarrierBumpAnalyser.cpp">
869872
<Filter>Services\OptionalCheats\Implementations</Filter>
870873
</ClCompile>
874+
<ClCompile Include="ForceFutureCheckpoint.cpp">
875+
<Filter>Services\OptionalCheats\Implementations</Filter>
876+
</ClCompile>
871877
</ItemGroup>
872878
<ItemGroup>
873879
<None Include="cpp.hint" />

HCMInternal/OptionalCheatEnum.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ InjectCheckpoint,\
1717
DumpCheckpoint,\
1818
InjectCore,\
1919
DumpCore,\
20+
ForceFutureCheckpoint,\
2021
Speedhack,\
2122
Invulnerability,\
2223
GetCurrentDifficulty,\

0 commit comments

Comments
 (0)