Skip to content

Commit

Permalink
Include twinmold fixes from project restoration. (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
PhlexPlexico authored Feb 28, 2024
1 parent 29388ce commit 088ee2c
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 10 deletions.
185 changes: 185 additions & 0 deletions code/include/game/actors/boss/twinmold.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#ifndef _GAME_ACTORS_BOSS_TWINMOLD_H
#define _GAME_ACTORS_BOSS_TWINMOLD_H
#include <cstddef>

#include "common/types.h"
#include "common/utils.h"
#include "game/actor.h"
#include "game/collision.h"
#include "z3d/z3DVec.h"

namespace game {
class GlobalContext;
}

namespace game::act {

struct BossTwinmold : Actor {
// Probably incomplete.
enum class Status : u16 {
Buried = 0,
BlueRisingOutOfSand = 1,
// Also resets the hit counter.
RedBurrowingIntoSand = 3,

Flying = 4,
Unk6 = 6,
// The most commonly seen state for Blue Twinmold.
FlyingAimlessly = 7,

Unk8 = 8,
Unk9 = 9,

// These states are entered after receiving enough hits.
BlueStunnedByShootingEyes = 11,
BlueStunnedEyeOut = 12,
BlueStunnedBurrowingIntoSand = 13,

Stunned = 15,
StunnedAndOnGround = 16,

TauntingLink = 18,
AfterTaunting = 19,

// Only for Red Twinmold?
TauntingAndAttacking = 21,
AfterTauntingAndAttacking = 22,
FlyingAndAttacking = 23,

BeingGrabbedByLink = 24,
BeingChokedByLink = 25,

PreparingToRiseOutOfSand = 26,

// ?
AfterTaunting2 = 98,

Inactive = 100,
FirstTimeRisingOutOfSand = 102,

DyingStart = 200,
DyingExploding = 201,
DyingFallingToGround = 202,
DyingTouchedGround = 203,
};

void* resource;
Status status;
u16 some_status_change_countdown;
u16 field_200;
u16 field_202;
u16 frame_counter;
u16 field_206;
u16 field_208;
u16 field_20A;
u16 field_20C;
u8 gap_20E[14];
u16 field_21C;
u8 gap_21E[14];
z3dVec3f field_22C;
z3dVec3f field_238;
u8 gap244[1];
u8 field_245;
u8 gap246[3];
u8 gap_249[27];
float field_264;
float field_268;
signed int field_26C;
z3dVec3f field_270;
u8 gap27C[15748];
u32 field_4000;
u8 gap_4004[3448];
u32 field_4D7C;
u32 field_4D80;
u8 gap_4D84[56];
u32 status_anim;
u8 gap_4DC0[8];
float field_4DC8;
signed int field_4DCC;
u8 gap_4DD0[560];
u32 field_5000;
u8 gap_5004[3448];
z3dVec3f field_5D7C;
z3dVec3f field_5D88;
z3dVec3f field_5D94;
z3dVec3f field_5DA0;
u8 gap5DAC[120];
void (*field_5E24)(BossTwinmold*, GlobalContext*);
/// Points to Blue Twinmold for Red Twinmold, and vice versa.
BossTwinmold* other_twinmold_actor;
int field_5E2C;
u8 gap_5E30[12];
/// one for each segment of Twinmold's body,
/// starting at the head (0) and ending at the tail (13)
CollisionBodies<CollisionBodyCylinderCollection, 14> collision_1;
CollisionBodies<CollisionBodyCylinderCollection, 14> collision_main;
CollisionBodyCylinder collision_2;
u8 gap_6794[8];
u32 field_679C;
u16 field_67A0;
u8 gap_67A2[26];
float field_67BC;
u16 field_67C0;
signed int field_67C4;
u8 gap_67C8[44];
float field_67F4;
float field_67F8;
u8 gap_67FC[8];
int (*field_6804)(BossTwinmold*, GlobalContext*, act::Actor*);
u8 gap_6808[6136];
u32 field_8000;
u8 gap_8004[784];
char field_8314;
char field_8315;
char field_8316;
char field_8317;
u8 gap_8318[3304];
u32 field_9000;
u8 gap_9004[208];
char field_90D4;
__attribute__((aligned(4))) u8 field_90D8;
u8 gap_90D9[11];
u16 hit_counter;
u16 field_90E6;
u8 gap_90E8[12];
u32 field_90F4;
int field_90F8;
u8 gap_90FC[16];
u32 field_910C;
u32 field_9110;
u8 gap_9114[4];
u32 field_9118;
u8 gap_911C[8];
u32 field_9124;
u8 gap_9128[4];
float eye_scale;
float field_9130;
u8 gap_9134[8];
float field_913C;
u8 gap_9140[24];
z3dVec3f field_9158;
z3dVec3f field_9164;
u8 gap9170[36];
CollisionBodies<CollisionBodyCylinderCollection, 1> collision_eye;
CollisionBodies<CollisionBodyCylinderCollection, 1> collision_3;
u32 field_9274;
u8 gap_9278[308];
u32 field_93AC;
u8 gap_93B0[308];
CollisionBodies<CollisionBodyCylinderCollection, 1> collision_4;
CollisionBodies<CollisionBodyCylinderCollection, 1> collision_5;
z3dVec3f field_95C4;
u8 gap_95D0[8];
int field_95D8;
int field_95DC;
int field_95E0;
u8 gap_95E4[60];
int field_9620;
int field_9624;
};
static_assert(sizeof(BossTwinmold) == 0x9628);
static_assert(offsetof(BossTwinmold, other_twinmold_actor) == 0x5E28);
static_assert(offsetof(BossTwinmold, gap_93B0) == 0x93B0);

} // namespace game::act
#endif
23 changes: 23 additions & 0 deletions code/include/rnd/boss.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef _RND_BOSS_H_
#define _RND_BOSS_H_
#include "common/advanced_context.h"
#include "rnd/settings.h"
#include "game/actors/boss/twinmold.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <optional>
#include <type_traits>
#if defined ENABLE_DEBUG || defined DEBUG_PRINT
#include "common/debug.h"
extern "C" {
#include <3ds/svc.h>
}
#endif

namespace rnd {
void FixBosses();
} // namespace rnd

#endif
8 changes: 6 additions & 2 deletions code/mm.ld
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ SECTIONS{
*(.patch_FixSurroundSound)
}

.patch_PostActorCalc 0x174E08 : {
*(.patch_PostActorCalc)
}

.patch_RemoveCouplesMaskMessage 0x1867C4 : {
*(.patch_RemoveCouplesMaskMessage)
}
Expand Down Expand Up @@ -271,9 +275,9 @@ SECTIONS{
*(.patch_EnteringLocation)
}

/* .patch_TwinmoldConsistentDamage 0x28E544 : {
.patch_TwinmoldConsistentDamage 0x28E544 : {
*(.patch_TwinmoldConsistentDamage)
} */
}

