mirror of https://github.com/nmlgc/ReC98.git
749 lines
19 KiB
C++
749 lines
19 KiB
C++
/// Constants
|
|
/// ---------
|
|
// Can't declare these as `static const` variables, because that would break
|
|
// compile-time Subpixel arithmetic
|
|
#define PELLET_LEFT_MIN (PLAYFIELD_LEFT)
|
|
#define PELLET_LEFT_MAX (PLAYFIELD_RIGHT - PELLET_W)
|
|
#define PELLET_TOP_MIN (PLAYFIELD_TOP)
|
|
#define PELLET_TOP_MAX (PLAYFIELD_BOTTOM)
|
|
|
|
#define PELLET_BOUNCE_TOP_MIN (PLAYFIELD_TOP + ((PELLET_H / 4) * 3))
|
|
|
|
#define PELLET_VELOCITY_MAX to_sp(8.0f)
|
|
|
|
static const unsigned int PELLET_DESTROY_SCORE = 10;
|
|
static const int PELLET_DECAY_FRAMES = 20;
|
|
static const int PELLET_DECAY_CELS = 2;
|
|
/// ---------
|
|
|
|
/// Globals
|
|
/// -------
|
|
pellet_t near *p;
|
|
bool pellet_interlace = false;
|
|
unsigned int pellet_destroy_score_delta = 0;
|
|
#include "th01/sprites/pellet.csp"
|
|
/// -------
|
|
|
|
CPellets::CPellets(void)
|
|
{
|
|
int i;
|
|
|
|
p = iteration_start();
|
|
for(i = 0; i < PELLET_COUNT; i++, p++) {
|
|
p->moving = false;
|
|
p->not_rendered = false;
|
|
}
|
|
|
|
alive_count = 0;
|
|
for(i = 0; i < sizeof(unknown_zero) / sizeof(unknown_zero[0]); i++) {
|
|
unknown_zero[i] = 0;
|
|
}
|
|
}
|
|
|
|
void vector2_to_player_from(
|
|
screen_x_t x,
|
|
screen_y_t y,
|
|
subpixel_t &ret_x,
|
|
subpixel_t &ret_y,
|
|
subpixel_t length,
|
|
unsigned char plus_angle
|
|
)
|
|
{
|
|
plus_angle = iatan2(
|
|
(player_center_y - y),
|
|
((player_left + (PLAYER_W / 2) - (PELLET_W / 2)) - x)
|
|
) + plus_angle;
|
|
ret_x = polar_x(0, length, plus_angle);
|
|
ret_y = polar_y(0, length, plus_angle);
|
|
}
|
|
|
|
// Sets the velocity for pellet #[i] in the given [pattern]. Returns true if
|
|
// this was the last pellet for this pattern.
|
|
bool16 pattern_velocity_set(
|
|
Subpixel &ret_x,
|
|
Subpixel &ret_y,
|
|
pellet_pattern_t pattern,
|
|
subpixel_t speed,
|
|
int &i,
|
|
screen_x_t pellet_left,
|
|
screen_y_t pellet_top
|
|
)
|
|
{
|
|
// ZUN bug: Due to this default, add_pattern() ends up repeatedly calling
|
|
// this function for [pattern] values not covered by the switch below,
|
|
// until it iterated over the entire pellet array...
|
|
bool16 done = false;
|
|
unsigned char angle = 0x00;
|
|
unsigned char spread_delta = 0x00;
|
|
|
|
#define to_aim_or_not_to_aim() \
|
|
if(pattern >= PP_AIMED_SPREADS) { \
|
|
goto aim; \
|
|
} \
|
|
/* Static pattern; add a 90° angle, so that 0° points downwards */ \
|
|
angle += 0x40; \
|
|
goto no_aim;
|
|
|
|
switch(pattern) {
|
|
case PP_1:
|
|
ret_y.v = speed;
|
|
ret_x.set(0.0f);
|
|
done = true;
|
|
break;
|
|
case PP_1_AIMED:
|
|
vector2_between(
|
|
pellet_left,
|
|
pellet_top,
|
|
player_left + 8,
|
|
player_center_y,
|
|
ret_x.v,
|
|
ret_y.v,
|
|
speed
|
|
);
|
|
done = true;
|
|
break;
|
|
|
|
case PP_2_SPREAD_WIDE:
|
|
case PP_2_SPREAD_WIDE_AIMED: spread_delta = 0x08; // fallthrough
|
|
case PP_2_SPREAD_NARROW:
|
|
case PP_2_SPREAD_NARROW_AIMED:
|
|
/**/ if(i == 0) { angle = (+0x04 + spread_delta); }
|
|
else if(i == 1) { angle = (-0x04 - spread_delta); done = true; }
|
|
to_aim_or_not_to_aim();
|
|
|
|
case PP_3_SPREAD_WIDE:
|
|
case PP_3_SPREAD_WIDE_AIMED: spread_delta = 0x05; // fallthrough
|
|
case PP_3_SPREAD_NARROW:
|
|
case PP_3_SPREAD_NARROW_AIMED:
|
|
/**/ if(i == 0) { angle = 0x00; }
|
|
else if(i == 1) { angle = (+0x04 + spread_delta); }
|
|
else if(i == 2) { angle = (-0x04 - spread_delta); done = true; }
|
|
to_aim_or_not_to_aim();
|
|
|
|
case PP_4_SPREAD_WIDE:
|
|
case PP_4_SPREAD_WIDE_AIMED: spread_delta = 0x04; // fallthrough
|
|
case PP_4_SPREAD_NARROW:
|
|
case PP_4_SPREAD_NARROW_AIMED:
|
|
/**/ if(i == 0) { angle = (+0x04 + spread_delta); }
|
|
else if(i == 1) { angle = (-0x04 - spread_delta); }
|
|
else if(i == 2) { angle = (+0x0C + (spread_delta * 3)); }
|
|
else if(i == 3) { angle = (-0x0C - (spread_delta * 3)); done = true; }
|
|
to_aim_or_not_to_aim();
|
|
|
|
case PP_5_SPREAD_WIDE:
|
|
case PP_5_SPREAD_WIDE_AIMED: spread_delta = 0x04; // fallthrough
|
|
case PP_5_SPREAD_NARROW:
|
|
case PP_5_SPREAD_NARROW_AIMED:
|
|
/**/ if(i == 0) { angle = 0x00; }
|
|
else if(i == 1) { angle = (+0x04 + spread_delta); }
|
|
else if(i == 2) { angle = (-0x04 - spread_delta); }
|
|
else if(i == 3) { angle = (+0x08 + (spread_delta * 2)); }
|
|
else if(i == 4) { angle = (-0x08 - (spread_delta * 2)); done = true; }
|
|
to_aim_or_not_to_aim();
|
|
|
|
case PP_1_RANDOM_NARROW_AIMED:
|
|
angle = ((rand() & 0x0F) - 0x07);
|
|
done = true;
|
|
goto aim;
|
|
|
|
case PP_1_RANDOM_WIDE:
|
|
angle = ((rand() & 0x3F) + 0x20);
|
|
done = true;
|
|
goto no_aim;
|
|
|
|
aim:
|
|
vector2_to_player_from(
|
|
pellet_left, pellet_top, ret_x.v, ret_y.v, speed, angle
|
|
);
|
|
break;
|
|
|
|
no_aim:
|
|
vector2(ret_x.v, ret_y.v, speed, angle);
|
|
break;
|
|
}
|
|
i++;
|
|
return done;
|
|
}
|
|
|
|
inline subpixel_t base_speed_for_rank(void)
|
|
{
|
|
return
|
|
(static_cast<int>(rank) == RANK_EASY) ? to_sp(0.0f) :
|
|
(static_cast<int>(rank) == RANK_NORMAL) ? to_sp(0.375f) :
|
|
(static_cast<int>(rank) == RANK_HARD) ? to_sp(0.75f) :
|
|
to_sp(1.125f);
|
|
}
|
|
|
|
#define speed_set(speed) \
|
|
speed += base_speed_for_rank(); \
|
|
speed += ((resident->pellet_speed * speed) / to_sp(2.5f)); \
|
|
if(speed < to_sp(1.0f)) { \
|
|
speed = to_sp(1.0f); \
|
|
}
|
|
|
|
#define pellet_init(pellet, left, top, pattern) \
|
|
pellet->decay_frame = 0; \
|
|
pellet->cur_left.v = TO_SP(left); \
|
|
pellet->cur_top.v = TO_SP(top); \
|
|
pellet->cloud_left = left; \
|
|
pellet->cloud_top = top; \
|
|
if(spawn_with_cloud) { \
|
|
pellet->cloud_frame = 1; \
|
|
} else { \
|
|
pellet->moving = true; \
|
|
} \
|
|
pellet->from_pattern = pattern;
|
|
|
|
void CPellets::add_pattern(
|
|
screen_x_t left, screen_y_t top, pellet_pattern_t pattern, subpixel_t speed
|
|
)
|
|
{
|
|
int i;
|
|
int pattern_i = 0;
|
|
int pattern_done;
|
|
Subpixel vel_x;
|
|
Subpixel vel_y;
|
|
|
|
// Should be >=, but yeah, it's just an inconsequential oversight.
|
|
if(alive_count > PELLET_COUNT) {
|
|
return;
|
|
}
|
|
if(
|
|
(left >= PLAYFIELD_RIGHT) ||
|
|
(top < (PLAYFIELD_TOP - PELLET_H)) ||
|
|
(left < (PLAYFIELD_LEFT - PELLET_W)) ||
|
|
(top > PLAYFIELD_BOTTOM)
|
|
) {
|
|
return;
|
|
}
|
|
speed_set(speed);
|
|
|
|
p = iteration_start();
|
|
for(i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == true) {
|
|
continue;
|
|
}
|
|
if(p->cloud_frame) {
|
|
continue;
|
|
}
|
|
pellet_init(p, left, top, pattern);
|
|
p->prev_left.v = -1;
|
|
p->age = 0;
|
|
alive_count++;
|
|
pattern_done = pattern_velocity_set(
|
|
vel_x, vel_y, pattern, speed, pattern_i, left, top
|
|
);
|
|
p->velocity.x.v = vel_x.v;
|
|
p->velocity.y.v = vel_y.v;
|
|
if(pattern_done == true) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPellets::add_single(
|
|
screen_x_t left,
|
|
screen_y_t top,
|
|
int angle,
|
|
subpixel_t speed_base,
|
|
pellet_motion_t motion_type,
|
|
subpixel_t speed_for_motion_fixed,
|
|
screen_x_t spin_center_x,
|
|
screen_y_t spin_center_y
|
|
)
|
|
{
|
|
int i;
|
|
Subpixel vel_x;
|
|
Subpixel vel_y;
|
|
|
|
// Should be >=, but yeah, it's just an inconsequential oversight.
|
|
if(alive_count > PELLET_COUNT) {
|
|
return;
|
|
}
|
|
speed_set(speed_base);
|
|
|
|
p = iteration_start();
|
|
for(i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == true) {
|
|
continue;
|
|
}
|
|
if(p->cloud_frame) {
|
|
continue;
|
|
}
|
|
pellet_init(p, left, top, PP_NONE);
|
|
p->motion_type = motion_type;
|
|
p->prev_left.v = -1;
|
|
p->age = 0;
|
|
alive_count++;
|
|
p->spin_center.x.v = TO_SP(spin_center_x);
|
|
p->spin_center.y.v = TO_SP(spin_center_y);
|
|
if(motion_type == PM_SPIN) {
|
|
vector2(vel_x.v, vel_y.v, speed_for_motion_fixed, angle);
|
|
p->spin_velocity.x.v = vel_x.v;
|
|
p->spin_velocity.y.v = vel_y.v;
|
|
p->angle = iatan2(
|
|
(p->cur_top - p->spin_center.y),
|
|
(p->cur_left - p->spin_center.x)
|
|
);
|
|
}
|
|
vector2(vel_x.v, vel_y.v, speed_base, angle);
|
|
p->speed.v = speed_for_motion_fixed;
|
|
p->velocity.x.v = vel_x.v;
|
|
p->velocity.y.v = vel_y.v;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CPellets::motion_type_apply_for_cur(void)
|
|
{
|
|
Subpixel velocity_to_player_x;
|
|
Subpixel velocity_to_player_y;
|
|
|
|
switch(p->motion_type) {
|
|
case PM_GRAVITY:
|
|
p->velocity.y.v += p->speed.v;
|
|
break;
|
|
case PM_SLING_AIMED:
|
|
if(p->sling_direction == PSD_NONE) {
|
|
p->sling_direction = static_cast<pellet_sling_direction_t>(
|
|
(rand() & 1) + PSD_CLOCKWISE
|
|
);
|
|
}
|
|
if(p->sling_direction == PSD_CLOCKWISE) {
|
|
vector2(p->velocity.x.v, p->velocity.y.v, p->speed.v, p->angle);
|
|
} else /* PSD_COUNTERCLOCKWISE */ {
|
|
vector2(p->velocity.y.v, p->velocity.x.v, p->speed.v, p->angle);
|
|
}
|
|
p->angle += PELLET_SLING_DELTA_ANGLE;
|
|
if(p->angle > 0x100) {
|
|
vector2_between(
|
|
p->cur_left.to_pixel(),
|
|
p->cur_top.to_pixel(),
|
|
player_left + 8,
|
|
player_center_y,
|
|
p->velocity.x.v,
|
|
p->velocity.y.v,
|
|
p->speed.v
|
|
);
|
|
p->from_pattern = PP_1_AIMED;
|
|
p->angle = 0x00;
|
|
}
|
|
break;
|
|
case PM_BOUNCE_FROM_TOP_THEN_GRAVITY:
|
|
// Wow... Three ZUN bugs in one single if() expression.
|
|
// 1)
|
|
// 2) Pellets are clipped at both the left (1) and the right (2)
|
|
// edge of the playfield at those exact same coordinates,
|
|
// *before* this code gets to run. Thus, they won't ever
|
|
// bounce from those two sides here.
|
|
// 3) Comparing screen-space coordinates to subpixels. What's a
|
|
// type system? :zunpet:
|
|
if(
|
|
(p->cur_left.v <= PELLET_LEFT_MIN)
|
|
|| (p->cur_left.to_pixel() >= to_sp(PELLET_LEFT_MAX))
|
|
|| (p->cur_top.v <= to_sp(PELLET_BOUNCE_TOP_MIN))
|
|
) {
|
|
p->velocity.x.v = -p->velocity.x.v;
|
|
p->velocity.y.set(0.0f);
|
|
p->motion_type = PM_GRAVITY;
|
|
// Nope, this doesn't help.
|
|
if(p->cur_left.v <= to_sp(PELLET_LEFT_MIN)) {
|
|
p->cur_left.set(PELLET_LEFT_MIN + 1.0f);
|
|
}
|
|
if(p->cur_left.v >= to_sp(PELLET_LEFT_MAX)) {
|
|
p->cur_left.set(PELLET_LEFT_MAX - 1.0f);
|
|
}
|
|
if(p->cur_top.v <= to_sp(PELLET_TOP_MIN)) {
|
|
p->cur_top.set(PELLET_TOP_MIN + 1.0f);
|
|
}
|
|
}
|
|
break;
|
|
case PM_FALL_STRAIGHT_FROM_TOP_THEN_NORMAL:
|
|
if(p->cur_top.to_pixel() <= PELLET_BOUNCE_TOP_MIN) {
|
|
p->velocity.x.set(0.0f);
|
|
p->velocity.y.v = p->speed.v;
|
|
p->motion_type = PM_NORMAL;
|
|
if(p->cur_top.to_pixel() <= PLAYFIELD_TOP) {
|
|
p->cur_top.set(PLAYFIELD_TOP + 1.0f);
|
|
}
|
|
}
|
|
break;
|
|
case PM_SPIN:
|
|
p->cur_left.v = polar_x(
|
|
p->spin_center.x.v, PELLET_SPIN_CIRCLE_RADIUS, p->angle
|
|
);
|
|
p->cur_top.v = polar_y(
|
|
p->spin_center.y.v, PELLET_SPIN_CIRCLE_RADIUS, p->angle
|
|
);
|
|
p->spin_center.x.v += p->spin_velocity.x.v;
|
|
p->spin_center.y.v += p->spin_velocity.y.v;
|
|
p->velocity.set(0.0f, 0.0f);
|
|
p->angle += PELLET_SPIN_DELTA_ANGLE;
|
|
break;
|
|
case PM_CHASE:
|
|
vector2_between(
|
|
p->cur_left.to_pixel(),
|
|
p->cur_top.to_pixel(),
|
|
player_left + 8,
|
|
player_center_y,
|
|
velocity_to_player_x.v,
|
|
velocity_to_player_y.v,
|
|
p->speed.v
|
|
);
|
|
if(p->cur_top.v < PELLET_CHASE_TOP_MAX) {
|
|
#define chase(cur, target) \
|
|
(cur > target) ? to_sp(-0.0625f) : \
|
|
(cur < target) ? to_sp( 0.0625f) : \
|
|
to_sp(0.0f);
|
|
p->velocity.x.v += chase(p->velocity.x.v, velocity_to_player_x.v);
|
|
p->velocity.y.v += chase(p->velocity.y.v, velocity_to_player_y.v);
|
|
#undef chase
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void pellet_put(screen_x_t left, vram_y_t top, int cel)
|
|
{
|
|
// Some `__asm` statements here look like they could be expressed using
|
|
// register pseudovariables. However, TCC would then use a different
|
|
// instruction than the one in ZUN's original binary.
|
|
_ES = SEG_PLANE_B;
|
|
|
|
_AX = (left >> 3);
|
|
_DX = top;
|
|
_DX <<= 6;
|
|
__asm add ax, dx;
|
|
_DX >>= 2;
|
|
__asm add ax, dx;
|
|
__asm mov di, ax;
|
|
|
|
_AX = (left & 7) << 4;
|
|
_BX = cel;
|
|
_BX <<= 7;
|
|
__asm add ax, bx;
|
|
_AX += reinterpret_cast<uint16_t>(sPELLET);
|
|
|
|
__asm mov si, ax;
|
|
_CX = PELLET_H;
|
|
put_loop: {
|
|
__asm movsw
|
|
_DI += (ROW_SIZE - sizeof(dots16_t));
|
|
if(static_cast<int16_t>(_DI) >= PLANE_SIZE) {
|
|
return;
|
|
}
|
|
}
|
|
__asm loop put_loop;
|
|
}
|
|
|
|
void pellet_render(screen_x_t left, screen_y_t top, int cel)
|
|
{
|
|
grcg_setcolor_rmw(7);
|
|
pellet_put(left, top, cel);
|
|
grcg_off();
|
|
}
|
|
|
|
inline bool16 overlaps_shot(
|
|
screen_x_t pellet_left, screen_y_t pellet_top, int i
|
|
)
|
|
{
|
|
return overlap_lt_gt(
|
|
pellet_left, pellet_top, PELLET_W, PELLET_H,
|
|
Shots.left[i], Shots.top[i], SHOT_W, SHOT_H
|
|
);
|
|
}
|
|
|
|
inline bool16 overlaps_orb(screen_x_t pellet_left, screen_y_t pellet_top)
|
|
{
|
|
return overlap_lt_gt(
|
|
pellet_left, pellet_top, PELLET_W, PELLET_H,
|
|
orb_cur_left, orb_cur_top, ORB_W, ORB_H
|
|
);
|
|
}
|
|
|
|
bool16 CPellets::visible_after_hittests_for_cur(
|
|
screen_x_t pellet_left, screen_y_t pellet_top
|
|
)
|
|
{
|
|
// Well, well. Since ZUN uses this super sloppy 16x8 rectangle to unblit
|
|
// 8x8 pellets, there's now the (completely unnecessary) possibility of
|
|
// accidentally unblitting parts of a sprite that was previously drawn
|
|
// into the 8 pixels right of a pellet.
|
|
// "Oh, I know! Let's test the entire 16 pixels, and in case we got an
|
|
// entity there, we simply make the *pellet* invisible for this frame!
|
|
// Then we don't even have to unblit it later! :tannedcirno:"
|
|
//
|
|
// Except that the only entity type this is done for are the player shots,
|
|
// and also only for 3 basically random ones? Note that indices 0, 1, and
|
|
// 2 in the shot array don't necessarily have to be the 3 ones that
|
|
// spawned last... although given the sheer ~quality~ of all this code, it
|
|
// might very well have been intended like that? Or maybe this code was
|
|
// written when the intended SHOT_COUNT was still 3. Urgh.
|
|
//
|
|
// Also note that the code is still testing the left 8 pixels that make up
|
|
// the actual pellet, despite already having hit-tested them with a call
|
|
// to CShots::hittest_pellet() before. Turns out that the check there
|
|
// (which actually does affect gameplay) is more precise and excludes the
|
|
// transparent edges of the shot sprite, which the blitting-related check
|
|
// here shouldn't do. (This entire paragraph would have been unnecessary
|
|
// if this code had just tested all 16 pixels at once, rather than having
|
|
// this weird, bloated, and slow split into two 8-pixel checks.)
|
|
//
|
|
// This is certainly a contender for the most stupid piece of code in this
|
|
// game. What about the player sprite, the Orb (for which we *do* the same
|
|
// test, but always return `true` regardless), or, heck, *other pellets*?
|
|
// It's just so hilarious how these mitigations don't help fixing the
|
|
// underlying problem, at all.
|
|
for(int i = 0; i < 3; i++) {
|
|
if(
|
|
(Shots.is_moving(i) == true) &&
|
|
(overlaps_shot(pellet_left, pellet_top, i) == true)
|
|
) {
|
|
return false;
|
|
} else if(overlaps_shot((pellet_left + 8), pellet_top, i) == true) {
|
|
return false;
|
|
}
|
|
}
|
|
if(p->decay_frame) {
|
|
return true;
|
|
}
|
|
if(
|
|
(overlaps_orb(pellet_left, pellet_top) == true) ||
|
|
(overlaps_orb((pellet_left + 8), pellet_top) == true)
|
|
) {
|
|
// Hey, let's also process a collision! Why not?! This is one magical
|
|
// Orb, after all. Even collides with pellets it doesn't actually hit.
|
|
p->velocity.y.v >>= 1;
|
|
p->velocity.x.v >>= 1;
|
|
p->decay_frame = 1;
|
|
pellet_destroy_score_delta += PELLET_DESTROY_SCORE;
|
|
return true;
|
|
}
|
|
if((player_deflecting == true) && (overlap_lt_gt(
|
|
pellet_left,
|
|
pellet_top,
|
|
PELLET_W,
|
|
PELLET_H,
|
|
(player_left - PELLET_W),
|
|
(player_top - (PELLET_H * 2)),
|
|
(PLAYER_W + (PELLET_W * 2)),
|
|
(PLAYER_H + (PELLET_H * 2))
|
|
) == true)) {
|
|
char deflect_angle;
|
|
if(p->cur_left.to_pixel() <= (player_left + 12)) {
|
|
deflect_angle = 0x80;
|
|
} else {
|
|
deflect_angle = 0x00;
|
|
}
|
|
vector2(p->velocity.x.v, p->velocity.y.v, to_sp(8.0f), deflect_angle);
|
|
if(!p->from_pattern) {
|
|
p->motion_type = PM_NORMAL;
|
|
}
|
|
// Yes, deflected pellets aren't rendered on the frames they're
|
|
// deflected on!
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CPellets::decay_tick_for_cur(void)
|
|
{
|
|
p->decay_frame++;
|
|
if(p->decay_frame > (PELLET_DECAY_FRAMES + 1)) {
|
|
p->decay_frame = 0;
|
|
p->moving = false;
|
|
alive_count--;
|
|
}
|
|
}
|
|
|
|
void CPellets::unput_update_render(void)
|
|
{
|
|
int i;
|
|
|
|
interlace_field = (interlace_field == false) ? true : false;
|
|
|
|
clouds_unput_update_render();
|
|
|
|
p = iteration_start();
|
|
for(i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == false) {
|
|
continue;
|
|
}
|
|
if(!pellet_interlace || (interlace_field == (i % 2))) {
|
|
if(p->not_rendered == false && (p->prev_left.v != -1)) {
|
|
p->sloppy_wide_unput();
|
|
}
|
|
}
|
|
if(p->from_pattern == PP_NONE && p->motion_type) {
|
|
motion_type_apply_for_cur();
|
|
}
|
|
if(p->velocity.y.v > PELLET_VELOCITY_MAX) {
|
|
p->velocity.y.v = PELLET_VELOCITY_MAX;
|
|
}
|
|
if(p->velocity.x.v > PELLET_VELOCITY_MAX) {
|
|
p->velocity.x.v = PELLET_VELOCITY_MAX;
|
|
}
|
|
p->cur_top.v += p->velocity.y.v;
|
|
p->cur_left.v += p->velocity.x.v;
|
|
|
|
// Shot<->Pellet hit testing
|
|
if(p->decay_frame == 0) {
|
|
if(Shots.hittest_pellet(
|
|
p->cur_left.to_pixel(), p->cur_top.to_pixel()
|
|
)) {
|
|
p->decay_frame = 0;
|
|
p->moving = false;
|
|
alive_count--;
|
|
p->sloppy_wide_unput();
|
|
}
|
|
}
|
|
// Clipping
|
|
if(
|
|
p->cur_top.v >= to_sp(PELLET_TOP_MAX) ||
|
|
p->cur_top.v < to_sp(PELLET_TOP_MIN) ||
|
|
p->cur_left.v >= to_sp(PELLET_LEFT_MAX) ||
|
|
p->cur_left.v <= to_sp(PELLET_LEFT_MIN)
|
|
) {
|
|
p->moving = false;
|
|
alive_count--;
|
|
p->decay_frame = 0;
|
|
p->sloppy_wide_unput();
|
|
}
|
|
}
|
|
|
|
Shots.unput_update_render();
|
|
|
|
p = iteration_start();
|
|
for(i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == false) {
|
|
continue;
|
|
}
|
|
if(!pellet_interlace || ((interlace_field & 1) == (i % 2))) {
|
|
if(visible_after_hittests_for_cur(
|
|
p->cur_left.to_pixel(), p->cur_top.to_pixel()
|
|
) == true) {
|
|
if(p->not_rendered == true) {
|
|
p->not_rendered = false;
|
|
}
|
|
#define render pellet_render
|
|
#define decay_frames_for_cel(cel) \
|
|
((PELLET_DECAY_FRAMES / PELLET_DECAY_CELS) * cel)
|
|
if(p->decay_frame == 0) {
|
|
render(p->cur_left.to_pixel(), p->cur_top.to_pixel(), 0);
|
|
} else if(p->decay_frame <= decay_frames_for_cel(1)) {
|
|
render(p->cur_left.to_pixel(), p->cur_top.to_pixel(), 1);
|
|
} else if(p->decay_frame < decay_frames_for_cel(2)) {
|
|
render(p->cur_left.to_pixel(), p->cur_top.to_pixel(), 2);
|
|
}
|
|
#undef decay_frames_for_cel
|
|
#undef render
|
|
} else {
|
|
p->not_rendered = true;
|
|
}
|
|
p->prev_left.v = p->cur_left.v;
|
|
p->prev_top.v = p->cur_top.v;
|
|
}
|
|
p->age++;
|
|
if(p->decay_frame) {
|
|
decay_tick_for_cur();
|
|
} else if(hittest_player_for_cur()) {
|
|
if(p->not_rendered == false) {
|
|
p->sloppy_wide_unput();
|
|
}
|
|
p->moving = false;
|
|
alive_count--;
|
|
p->decay_frame = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPellets::unput_and_reset_all(void)
|
|
{
|
|
p = iteration_start();
|
|
for(int i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == false) {
|
|
continue;
|
|
}
|
|
if(p->not_rendered == false) {
|
|
p->sloppy_wide_unput_at_cur_pos();
|
|
}
|
|
p->decay_frame = 0;
|
|
p->moving = false;
|
|
p->cloud_frame = 0;
|
|
}
|
|
alive_count = 0;
|
|
}
|
|
|
|
void CPellets::decay_all(void)
|
|
{
|
|
p = iteration_start();
|
|
for(int i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == false) {
|
|
continue;
|
|
}
|
|
if(p->decay_frame) {
|
|
continue;
|
|
}
|
|
p->velocity.y.v /= 1.5f;
|
|
p->velocity.x.v /= 1.5f;
|
|
p->decay_frame = 1;
|
|
pellet_destroy_score_delta += PELLET_DESTROY_SCORE;
|
|
}
|
|
}
|
|
|
|
void CPellets::reset_all(void)
|
|
{
|
|
p = iteration_start();
|
|
for(int i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->moving == false) {
|
|
continue;
|
|
}
|
|
p->moving = false;
|
|
p->decay_frame = 0;
|
|
p->cloud_frame = 0;
|
|
}
|
|
alive_count = 0;
|
|
}
|
|
|
|
bool16 CPellets::hittest_player_for_cur(void)
|
|
{
|
|
if(player_invincible == true || p->decay_frame) {
|
|
return false;
|
|
}
|
|
if(
|
|
(p->cur_left.to_pixel() >= (player_left + 4)) &&
|
|
(p->cur_left.to_pixel() <= (player_left + 20)) &&
|
|
(p->cur_top.to_pixel() >= (player_top + (player_sliding * 8))) &&
|
|
// Yup, <, not <= as in the overlap_point_le_ge() macro.
|
|
(p->cur_top.to_pixel() < (player_top + PLAYER_H - PELLET_H))
|
|
) {
|
|
done = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const int CLOUD_COL = 7;
|
|
|
|
void CPellets::clouds_unput_update_render(void)
|
|
{
|
|
p = iteration_start();
|
|
for(int i = 0; i < PELLET_COUNT; i++, p++) {
|
|
if(p->cloud_frame == 0) {
|
|
continue;
|
|
}
|
|
p->cloud_frame++;
|
|
if(p->cloud_frame == 2) {
|
|
pellet_cloud_put_8(p->cloud_left, p->cloud_top, CLOUD_COL, 0);
|
|
} else if(p->cloud_frame == 5) {
|
|
pellet_cloud_unput_8(p->cloud_left, p->cloud_top, 0);
|
|
} else if(p->cloud_frame == 6) {
|
|
pellet_cloud_put_8(p->cloud_left, p->cloud_top, CLOUD_COL, 1);
|
|
} else if(p->cloud_frame == 9) {
|
|
pellet_cloud_unput_8(p->cloud_left, p->cloud_top, 1);
|
|
p->cloud_frame = 0;
|
|
p->moving = true;
|
|
}
|
|
}
|
|
}
|