1#include "Game/DLC/aocHardModeManager.h"
2#include <math/seadMathCalcCommon.h>
3#include "KingSystem/ActorSystem/actActor.h"
4#include "KingSystem/ActorSystem/actActorUtil.h"
5#include "KingSystem/ActorSystem/actTag.h"
6#include "KingSystem/Utils/InitTimeInfo.h"
7
8namespace uking::aoc {
9
10SEAD_SINGLETON_DISPOSER_IMPL(HardModeManager)
11
12namespace {
13struct aoc2StaticData {
14 ksys::util::InitConstants init_constants;
15 sead::SafeString flag_name_HardMode_HighScore{"HardMode_HighScore"};
16 sead::SafeString flag_name_AoC_HardMode_Enabled{"AoC_HardMode_Enabled"};
17 sead::SafeString flag_name_IsLastPlayHardMode{"IsLastPlayHardMode"};
18};
19
20aoc2StaticData sData;
21sead::FixedSafeString<64> sStr{""};
22} // namespace
23
24HardModeManager::HardModeManager()
25 : mGdtResetSlot(this, &HardModeManager::setHardModeEnabledFlag),
26 mGdtReinitSlot(this, &HardModeManager::initFlagHandles) {
27 mMultipliers.fill(0.5);
28
29 setHardModeChange(HardModeChange::IsLastPlayHardMode, true);
30 setHardModeChange(HardModeChange::EnableShorterEnemyNotice, true);
31 setHardModeChange(HardModeChange::EnableLifeRegen, true);
32 setHardModeChange(HardModeChange::DisableNoDeathDamage, true);
33 setHardModeChange(HardModeChange::PatchGanonStunLock, true);
34 setHardModeChange(HardModeChange::RandomizeGuardianChargeBeam, true);
35 setHardModeChange(HardModeChange::ApplyDamageMultiplier, true);
36}
37
38void HardModeManager::setHardModeEnabledFlag(ksys::gdt::Manager::ResetEvent*) {
39 ksys::gdt::Manager::instance()->setBool(true, mAoCHardModeEnabledFlag);
40 mGdtResetSlot.release();
41}
42
43void HardModeManager::initFlagHandles(ksys::gdt::Manager::ReinitEvent*) {
44 auto* gdm = ksys::gdt::Manager::instance();
45 mHardModeHighScoreFlag = gdm->getS32Handle(sData.flag_name_HardMode_HighScore);
46 mAoCHardModeEnabledFlag = gdm->getBoolHandle(sData.flag_name_AoC_HardMode_Enabled);
47 mIsLastPlayHardModeFlag = gdm->getBoolHandle(sData.flag_name_IsLastPlayHardMode);
48}
49
50HardModeManager::~HardModeManager() {
51 mFileHandle.requestUnload2();
52 if (_120) {
53 // TODO: use the normal operator delete once we figure out what _120 is
54 ::operator delete(_120);
55 _120 = nullptr;
56 }
57}
58
59void HardModeManager::init(sead::Heap*) {
60 init_();
61}
62
63void HardModeManager::init_() {
64 initFlagHandles();
65 ksys::gdt::Manager::instance()->addReinitCallback(mGdtReinitSlot);
66}
67
68void HardModeManager::nerfHpRestore(f32* hp) const {
69 *hp = sead::Mathf::clampMin(*hp * getMultiplier(MultiplierType::HpRestore), 1.0f);
70}
71
72void HardModeManager::nerfHpRestore(s32* hp) const {
73 *hp = sead::Mathi::clampMin(*hp * getMultiplier(MultiplierType::HpRestore), 1);
74}
75
76void HardModeManager::modifyEnemyNoticeDuration(f32* value) const {
77 *value = sead::Mathf::clampMin(*value * getMultiplier(MultiplierType::EnemyNoticeDuration), 0);
78}
79
80bool HardModeManager::shouldCreateLifeRecoverInfo(ksys::act::Actor* actor) {
81 // Health regen should only apply to enemy actors.
82 if (!ksys::act::isEnemyProfile(actor))
83 return false;
84
85 // But not to wolves or bears...
86 if (ksys::act::isWolfOrBear(actor))
87 return false;
88
89 // and not to Dark Beast Ganon...
90 if (actor->getName() == "Enemy_GanonBeast")
91 return false;
92
93 // and not to enemy swarms...
94 if (actor->getProfile() == "EnemySwarm")
95 return false;
96
97 return actor->getMaxLife() > 1;
98}
99
100bool HardModeManager::shouldApplyMasterModeDamageMultiplier(
101 const ksys::act::ActorConstDataAccess& accessor) {
102 if (!accessor.hasProc())
103 return false;
104
105 ksys::act::ActorConstDataAccess parent;
106 if (accessor.hasConnectedCalcParent() && accessor.acquireConnectedCalcParent(&parent))
107 return shouldApplyMasterModeDamageMultiplier(parent);
108
109 if (accessor.hasTag(ksys::act::tags::IsMasterModeDamageMultiplierActor) ||
110 (HardModeManager::instance() && HardModeManager::instance()->isTestOfStrengthShrine() &&
111 accessor.hasTag(ksys::act::tags::AncientGuardTarget))) {
112 return true;
113 }
114
115 const sead::SafeString& profile = accessor.getProfile();
116 const sead::SafeString& name = accessor.getName();
117
118 if (profile == "LastBoss" || profile == "SiteBoss")
119 return true;
120
121 if (name == "Enemy_GanonBeast" || name == "GanonShockWave" || name == "EnemyGanonShockWave" ||
122 name == "GanonSeaOfFlame" || name == "GanonFlameBall" || name == "GanonPillarOfFlame" ||
123 name == "GanonNormalArrow" || name == "GanonSpearForThrowing" || name == "CurseGanonBeam" ||
124 name == "GanonBeam" || name == "GanonIceBullet" || name == "GanonThunder" ||
125 name == "GanonIronPile" || name == "GanonTornado" || name == "GanonBeastBeam" ||
126 name == "SiteBossSeaOfFlame" || name == "SiteBossSeaOfFlameRotate" ||
127 name == "SiteBossFlameBall" || name == "SiteBossBigFlameBall" ||
128 name == "SiteBossPillarOfFlame" || name == "SiteBossWearFlame" ||
129 name == "SiteBossDrawingFlameTornado" || name == "SiteBossGaleArrow" ||
130 name == "SiteBossNormalArrow" || name == "SiteBossSpearForThrowing" ||
131 name == "SiteBossReflectArrow" || name == "ArrowRainChild" ||
132 name == "SiteBossSpearIceBullet" || name == "SiteBossTornado" ||
133 name == "LastBossThunder" || name == "Enemy_Assassin_Senior" ||
134 name == "AssassinRockBall" || name == "AssassinIronBall") {
135 return true;
136 }
137
138 return false;
139}
140
141void HardModeManager::buffDamage(s32& damage) {
142 damage = damage * 1.5f;
143 if (damage == 1) {
144 damage = 2;
145 }
146}
147
148void HardModeManager::loadIsLastPlayHardModeFlag() {
149 bool value{};
150 ksys::gdt::Manager::instance()->getBool(mIsLastPlayHardModeFlag, &value);
151 const bool x = value;
152#ifdef MATCHING_HACK_NX_CLANG
153 asm("");
154#endif
155 setFlag(Flag::EnableHardMode, x);
156}
157
158void HardModeManager::loadIsHardModeFlag() {
159 bool value{};
160 ksys::gdt::Manager::instance()->getBool(mAoCHardModeEnabledFlag, &value);
161 const bool x = value;
162#ifdef MATCHING_HACK_NX_CLANG
163 asm("");
164#endif
165 setFlag(Flag::EnableHardMode, x);
166}
167
168void HardModeManager::storeIsLastPlayHardModeFlag() {
169 ksys::gdt::Manager::instance()->setBool(checkFlag(Flag::EnableHardMode),
170 mIsLastPlayHardModeFlag);
171}
172
173void HardModeManager::resetIsLastPlayHardModeFlag() {
174 ksys::gdt::Manager::instance()->setBool(false, "IsLastPlayHardMode");
175}
176
177bool HardModeManager::isTestOfStrengthShrine() const {
178 if (mMapType != "CDungeon")
179 return false;
180
181 // There are 20 ToS shrines in the base game (070-089).
182 if (mMapName.startsWith("Dungeon07") || mMapName.startsWith("Dungeon08"))
183 return true;
184
185 // Ruvo Korbah
186 if (mMapName == "Dungeon135")
187 return true;
188
189 return false;
190}
191
192void HardModeManager::calc() {
193 volatile u32 unused = 0;
194 static_cast<void>(unused);
195}
196
197bool HardModeManager::rankUpEnemy(const sead::SafeString& actor_name, const ksys::map::Object& obj,
198 const char** new_name) {
199 if (obj.getFlags().isOn(ksys::map::Object::Flag::HasUniqueName) ||
200 obj.getHardModeFlags().isOn(ksys::map::Object::HardModeFlag::DisableRankup)) {
201 return false;
202 }
203
204 if (!actor_name.startsWith("Enemy"))
205 return false;
206
207 if (isTestOfStrengthShrine())
208 return false;
209
210 sead::SafeString next = "";
211
212 /// @bug Yes, this is duplicated and shouldn't exist.
213 if (!actor_name.startsWith("Enemy"))
214 return false;
215
216 if (actor_name == "Enemy_Assassin_Junior")
217 next = "Enemy_Assassin_Middle";
218 else if (actor_name == "Enemy_Assassin_Middle")
219 next = "Enemy_Assassin_Senior";
220 else if (actor_name == "Enemy_Assassin_Shooter_Junior")
221 next = "Enemy_Assassin_Shooter_Azito_Junior";
222
223 else if (actor_name == "Enemy_Bokoblin_Junior")
224 next = "Enemy_Bokoblin_Middle";
225 else if (actor_name == "Enemy_Bokoblin_Middle")
226 next = "Enemy_Bokoblin_Senior";
227 else if (actor_name == "Enemy_Bokoblin_Senior")
228 next = "Enemy_Bokoblin_Dark";
229 else if (actor_name == "Enemy_Bokoblin_Dark")
230 next = "Enemy_Bokoblin_Gold";
231
232 else if (actor_name == "Enemy_Bokoblin_Guard_Junior")
233 next = "Enemy_Bokoblin_Guard_Middle";
234
235 else if (actor_name == "Enemy_Bokoblin_Guard_Junior_Ambush")
236 next = "Enemy_Bokoblin_Guard_Middle_Ambush";
237
238 else if (actor_name == "Enemy_Bokoblin_Guard_Junior_TreeHouseTop")
239 next = "Enemy_Bokoblin_Guard_Middle_TreeHouseTop";
240
241 /// @bug Yes, this is duplicated and shouldn't exist.
242 else if (actor_name == "Enemy_Bokoblin_Guard_Junior_Ambush")
243 next = "Enemy_Bokoblin_Guard_Middle_Ambush";
244
245 else if (actor_name == "Enemy_Chuchu_Electric_Junior")
246 next = "Enemy_Chuchu_Electric_Middle";
247 else if (actor_name == "Enemy_Chuchu_Electric_Middle")
248 next = "Enemy_Chuchu_Electric_Senior";
249
250 else if (actor_name == "Enemy_Chuchu_Fire_Junior")
251 next = "Enemy_Chuchu_Fire_Middle";
252 else if (actor_name == "Enemy_Chuchu_Fire_Middle")
253 next = "Enemy_Chuchu_Fire_Senior";
254
255 else if (actor_name == "Enemy_Chuchu_Ice_Junior")
256 next = "Enemy_Chuchu_Ice_Middle";
257 else if (actor_name == "Enemy_Chuchu_Ice_Middle")
258 next = "Enemy_Chuchu_Ice_Senior";
259
260 else if (actor_name == "Enemy_Chuchu_Junior")
261 next = "Enemy_Chuchu_Middle";
262 else if (actor_name == "Enemy_Chuchu_Middle")
263 next = "Enemy_Chuchu_Senior";
264
265 else if (actor_name == "Enemy_Giant_Junior")
266 next = "Enemy_Giant_Middle";
267 else if (actor_name == "Enemy_Giant_Middle")
268 next = "Enemy_Giant_Senior";
269
270 else if (actor_name == "Enemy_Golem_Junior")
271 next = "Enemy_Golem_Middle";
272 else if (actor_name == "Enemy_Golem_Middle")
273 next = "Enemy_Golem_Senior";
274
275 else if (actor_name == "Enemy_Guardian_Mini_Baby")
276 next = "Enemy_Guardian_Mini_Junior";
277 else if (actor_name == "Enemy_Guardian_Mini_Junior")
278 next = "Enemy_Guardian_Mini_Middle";
279 else if (actor_name == "Enemy_Guardian_Mini_Middle")
280 next = "Enemy_Guardian_Mini_Senior";
281
282 else if (actor_name == "Enemy_Guardian_Mini_Junior_DetachLineBeam")
283 next = "Enemy_Guardian_Mini_Middle_DetachLineBeam";
284
285 else if (actor_name == "Enemy_Lizalfos_Junior")
286 next = "Enemy_Lizalfos_Middle";
287 else if (actor_name == "Enemy_Lizalfos_Middle")
288 next = "Enemy_Lizalfos_Senior";
289 else if (actor_name == "Enemy_Lizalfos_Senior")
290 next = "Enemy_Lizalfos_Dark";
291 else if (actor_name == "Enemy_Lizalfos_Dark")
292 next = "Enemy_Lizalfos_Gold";
293
294 else if (actor_name == "Enemy_Lizalfos_Guard_Junior")
295 next = "Enemy_Lizalfos_Guard_Middle";
296
297 else if (actor_name == "Enemy_Lizalfos_Guard_Junior_LongVisibility")
298 next = "Enemy_Lizalfos_Guard_Middle_LongVisibility";
299
300 else if (actor_name == "Enemy_Lizalfos_Junior_Guard_Ambush")
301 next = "Enemy_Lizalfos_Middle_Guard_Ambush";
302
303 else if (actor_name == "Enemy_Lynel_Junior")
304 next = "Enemy_Lynel_Middle";
305 else if (actor_name == "Enemy_Lynel_Middle")
306 next = "Enemy_Lynel_Senior";
307 else if (actor_name == "Enemy_Lynel_Senior")
308 next = "Enemy_Lynel_Dark";
309 else if (actor_name == "Enemy_Lynel_Dark")
310 next = "Enemy_Lynel_Gold";
311
312 else if (actor_name == "Enemy_Moriblin_Junior")
313 next = "Enemy_Moriblin_Middle";
314 else if (actor_name == "Enemy_Moriblin_Middle")
315 next = "Enemy_Moriblin_Senior";
316 else if (actor_name == "Enemy_Moriblin_Senior")
317 next = "Enemy_Moriblin_Dark";
318 else if (actor_name == "Enemy_Moriblin_Dark")
319 next = "Enemy_Moriblin_Gold";
320
321 else if (actor_name == "Enemy_Wizzrobe_Electric")
322 next = "Enemy_Wizzrobe_Electric_Senior";
323 else if (actor_name == "Enemy_Wizzrobe_Fire")
324 next = "Enemy_Wizzrobe_Fire_Senior";
325 else if (actor_name == "Enemy_Wizzrobe_Ice")
326 next = "Enemy_Wizzrobe_Ice_Senior";
327
328 if (next.isEmpty())
329 return false;
330
331 *new_name = next.cstr();
332 return true;
333}
334
335} // namespace uking::aoc
336