.patch_FasterBlockMovement 0x2AC634 : {
*(.patch_FasterBlockMovement)
Expand Down
10 changes: 10 additions & 0 deletions code/source/asm/boss_hooks.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.arm
.text

.global hook_PostActorCalc
hook_PostActorCalc:
push {r0-r12, lr}
bl PostActorCalc @ found in main.cpp
pop {r0-r12,lr}
add r3,r5,#0x10
bx lr
6 changes: 6 additions & 0 deletions code/source/asm/boss_patches.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.arm

.section .patch_PostActorCalc
.global patch_PostActorCalc
patch_PostActorCalc:
bl hook_PostActorCalc
8 changes: 4 additions & 4 deletions code/source/asm/patches.s
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ patch_HandleOcarinaHooks:
b hook_HandleOcarina

@ Remove call from twinmold->life -= twinmold_min_damage.
@ .section .patch_TwinmoldConsistentDamage
@ .global patch_TwinmoldConsistentDamage
@ patch_TwinmoldConsistentDamage:
@ nop
.section .patch_TwinmoldConsistentDamage
.global patch_TwinmoldConsistentDamage
patch_TwinmoldConsistentDamage:
nop

.section .patch_FasterBlockMovement
.global patch_FasterBlockMovement
Expand Down
5 changes: 5 additions & 0 deletions code/source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "game/states/state.h"
#include "game/ui.h"
#include "game/ui/screens/gearscreen.h"
#include "rnd/boss.h"
#include "rnd/extdata.h"
#include "rnd/icetrap.h"
#include "rnd/input.h"
Expand Down Expand Up @@ -142,6 +143,10 @@ namespace rnd {
__init_array_start[i]();
}
}

