Skip to content

Commit

Permalink
[Decompilation] [th01] Missiles: Unput/update/render function
Browse files Browse the repository at this point in the history
And that's why these feel so awful in-game: Interlaced rendering, and
an unfair 46×46 hitbox around Reimu's center point.

Part of P0165, funded by Ember2528.
  • Loading branch information
nmlgc committed Nov 7, 2021
1 parent bce7706 commit b4a6432
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 344 deletions.
168 changes: 168 additions & 0 deletions th01/main/bullet/missile.cpp
@@ -0,0 +1,168 @@
extern "C" {
#include "planar.h"
#include "th01/math/overlap.hpp"
#include "th01/hardware/egc.h"
#include "th01/hardware/input.hpp"
#include "th01/snd/mdrv2.h"
#include "th01/formats/ptn.hpp"
}
#include "th01/main/vars.hpp"
#include "th01/main/bullet/missile.hpp"

// Missiles are blitted to unaligned X positions (and are, in fact, the only
// 16×16 .PTN entity where this is the case), so a missile sprite is likely
// to cover two 16-pixel words. Since the left coordinate is rounded down to
// the previous multiple of 16, the unblitting width therefore has to be
// doubled.
// … Except that it doesn't have to, because egc_copy_rect_1_to_0_16() already
// ceils the unblitted width to the next multiple of 16. Which makes this
// addition just as unnecessary as the _word_w() wrapper.
#define sloppy_unput(missiles, i) { \
egc_copy_rect_1_to_0_16_word_w( \
(missiles).prev_left[i].to_pixel(), \
(missiles).prev_top[i].to_pixel(), \
(15 + MISSILE_W), \
MISSILE_H \
); \
}

#define in_current_interlace_field(i) \
((i % 2) == (frame_rand & 1))

// Calculates the current [ptn_id] and [quarter] for the missile at the given
// index.
// TODO: Should be turned into a class method once it can be part of this
// translation unit.
void ptn_cel_for(CMissiles& that, int i, main_ptn_id_t& ptn_id, int& quarter);

void CMissiles::unput_update_render(void)
{
int i;

// Unput/update/explode
for(i = 0; i < MISSILE_COUNT; i++) {
if(flag[i] == MF_FREE) {
continue;
}
if(in_current_interlace_field(i) && (prev_left[i].v != MISSILE_NEW)) {
sloppy_unput(*this, i);
}
if(flag[i] == MF_MOVING) {
cur_left[i].v += velocity_x[i].v;
cur_top[i].v += velocity_y[i].v;

if(!overlap_xy_lrtb_le_ge(
cur_left[i].to_pixel(),
cur_top[i].to_pixel(),
MISSILE_LEFT_MIN,
MISSILE_TOP_MIN,
MISSILE_LEFT_MAX,
MISSILE_TOP_MAX
)) {
flag[i] = MF_HIT;
sloppy_unput(*this, i);

if(cur_left[i].to_pixel() < MISSILE_LEFT_MIN) {
prev_left[i].v = to_sp(MISSILE_LEFT_MIN);
} else if(cur_left[i].to_pixel() > MISSILE_LEFT_MAX) {
prev_left[i].v = to_sp(MISSILE_LEFT_MAX);
}
if(cur_top[i].to_pixel() < MISSILE_TOP_MIN) {
prev_top[i].v = to_sp(MISSILE_TOP_MIN);
} else if(cur_top[i].to_pixel() > MISSILE_TOP_MAX) {
prev_top[i].v = to_sp(PLAYFIELD_BOTTOM - (MISSILE_H / 2));
}
}
} else {
if(in_current_interlace_field(i)) {
static_cast<int8_t>(flag[i])++;
}
if(flag[i] > MF_HIT_last) {
flag[i] = MF_FREE;
sloppy_unput(*this, i);
mdrv2_se_play(7);
}
}
}

// Render
for(i = 0; i < MISSILE_COUNT; i++) {
if((flag[i] == MF_FREE) || !in_current_interlace_field(i)) {
continue;
} else if(flag[i] == MF_MOVING) {
main_ptn_id_t ptn_id;
int quarter;

/* TODO: Replace with the decompiled calls
* ptn_cel_for(i, ptn_id, quarter);
* ptn_put_quarter(
* cur_left[i].to_pixel(), cur_top[i].to_pixel(), ptn_id, quarter
* );
* once ptn_cel_for() is part of this translation unit */
__asm {
push ss;
lea ax, quarter;
push ax
push ss;
lea ax, ptn_id;
push ax
db 0x56; // PUSH SI
db 0x66, 0xFF, 0x76, 0x06; // PUSH LARGE [this]
push cs;
call near ptr ptn_cel_for;
push quarter;
push ptn_id;
}
_AX = cur_top[i].to_pixel();
__asm {
push ax;
}
_AX = ((Subpixel *)(
(uint8_t __seg *)(_ES) +
(uint8_t __near *)(_BX) +
(uint16_t)&(((CMissiles __near *)0)->cur_left)
))->to_pixel();
__asm {
push ax;
call far ptr ptn_put_quarter
add sp, 22
}
prev_left[i] = cur_left[i];
prev_top[i] = cur_top[i];
} else { // >= MF_HIT
ptn_put_quarter(
prev_left[i].to_pixel(),
prev_top[i].to_pixel(),
(ptn_id_base + MISSILE_HIT_IMAGE),
(flag[i] - MF_HIT)
);
}
}

// Collision detection
if(player_invincible) {
return;
}
for(i = 0; i < MISSILE_COUNT; i++) {
if(flag[i] == MF_FREE) {
continue;
}

// 46×46 pixels around the player's center point
enum {
HITBOX_OFFSET_LEFT = (-(MISSILE_W / 2)),
HITBOX_OFFSET_RIGHT = ((PLAYER_W / 2) + (MISSILE_W / 2)),
HITBOX_OFFSET_TOP = (-(MISSILE_H / 2)),
HITBOX_OFFSET_BOTTOM = (PLAYER_H)
};
if(
(cur_left[i].to_pixel() > (player_left + HITBOX_OFFSET_LEFT)) &&
(cur_left[i].to_pixel() < (player_left + HITBOX_OFFSET_RIGHT)) &&
(cur_top[i].to_pixel() < (player_top + HITBOX_OFFSET_BOTTOM)) &&
(cur_top[i].to_pixel() > (player_top + HITBOX_OFFSET_TOP))
) {
done = true;
return;
}
}
}
3 changes: 3 additions & 0 deletions th01/main/bullet/missile.hpp
Expand Up @@ -40,6 +40,9 @@ class CMissiles {
int8_t unknown[MISSILE_COUNT];
uint8_t ptn_id_base; // main_ptn_id_t. Very bold to limit this to 8 bits!
missile_flag_t flag[MISSILE_COUNT];

public:
void unput_update_render(void);
};

extern CMissiles Missiles;
1 change: 1 addition & 0 deletions th01/main_32_.cpp
Expand Up @@ -8,5 +8,6 @@ extern "C" {
#include "th01/main/player/player.hpp"
}

#include "th01/main/bullet/missile.cpp"
#include "th01/main/player/bomb_d_f.cpp"
#include "th01/main/particle.cpp"

0 comments on commit b4a6432

Please sign in to comment.