summaryrefslogtreecommitdiff
path: root/mm/2s2h/Enhancements/DifficultyOptions/BossHealthMultiplier.cpp
blob: c63444f0ce7b2a2c7d9eb37d6b112c61e28d3916 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <libultraship/bridge/consolevariablebridge.h>
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h/ShipInit.hpp"
#include <unordered_map>

extern "C" {
#include "variables.h"
#include "functions.h"
}

#define CVAR_NAME "gEnhancements.DifficultyOptions.BossHealthMultiplier"
#define CVAR CVarGetInteger(CVAR_NAME, 0)

static std::unordered_map<Actor*, int> sLastBossHealth;

// Array mapping the UI combo box selection (0-4) directly to its corresponding float multiplier.
static const float sMultipliers[] = { 1.0f, 1.25f, 1.50f, 1.75f, 2.0f };

void HandleBossHealthMultiplier(Actor* actor) {
    if (gPlayState != NULL && Player_InBlockingCsMode(gPlayState, GET_PLAYER(gPlayState))) {
        return;
    }

    int currentHealth = actor->colChkInfo.health;

    if (currentHealth > 0) {

        if (sLastBossHealth.count(actor) == 0 || currentHealth > sLastBossHealth[actor]) {

            int option = CVAR;
            // Safety boundary check to prevent out-of-bounds array access if the CVAR contains an invalid value.
            if (option < 0 || option > 4)
                option = 0;
            float multiplier = sMultipliers[option];

            currentHealth = (s8)(currentHealth * multiplier);
            actor->colChkInfo.health = currentHealth;
        }

        sLastBossHealth[actor] = currentHealth;
    }
}

// Hooked to OnActorDestroy to automatically erase the boss from memory when the scene unloads.
void ClearBossHealthMultiplier(Actor* actor) {
    sLastBossHealth.erase(actor);
}

// Registers conditional, ID-specific hooks for both updating and destroying bosses.
// This optimizes performance by executing logic only for these 5 specific boss actors
// when the multiplier is active (CVAR > 0), and ensures clean memory management on unload.
void RegisterBossHealthMultiplier() {
    COND_ID_HOOK(OnActorUpdate, ACTOR_BOSS_01, CVAR > 0, HandleBossHealthMultiplier);
    COND_ID_HOOK(OnActorUpdate, ACTOR_BOSS_HAKUGIN, CVAR > 0, HandleBossHealthMultiplier);
    COND_ID_HOOK(OnActorUpdate, ACTOR_BOSS_03, CVAR > 0, HandleBossHealthMultiplier);
    COND_ID_HOOK(OnActorUpdate, ACTOR_BOSS_02, CVAR > 0, HandleBossHealthMultiplier);
    COND_ID_HOOK(OnActorUpdate, ACTOR_BOSS_07, CVAR > 0, HandleBossHealthMultiplier);

    COND_ID_HOOK(OnActorDestroy, ACTOR_BOSS_01, true, ClearBossHealthMultiplier);
    COND_ID_HOOK(OnActorDestroy, ACTOR_BOSS_HAKUGIN, true, ClearBossHealthMultiplier);
    COND_ID_HOOK(OnActorDestroy, ACTOR_BOSS_03, true, ClearBossHealthMultiplier);
    COND_ID_HOOK(OnActorDestroy, ACTOR_BOSS_02, true, ClearBossHealthMultiplier);
    COND_ID_HOOK(OnActorDestroy, ACTOR_BOSS_07, true, ClearBossHealthMultiplier);
}

static RegisterShipInitFunc initFunc(RegisterBossHealthMultiplier, { CVAR_NAME });