void PostActorCalc() {
FixBosses();
}
}

} // namespace rnd
93 changes: 93 additions & 0 deletions code/source/rnd/boss.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "rnd/boss.h"
/**
* @file boss.cpp
* @author leoetlino (https://github.com/leoetlino/)
* @brief
* @date 2021-09-15
*
* Brought in from the Project Restoration libraries. Edited to adjust for the randomizer.
*/
namespace rnd {

struct TwinmoldFixState {
s8 blue_prev_life;
s8 red_prev_life;
game::act::BossTwinmold::Status red_prev_status;
u16 red_prev_hit_counter;
bool is_hit_counter_sane;
};

void FixTwinmold() {
static std::optional<TwinmoldFixState> state{};
const game::GlobalContext* gctx = GetContext().gctx;

auto* red_twinmold =
gctx->FindActorWithId<game::act::BossTwinmold>(game::act::Id::BossTwinmold, game::act::Type::Boss);
if (!red_twinmold) {
state.reset();
return;
}

auto* blue_twinmold = red_twinmold->other_twinmold_actor;

if (state) {
// Red Twinmold has 12 HP (after killing their blue friend).
//
// Spinning it deals 3-5 damage points based on lin_vel_xxx:
// boss->life -= 3 + (5 - 2) * player->lin_vel_xxx;
// It is possible to deal 5 damage to the boss by spinning the main stick,
// but that's not obvious at all...
//
// If the player spins the main stick (which is not an obvious thing to do...),
// killing Red Twinmold takes 3 identical cycles.
// If not, 4 cycles (!) are required.
//
// Let's make that less tedious and less boring by reducing the number of required cycles
// (1 if the player touches the stick, 2 otherwise).
if (state->red_prev_life > red_twinmold->life) {
#if defined ENABLE_DEBUG || defined DEBUG_PRINT
util::Print("%s: dealing more damage to Red Twinmold\n", __func__);
#endif
red_twinmold->life -= 8;
}

// Only update the hit counter if it is sane. One way of ensuring that condition is satisfied
// is to only consider the counter to be sane after the player has hit Twinmold once.
if (red_twinmold->hit_counter == 8)
state->is_hit_counter_sane = true;

// 10 hits are required to stun Red or Blue Twinmold. This would have been acceptable
// if it weren't for the fact that Red Twinmold regularly burrows back into sand during phase
// 2 and the hit counter is reset every time that happens. This makes for a confusing
// experience the first time the player fights Twinmold, as there is nothing in the game that
// indicates that the hit counter resets every time (and it's still frustrating on subsequent
// playthroughs).
//
// Fix that by restoring the previous hit counter after it's been reset by the game.
const bool was_reset = red_twinmold->hit_counter == 9 && state->red_prev_hit_counter != 9;
const bool is_legit_reset = red_twinmold->status == game::act::BossTwinmold::Status::Stunned;
if (state->is_hit_counter_sane && was_reset && !is_legit_reset) {
#if defined ENABLE_DEBUG || defined DEBUG_PRINT
util::Print("%s: restoring hit counter (%u)\n", __func__, state->red_prev_hit_counter);
#endif

red_twinmold->hit_counter = state->red_prev_hit_counter;
}
} else {
#if defined ENABLE_DEBUG || defined DEBUG_PRINT
util::Print("%s: initialising state\n", __func__);
#endif
state.emplace();
state->is_hit_counter_sane = false;
}

state->blue_prev_life = blue_twinmold->life;
state->red_prev_life = red_twinmold->life;
state->red_prev_status = red_twinmold->status;
if (state->is_hit_counter_sane)
state->red_prev_hit_counter = red_twinmold->hit_counter;
}
void FixBosses() {
FixTwinmold();
}
} // namespace rnd
Loading

0 comments on commit 088ee2c

Please sign in to comment.