ReC98/th04/main/bullet/update.cpp

505 lines
14 KiB
C++
Raw Normal View History

#pragma option -G
#include "platform.h"
#include "pc98.h"
#include "planar.h"
#include "master.hpp"
#include "th01/math/overlap.hpp"
#include "th01/math/subpixel.hpp"
#include "th04/math/motion.hpp"
extern "C" {
#include "th04/math/vector.hpp"
#include "th04/main/frames.h"
#include "th04/main/scroll.hpp"
#include "th04/main/playfld.hpp"
#include "th04/main/drawp.hpp"
#include "th04/main/playperf.hpp"
#include "th04/main/rank.hpp"
#include "th04/main/score.hpp"
#include "th04/main/slowdown.hpp"
#include "th04/main/spark.hpp"
#include "th04/main/bullet/bullet.hpp"
#include "th04/main/bullet/clearzap.hpp"
#include "th04/main/player/player.hpp"
}
#include "th04/main/hud/hud.hpp"
#include "th04/main/hud/overlay.hpp"
#include "th04/main/pointnum/pointnum.hpp"
#include "th04/main/gather.hpp"
#if (GAME == 5)
#include "th04/main/item/item.hpp"
#include "th05/sprites/main_pat.h"
static const int SLOWDOWN_BULLET_THRESHOLD_UNUSED = 32;
// Updates [bullet]'s patnum based on its current angle.
void pascal near bullet_update_patnum(bullet_t near &bullet)
{
unsigned char patnum_base; // main_patnum_t, obviously
if(bullet.patnum < PAT_BULLET16_D) {
return;
}
if(bullet.patnum < (PAT_BULLET16_D_BLUE_last + 1)) {
patnum_base = PAT_BULLET16_D_BLUE;
} else if(bullet.patnum < (PAT_BULLET16_D_GREEN_last + 1)) {
patnum_base = PAT_BULLET16_D_GREEN;
} else if(bullet.patnum < (PAT_BULLET16_V_RED_last + 1)) {
patnum_base = PAT_BULLET16_V_RED;
} else {
patnum_base = PAT_BULLET16_V_BLUE;
}
bullet.patnum = bullet_patnum_for_angle(patnum_base, bullet.angle);
}
#else
#include "th04/sprites/main_pat.h"
static const int SLOWDOWN_BULLET_THRESHOLD_UNUSED = 24;
inline void bullet_update_patnum(bullet_t near &bullet) {
}
#endif
#pragma option -a2
#define bullet_velocity_set_from_angle_and_speed(bullet) \
vector2_near(bullet.pos.velocity, bullet.angle, bullet.speed_cur)
void pascal near bullet_turn_x(bullet_t near &bullet)
{
bullet.ax.turns_done++;
bullet.angle = (0x80 - bullet.angle);
if(bullet.ax.turns_done >= bullet_special_motion.turns_max) {
bullet.move_state = BMS_REGULAR;
}
bullet_velocity_set_from_angle_and_speed(bullet);
bullet_update_patnum(bullet);
}
void pascal near bullet_turn_y(bullet_t near &bullet)
{
bullet.ax.turns_done++;
bullet.angle = (/* 0x00 */ - bullet.angle);
if(bullet.ax.turns_done >= bullet_special_motion.turns_max) {
bullet.move_state = BMS_REGULAR;
}
bullet_velocity_set_from_angle_and_speed(bullet);
bullet_update_patnum(bullet);
}
#define bullet_turn_complete(bullet) \
bullet_update_patnum(bullet); \
bullet.speed_cur = bullet.speed_final; \
if(bullet.ax.turns_done >= bullet_special_motion.turns_max) { \
bullet.move_state = BMS_REGULAR; \
} \
bullet_velocity_set_from_angle_and_speed(bullet);
enum bullet_bounce_edge_t {
BBE_LEFT = (1 << 0),
BBE_RIGHT = (1 << 1),
BBE_TOP = (1 << 2),
BBE_BOTTOM = (1 << 3),
};
#define bullet_bounce(bullet, edges) \
if( \
((edges & BBE_LEFT) && (bullet.pos.cur.x.v <= to_sp(0.0f))) || \
((edges & BBE_RIGHT) && (bullet.pos.cur.x.v >= to_sp(PLAYFIELD_W))) \
) { \
bullet_turn_x(bullet); \
} \
if(\
((edges & BBE_TOP) && (bullet.pos.cur.y.v <= to_sp(0.0f))) || \
((edges & BBE_BOTTOM) && (bullet.pos.cur.y.v >= to_sp(PLAYFIELD_H))) \
) { \
bullet_turn_y(bullet); \
}
// Calculates [bullet.velocity] for the special motion types.
void pascal near bullet_update_special(bullet_t near &bullet)
{
switch(bullet.special_motion) {
case BSM_SLOWDOWN_THEN_TURN_AIMED:
if(bullet.speed_cur.v != to_sp(0.0f)) {
bullet_velocity_set_from_angle_and_speed(bullet);
bullet.speed_cur.v--; // -= to_sp(1 / 16.0f)
} else {
bullet.ax.turns_done++;
bullet.angle = iatan2(
(player_pos.cur.y.v - bullet.pos.cur.y.v),
(player_pos.cur.x.v - bullet.pos.cur.x.v)
);
bullet_turn_complete(bullet);
}
break;
case BSM_SLOWDOWN_THEN_TURN:
if(bullet.speed_cur.v != to_sp(0.0f)) {
bullet_velocity_set_from_angle_and_speed(bullet);
bullet.speed_cur.v--; // -= to_sp(1 / 16.0f)
} else {
bullet.ax.turns_done++;
bullet.angle += bullet.dx.angle.turn_by;
bullet_turn_complete(bullet);
}
break;
case BSM_SPEEDUP:
bullet_velocity_set_from_angle_and_speed(bullet);
bullet.speed_cur.v += bullet_special_motion.speed_delta.v;
break;
case BSM_SLOWDOWN_TO_ANGLE:
if(bullet.speed_cur.v != to_sp(0.0f)) {
bullet_velocity_set_from_angle_and_speed(bullet);
// >= to_sp(2 / 16.0f) would have been cleaner
if(bullet.speed_cur.v > to_sp8(1 / 16.0f)) {
bullet.speed_cur.v += to_sp(-2 / 16.0f);
} else {
bullet.speed_cur.set(0.0f);
}
if(bullet.speed_cur.v < to_sp8(2.0f)) {
bullet.angle += (static_cast<int8_t>(
bullet.dx.angle.target - bullet.angle
) / 4);
}
} else {
bullet.angle = bullet.dx.angle.target;
bullet.speed_cur.v = bullet.speed_final.v;
bullet.move_state = BMS_REGULAR;
bullet_velocity_set_from_angle_and_speed(bullet);
}
break;
case BSM_BOUNCE_LEFT_RIGHT:
bullet_bounce(bullet, (BBE_LEFT | BBE_RIGHT));
break;
case BSM_BOUNCE_TOP_BOTTOM:
bullet_bounce(bullet, (BBE_TOP | BBE_BOTTOM));
break;
case BSM_BOUNCE_LEFT_RIGHT_TOP_BOTTOM:
bullet_bounce(bullet, (BBE_LEFT | BBE_RIGHT | BBE_TOP | BBE_BOTTOM));
break;
case BSM_BOUNCE_LEFT_RIGHT_TOP:
bullet_bounce(bullet, (BBE_LEFT | BBE_RIGHT | BBE_TOP));
break;
case BSM_GRAVITY:
if(stage_frame_mod2) {
bullet.pos.velocity.y.v += bullet_special_motion.speed_delta;
}
break;
#if (GAME == 5)
case BSM_EXACT_LINEAR:
bullet.distance.v += bullet.speed_cur.v;
vector2_at(
drawpoint,
bullet.origin.x.v,
bullet.origin.y.v,
bullet.distance.v,
bullet.angle
);
bullet.pos.velocity.x.v = (drawpoint.x.v - bullet.pos.cur.x.v);
bullet.pos.velocity.y.v = (drawpoint.y.v - bullet.pos.cur.y.v);
break;
#endif
}
}
void bullets_update(void)
{
int i;
int bullets_seen = 0;
#if (GAME == 5)
pellet_clouds_render_count = 0;
#endif
pellets_render_count = 0;
bullet_t near *bullet = &bullets[BULLET_COUNT - 1];
// Since we iterate over the bullet array backwards, we encounter the 16×16
// bullets first.
#define is_bullet16(i) \
(i < BULLET16_COUNT)
#define is_pellet(i) \
!is_bullet16(i)
if(bullet_zap.active == false) {
for(i = 0; i < BULLET_COUNT; i++, bullet--) {
if(bullet->flag == 0) {
continue;
}
if(bullet->flag == 2) {
bullet->flag = 0;
continue;
}
bullets_seen++;
if(bullet_clear_time) {
if(bullet->move_state < BMS_DECAY) {
bullet->move_state = BMS_DECAY;
bullet->patnum = is_bullet16(i)
? PAT_DECAY_BULLET16
: PAT_DECAY_PELLET;
if(bullet->age != 0) {
score_delta += 100;
} else {
score_delta += 10;
}
} else {
reinterpret_cast<unsigned char &>(bullet->move_state)++;
if(bullet->move_state >= BMS_DECAY_END) {
bullet->pos.update_seg3();
bullet->flag = 2;
continue;
}
if((bullet->move_state % BMS_DECAY_FRAMES_PER_CEL) == 0) {
bullet->patnum++;
}
}
}
bullet->age++;
if(bullet->spawn_state >= BSS_ACTIVE) {
if(bullet->spawn_state == BSS_ACTIVE) {
bullet->spawn_state = BSS_GRAZEABLE;
} else {
// In delay cloud state
if(bullet->spawn_state == BSS_CLOUD_BACKWARDS) {
bullet->pos.prev = bullet->pos.cur;
bullet->pos.cur.x.v -= (bullet->pos.velocity.x.v << 3);
bullet->pos.cur.y.v -= (bullet->pos.velocity.y.v << 3);
bullet->spawn_state = BSS_CLOUD_FORWARDS;
} else if(bullet->spawn_state == BSS_CLOUD_FORWARDS) {
bullet->pos.update_seg3();
} else {
bullet->pos.prev = bullet->pos.cur;
bullet->pos.cur.x.v += (bullet->pos.velocity.x.v / 3);
bullet->pos.cur.y.v += (bullet->pos.velocity.y.v / 3);
}
reinterpret_cast<unsigned char &>(bullet->spawn_state)++;
if(bullet->spawn_state >= BSS_CLOUD_END) {
#if (GAME == 5)
if(!playfield_encloses_yx_lt_ge(
bullet->pos.cur.x,
bullet->pos.cur.y,
BULLET16_W,
BULLET16_H
)) {
bullet->flag = 2;
continue;
}
#endif
bullet->spawn_state = BSS_ACTIVE;
}
#if (GAME == 5)
else if(is_pellet(i)) {
pellet_clouds_render[pellet_clouds_render_count++] =
bullet;
}
#endif
continue;
}
}
if(bullet->move_state == BMS_SPECIAL) {
bullet_update_special(*bullet);
} else if(bullet->move_state == BMS_SLOWDOWN) {
bullet->ax.slowdown_time--;
bullet->speed_cur.v = (bullet->speed_final.v + ((
bullet->ax.slowdown_time * bullet->dx.slowdown_speed_delta.v
) / BMS_SLOWDOWN_FRAMES));
if(bullet->ax.slowdown_time == 0) {
bullet->speed_cur = bullet->speed_final;
bullet->move_state = BMS_REGULAR;
}
bullet_velocity_set_from_angle_and_speed((*bullet));
}
/* DX:AX = */ bullet->pos.update_seg3();
if(!playfield_encloses(_AX, _DX, BULLET16_W, BULLET16_H)) {
bullet->flag = 2;
continue;
}
if(bullet_clear_time != 0) {
continue;
}
_AX -= player_pos.cur.x.v;
_DX -= player_pos.cur.y.v;
if(player_invincibility_time == 0) {
// Yup, a bullet must have been grazed in a previous frame
// before it can be collided with.
if(bullet->spawn_state != BSS_GRAZEABLE) {
if(overlap_wh_inplace_fast(
_AX, _DX, BULLET_KILLBOX_W, BULLET_KILLBOX_H
)) {
bullet->flag = 2;
player_is_hit = true;
continue;
}
} else {
// Yes, the graze box is biased to the right, and taller
// than wide.
if(overlap_offcenter_inplace_fast(
_AX, _DX,
to_sp(16.0f), to_sp(22.0f), to_sp(20.0f), to_sp(22.0f)
)) {
/* TODO: Replace with the decompiled call
* sparks_add_random(bullet->pos.cur.x, bullet->pos.cur.y, to_sp(2.0f), 2);
* once that function is part of this translation unit */
_asm {
db 0xFF, 0x74, 0x02;
db 0xFF, 0x74, 0x04;
db 0x66, 0x68, 2, 0x00, (2 * 16), 0x00;
nop;
push cs;
call near ptr sparks_add_random;
}
bullet->spawn_state = BSS_GRAZED;
if(stage_graze < STAGE_GRAZE_CAP) {
stage_graze++;
hud_graze_put();
score_delta += graze_score;
}
}
}
}
if(is_pellet(i)) {
pellets_render[pellets_render_count].top.left = (
bullet->pos.cur.to_screen_left(PELLET_W)
);
pellets_render[pellets_render_count].top.top = (
bullet->pos.cur.to_vram_top_scrolled_seg3(PELLET_H)
);
pellets_render_count++;
}
}
if(turbo_mode == false) {
#if (GAME == 5)
slowdown_caused_by_bullets = false;
i = 42;
#else
i = 24;
i += playperf;
#endif
i += (rank * 8);
if(bullets_seen >= i) {
if(!stage_frame_mod2) {
slowdown_factor = 2;
#if (GAME == 5)
slowdown_caused_by_bullets = true;
#endif
}
} else if(bullets_seen >= (i + SLOWDOWN_BULLET_THRESHOLD_UNUSED)) {
// Yes, never executed, as the first condition would then have
// been true as well
slowdown_factor = 2;
#if (GAME == 5)
slowdown_caused_by_bullets = true;
#endif
}
}
} else {
// A bit wasteful to run all of this every frame, since no new bullets
// are spawned while [bullet_zap] is active; the bullet spawn wrapper
// functions prevent that. Then again, this means that the code here
// does kind of rely on bullets not being spawned through other methods.
unsigned int score_per_bullet;
unsigned int score_step;
// Without this cap, the [popup_bonus] formula would be
// 0.5n³ - n² + 1.5n
// with n = [bullets_seen].
unsigned int score_per_bullet_cap;
unsigned char patnum;
patnum =
(PAT_BULLET_ZAP + (bullet_zap.frames / BULLET_ZAP_FRAMES_PER_CEL)
);
score_per_bullet = 1;
score_step = 1;
#if (GAME == 5)
// ZUN bug: All code below uses TH04's patnum for that sprite. This
// causes the decay animation to never actually play in TH05.
#define PAT_BULLET_ZAP 72
score_per_bullet_cap = (rank == RANK_EXTRA)
? 1600
: select_for_rank(960, 1280, 1280, 1280);
#else
switch(rank) {
case RANK_EASY:
score_per_bullet_cap = 1000;
break;
case RANK_NORMAL:
case RANK_HARD:
case RANK_LUNATIC:
score_per_bullet_cap = 1600;
break;
case RANK_EXTRA:
score_per_bullet_cap = 2000;
break;
}
#endif
overlay_popup_bonus = 0;
for(i = 0; i < BULLET_COUNT; i++, bullet--) {
if(bullet->flag != 1) {
continue;
}
bullet->pos.velocity.set(0.0f, 0.0f);
bullet->pos.update_seg3();
// ZUN bug: Always false in TH05, see above
if(patnum < (PAT_BULLET_ZAP + BULLET_DECAY_CELS)) {
bullet->patnum = patnum;
#if (GAME == 5)
bullets_seen++;
#endif
continue;
}
overlay_popup_bonus += score_per_bullet;
score_delta += score_per_bullet;
pointnums_add_white(
bullet->pos.cur.x, bullet->pos.cur.y, score_per_bullet
);
score_per_bullet += score_step;
score_step += 3;
if(score_per_bullet > score_per_bullet_cap) {
score_per_bullet = score_per_bullet_cap;
}
bullet->flag = 2;
#if (GAME == 5)
if(bullet_zap_drop_point_items && ((bullets_seen % 4) == 0)) {
items_add(bullet->pos.cur.x, bullet->pos.cur.y, IT_POINT);
}
bullets_seen++;
#endif
}
// Note that this would show only one popup even *if* bullets could
// spawn during the zap frames: Popups can be changed at least every
// 64 frames, and BULLET_ZAP_FRAMES is smaller.
if(overlay_popup_bonus) {
overlay_popup_show(POPUP_ID_BONUS);
}
bullet_zap.frames++;
// ZUN bug: Always true in TH05, see above
if(patnum >= (PAT_BULLET_ZAP + BULLET_ZAP_CELS)) {
bullet_zap.active = false;
}
}
if(bullet_clear_time) {
bullet_clear_time--;
}
}