2022-08-12 02:27:17 +00:00
|
|
|
extern int orb_frames_outside_portal; // unused
|
2022-06-26 07:39:33 +00:00
|
|
|
extern double orb_velocity_y;
|
|
|
|
|
2022-08-12 00:35:50 +00:00
|
|
|
#define COEFFICIENT_OF_RESTITUTION 0.78
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
|
2022-07-07 12:36:07 +00:00
|
|
|
inline double gravity_for(const double& force) {
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
return ((orb_force_frame / 5) + orb_force);
|
|
|
|
}
|
|
|
|
|
2022-06-26 07:39:33 +00:00
|
|
|
pixel_t orb_velocity_y_update(void)
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
{
|
|
|
|
orb_velocity_y = gravity_for(orb_force);
|
2022-08-12 00:35:50 +00:00
|
|
|
if(orb_velocity_y > 16.0f) {
|
|
|
|
orb_velocity_y = 16.0f;
|
|
|
|
} else if(orb_velocity_y < -16.0) {
|
|
|
|
orb_velocity_y = -16.0;
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
}
|
|
|
|
return gravity_for(orb_force);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define random_velocity_change(val, new_velocity) \
|
|
|
|
if(orb_force_frame < 17) { \
|
|
|
|
if((rand() % 50) == val) { \
|
|
|
|
orb_velocity_x = new_velocity; \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
|
|
|
void orb_force_new(double immediate, orb_force_t force)
|
|
|
|
{
|
2022-06-28 16:27:23 +00:00
|
|
|
if(force == OF_BOUNCE_FROM_SURFACE) {
|
2022-08-12 00:35:50 +00:00
|
|
|
orb_force = (-orb_velocity_y * COEFFICIENT_OF_RESTITUTION);
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
if(orb_velocity_x == OVX_0) {
|
|
|
|
random_velocity_change(0, OVX_4_LEFT);
|
|
|
|
random_velocity_change(1, OVX_4_RIGHT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(force == OF_BOUNCE_FROM_TOP) {
|
|
|
|
orb_force = ((-orb_velocity_y) - (orb_force_frame / 4));
|
|
|
|
}
|
|
|
|
if(force == OF_SHOT) {
|
2022-08-12 00:35:50 +00:00
|
|
|
orb_force = (-10.0 + (orb_velocity_y / 2.0));
|
[Decompilation] [th01] Orb physics
"Physics". Not only did ZUN restrict the X velocity to the 5 discrete
states of -8, -4, 0, 4, and 8 (because hey, unaligned blitting is slow
anyway?), but gravity is also only applied every 5 frames.
We're still missing quite a bit of usage code, but these are the core
functions. One of which turned out to be undecompilable, due to… a
rigorously defined instruction order when performing arithmetic between
`double`s and `float`s?! Still, spelling out all this stuff in ASM
seems much better than somehow splitting the data segment, just so that
we can immediately use literals there.
Part of P0097, funded by Ember2528.
2020-06-09 18:39:44 +00:00
|
|
|
}
|
|
|
|
if(force == OF_IMMEDIATE) {
|
|
|
|
orb_force = immediate;
|
|
|
|
}
|
|
|
|
orb_force_frame = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void orb_move_x(orb_velocity_x_t velocity_x)
|
|
|
|
{
|
|
|
|
switch(velocity_x) {
|
|
|
|
case OVX_4_LEFT: orb_cur_left -= 4; break;
|
|
|
|
case OVX_4_RIGHT: orb_cur_left += 4; break;
|
|
|
|
case OVX_8_LEFT: orb_cur_left -= 8; break;
|
|
|
|
case OVX_8_RIGHT: orb_cur_left += 8; break;
|
|
|
|
}
|
|
|
|
if(orb_cur_left <= ORB_LEFT_MIN) {
|
|
|
|
if(orb_velocity_x == OVX_4_LEFT) {
|
|
|
|
orb_velocity_x = OVX_4_RIGHT;
|
|
|
|
} else if(orb_velocity_x == OVX_8_LEFT) {
|
|
|
|
orb_velocity_x = OVX_8_RIGHT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(orb_cur_left >= ORB_LEFT_MAX) {
|
|
|
|
if(orb_velocity_x == OVX_4_RIGHT) {
|
|
|
|
orb_velocity_x = OVX_4_LEFT;
|
|
|
|
} else if(orb_velocity_x == OVX_8_RIGHT) {
|
|
|
|
orb_velocity_x = OVX_8_LEFT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-12 02:27:17 +00:00
|
|
|
|
|
|
|
inline main_ptn_id_t orb_anim_cel(void) {
|
|
|
|
return static_cast<main_ptn_id_t>(
|
|
|
|
PTN_ORB + (orb_rotation_frame / ORB_FRAMES_PER_CEL)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pascal orb_and_pellets_and_stage_unput_update_render__vsync_wait(
|
|
|
|
int stage_id
|
|
|
|
)
|
|
|
|
{
|
|
|
|
if(!orb_in_portal) {
|
|
|
|
orb_move_x(orb_velocity_x);
|
|
|
|
orb_cur_top += orb_velocity_y_update();
|
|
|
|
orb_frames_outside_portal++;
|
|
|
|
|
|
|
|
if(orb_is_moving_left()) {
|
|
|
|
orb_rotation_frame++;
|
|
|
|
}
|
|
|
|
if(orb_is_moving_right()) {
|
|
|
|
orb_rotation_frame--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ZUN quirk: The fourth cel is only visible for a single frame?
|
|
|
|
if(orb_rotation_frame >= ((ORB_CELS * ORB_FRAMES_PER_CEL) - 2)) {
|
|
|
|
orb_rotation_frame = 0;
|
|
|
|
}
|
|
|
|
if(orb_rotation_frame < 0) {
|
|
|
|
orb_rotation_frame = ((ORB_CELS - 1) * ORB_FRAMES_PER_CEL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(orb_cur_top > ORB_TOP_MAX) {
|
|
|
|
orb_force_new(COEFFICIENT_OF_RESTITUTION, OF_BOUNCE_FROM_SURFACE);
|
|
|
|
orb_cur_top = ORB_TOP_MAX;
|
|
|
|
cardcombo_cur = 0;
|
|
|
|
}
|
|
|
|
if(orb_cur_top < ORB_TOP_MIN) {
|
|
|
|
orb_force_new(0, OF_BOUNCE_FROM_TOP);
|
|
|
|
orb_cur_top = ORB_TOP_MIN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
frame_delay(1);
|
|
|
|
Pellets.unput_update_render();
|
|
|
|
|
|
|
|
// Stage object collision is explicitly deactivated on boss stages? Since
|
|
|
|
// there's still a card combo metric on the TOTLE screen though, these
|
|
|
|
// lines add another piece of evidence that that screen was shown for
|
|
|
|
// regular stages as well, at one point in development.
|
|
|
|
if(!stage_is_boss(stage_id)) {
|
|
|
|
cards_hittest(stage_id);
|
|
|
|
obstacles_update_and_render(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!orb_in_portal) {
|
|
|
|
orb_player_hittest(OR_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!orb_in_portal) {
|
2023-02-09 09:44:02 +00:00
|
|
|
// ZUN landmine: Shouldn't you unblit *before* updating the rotation
|
|
|
|
// frame? While it's OK (and even sort of essential) to assume that all
|
|
|
|
// Orb sprites are shaped identically (as they are in the original
|
|
|
|
// data), ordering the calls like this still violates common sense.
|
2022-08-12 02:27:17 +00:00
|
|
|
ptn_unput_8(orb_prev_left, orb_prev_top, orb_anim_cel());
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!stage_is_boss(stage_id)) {
|
|
|
|
cards_update_and_render();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!orb_in_portal && !player_is_hit) {
|
|
|
|
ptn_put_8(orb_cur_left, orb_cur_top, orb_anim_cel());
|
|
|
|
}
|
|
|
|
|
|
|
|
orb_prev_left = orb_cur_left;
|
|
|
|
orb_prev_top = orb_cur_top;
|
|
|
|
}
|