blues/ja/level.c

3050 lines
88 KiB
C

/* level main loop */
#include "game.h"
#include "resource.h"
#include "sys.h"
#include "util.h"
static const char *_background[] = {
"back0.eat", "back1.eat", "back2.eat", "back3.eat"
};
static const char *_decor[] = {
"jardin.eat", "prison.eat", "entrepot.eat", "egout.eat"
};
static const uint16_t _vinyl_throw_delay = 5;
static const uint16_t _object_respawn_delay = 224;
static const uint16_t _undefined = 0x55AA;
static void level_player_draw_powerdown_animation(struct player_t *player);
static void level_player_draw_powerup_animation(struct player_t *player, struct object_t *obj);
static void level_update_tiles(struct object_t *obj, int ax, int dx, int bp);
static void do_end_of_level() {
static const uint8_t color_index = 127;
ja_decode_motif(g_vars.level_num % 9, color_index);
static const uint8_t white[] = { 0x3F, 0x3F, 0x3F };
g_sys.set_palette_color(color_index, white);
g_sys.copy_bitmap(g_res.vga, GAME_SCREEN_W, GAME_SCREEN_H);
g_sys.update_screen();
g_sys.sleep(1000);
g_sys.fade_out_palette();
}
static void level_player_die(struct player_t *player) {
g_vars.player_xscroll = 0;
player_flags2(&player->obj) |= 8;
level_player_draw_powerdown_animation(player);
if (player_flags(&player->obj) & 0x40) {
// player_flags2(&player->other_player->obj) &= ~1;
}
--player->lifes_count;
play_sound(SOUND_14);
}
static void level_reset_palette() {
if (g_vars.reset_palette_flag) {
return;
}
g_vars.reset_palette_flag = 1;
memcpy(g_vars.palette_buffer, common_palette_data, 384);
const uint8_t *pal = levels_palette_data + g_vars.level_num * 144 * 3;
memcpy(g_vars.palette_buffer + 384, pal, 384); pal += 384;
if ((g_vars.level & 1) != 0) {
memcpy(g_vars.palette_buffer + 384 - 48, pal, 48);
}
for (int i = 0; i < 8; ++i) {
uint8_t *dst = g_vars.palette_buffer + 384 + 48 * i;
dst[0] = 0;
dst[1] = 0x20;
dst[2] = 0x3F;
}
g_sys.set_screen_palette(g_vars.palette_buffer, 0, 256, 6);
}
static void level_update_palette() {
if (g_vars.level_time == 12 || g_vars.level_time2 == g_vars.level_time) {
return;
}
if (g_vars.level_time2 < g_vars.level_time) {
g_vars.level_time2 = g_vars.level_time;
g_vars.reset_palette_flag = 0;
level_reset_palette();
return;
}
g_vars.level_time2 = g_vars.level_time;
if (g_vars.level_time < 5) {
play_sound(SOUND_15);
uint8_t *palette_buffer = g_vars.palette_buffer + 128 * 3;
for (int i = 0; i < 128 * 3; ++i) {
const int color = palette_buffer[i];
palette_buffer[i] = MAX(0, color - 8);
}
g_sys.set_screen_palette(palette_buffer, 128, 128, 6);
if (g_vars.level_time == 0) {
level_player_die(&g_vars.players_table[0]);
level_player_die(&g_vars.players_table[1]);
play_sound(SOUND_14);
}
}
}
static uint8_t level_lookup_tile(uint8_t num) {
const uint8_t type = g_vars.tilemap_lut_type[num];
if (type == 2) {
if ((g_vars.tilemap_flags & 2) != 0 && (g_vars.level_loop_counter & 0x20) != 0) {
return 0;
}
if ((g_vars.tilemap_flags & 4) == 0) {
const uint8_t flags = g_vars.tilemap_lut_init2[num] ^ g_vars.tilemap_flags;
if (flags & 1) {
return 0;
}
}
}
num = g_vars.tilemap_current_lut[num];
if (num == 248 && (g_vars.tilemap_flags & 1) != 0) {
++num;
}
return num;
}
static uint8_t level_get_tile(int offset) {
if (offset < 0 || offset >= g_vars.tilemap_w * g_vars.tilemap_h) {
print_warning("Invalid tilemap offset %d", offset);
return 0;
}
int num = g_vars.tilemap_data[offset];
num = level_lookup_tile(num);
return g_vars.level_tiles_lut[num];
}
static void level_draw_tile(uint8_t tile_num, int x, int y, int dx, int dy) {
tile_num = level_lookup_tile(tile_num);
// video_set_palette_index(g_vars.tile_palette_table[tile_num]);
ja_decode_tile(g_res.tmp + tile_num * 128, 0x80 | (g_vars.tile_palette_table[tile_num] * 0x10), g_res.vga, GAME_SCREEN_W, x * 16 - dx, y * 16 - dy);
}
static void level_draw_tilemap() {
video_copy_backbuffer(PANEL_H);
const uint8_t *current_lut = g_vars.tilemap_current_lut + 0x100;
if (current_lut >= &g_vars.tilemap_lut_init[0x600]) {
g_vars.tilemap_current_lut = g_vars.tilemap_lut_init;
} else {
g_vars.tilemap_current_lut = current_lut;
}
for (int y = 0; y < TILEMAP_SCREEN_H / 16 + 1; ++y) {
const int y_offset = g_vars.tilemap_y + y;
if (!(y_offset >= 0 && y_offset < g_vars.tilemap_h)) {
continue;
}
for (int x = 0; x < TILEMAP_SCREEN_W / 16 + 1; ++x) {
const int x_offset = g_vars.tilemap_x + x;
if (!(x_offset >= 0 && x_offset < g_vars.tilemap_w)) {
continue;
}
const uint8_t num = g_vars.tilemap_data[y_offset * g_vars.tilemap_w + x_offset];
level_draw_tile(num, x, y, g_vars.tilemap_scroll_dx << 2, g_vars.tilemap_scroll_dy);
}
}
}
static void level_adjust_hscroll_right(int dx) {
if (g_vars.tilemap_x >= g_vars.tilemap_end_x) {
const int8_t _al = 3 - g_vars.tilemap_scroll_dx;
if (_al < dx) {
dx = _al;
}
}
g_vars.tilemap_scroll_dx += dx;
if (g_vars.tilemap_scroll_dx < 4) {
return;
}
g_vars.tilemap_scroll_dx -= 4;
++g_vars.tilemap_x;
}
static void level_adjust_hscroll_left(int dx) {
g_vars.tilemap_scroll_dx -= dx;
if (g_vars.tilemap_scroll_dx >= 0) {
return;
}
g_vars.tilemap_scroll_dx += 4;
--g_vars.tilemap_x;
if (g_vars.tilemap_x < 0) {
g_vars.tilemap_x = 0;
g_vars.tilemap_scroll_dx = 0;
}
}
static void level_adjust_vscroll_down(int dy) {
if (g_vars.tilemap_y >= g_vars.tilemap_end_y) {
const int8_t _al = 15 - g_vars.tilemap_scroll_dy;
if (_al < dy) {
dy = _al;
}
}
g_vars.tilemap_scroll_dy += dy;
if (g_vars.tilemap_scroll_dy < 16) {
return;
}
g_vars.tilemap_scroll_dy -= 16;
++g_vars.tilemap_y;
}
static void level_adjust_vscroll_up(int dy) {
g_vars.tilemap_scroll_dy -= dy;
if (g_vars.tilemap_scroll_dy >= 0) {
return;
}
g_vars.tilemap_scroll_dy += 16;
--g_vars.tilemap_y;
if (g_vars.tilemap_y < 0) {
g_vars.tilemap_scroll_dy = 0;
g_vars.tilemap_y = 0;
}
}
static void level_hit_player(struct player_t *p) {
player_flags(&g_vars.players_table[0].obj) &= ~0x40;
player_flags(&g_vars.players_table[1].obj) &= ~0x40;
player_flags2(&g_vars.players_table[0].obj) &= ~1;
player_flags2(&g_vars.players_table[1].obj) &= ~1;
}
static void level_player_hit_object(struct player_t *p, struct object_t *obj) {
if (g_options.cheats & CHEATS_OBJECT_NO_HIT) {
return;
}
if (object_blinking_counter(obj) != 0 || g_vars.level_loop_counter <= 20) {
return;
}
play_sound(SOUND_4);
if ((player_flags(&p->obj) & PLAYER_FLAGS_POWERUP) == 0 && (--p->energy < 0)) {
p->energy = 0;
level_player_die(p);
}
player_hit_counter(&p->obj) = 8;
player_y_delta(&p->obj) = -40;
if (player_flags(&p->obj) & PLAYER_FLAGS_POWERUP) {
level_player_draw_powerdown_animation(p);
}
level_hit_player(p);
object_blinking_counter(obj) = 33;
}
static void level_player_update_xvelocity(struct player_t *player, int16_t x) {
if ((player_flags2(&player->obj) & 2) == 0) {
x *= 8;
if (x == 0) {
if (player_x_delta(&player->obj) != 0) {
player_x_delta(&player->obj) += (player_x_delta(&player->obj) >= 0) ? -8 : 8;
const int dx = abs(player_x_delta(&player->obj));
if (dx < 8) {
player_x_delta(&player->obj) = 0;
}
}
} else if (x < 0) {
if (player_x_delta(&player->obj) + x < -32) {
player_x_delta(&player->obj) = -32;
} else {
player_x_delta(&player->obj) += x;
}
} else {
if (player_x_delta(&player->obj) + x > 32) {
player_x_delta(&player->obj) = 32;
} else {
player_x_delta(&player->obj) += x;
}
}
}
}
static struct object_t *find_object(int offset, int count, int stride) {
for (int i = 0; i < count; ++i) {
struct object_t *obj = &g_vars.objects_table[offset + i * stride];
if (obj->spr_num == 0xFFFF) {
return obj;
}
}
print_warning("No free object %d count %d", offset, count / stride);
return 0;
}
// small stars
static void level_init_object_spr_num_46(int x_pos, int y_pos) {
struct object_t *obj = find_object(2, 20, 1);
if (obj) {
obj->x_pos = x_pos;
obj->y_pos = y_pos;
obj->spr_num = 46;
object2_spr_count(obj) = 4;
object2_spr_tick(obj) = 1;
object_blinking_counter(obj) = 0;
}
}
// large star
static void level_init_object_spr_num_92(int x_pos, int y_pos) {
struct object_t *obj = find_object(2, 20, 1);
if (obj) {
obj->x_pos = x_pos;
obj->y_pos = y_pos;
obj->spr_num = 92;
object2_spr_count(obj) = 4;
object2_spr_tick(obj) = 1;
object_blinking_counter(obj) = 0;
}
}
// light
static void level_init_object_spr_num_98(int x_pos, int y_pos) {
struct object_t *obj = find_object(2, 20, 1);
if (obj) {
obj->x_pos = x_pos;
obj->y_pos = y_pos;
obj->spr_num = 98;
object2_spr_count(obj) = 4;
object2_spr_tick(obj) = 1;
object_blinking_counter(obj) = 0;
}
}
// light
static void level_init_object_spr_num_98_align_pos(int x_pos, int y_pos) {
struct object_t *obj = find_object(2, 20, 1);
if (obj) {
obj->x_pos = (x_pos << 4) + 8;
obj->y_pos = (y_pos << 4) + 12;
obj->spr_num = 98;
object2_spr_count(obj) = 6;
object2_spr_tick(obj) = 1;
object_blinking_counter(obj) = 0;
}
}
// vinyl fade animation
static void level_init_object_spr_num_104(int x_pos, int y_pos) {
struct object_t *obj = find_object(2, 20, 1);
if (obj) {
obj->x_pos = (x_pos << 4) + 8;
obj->y_pos = (y_pos << 4) + 8;
obj->spr_num = 104;
object2_spr_count(obj) = 6;
object2_spr_tick(obj) = 3;
object_blinking_counter(obj) = 0;
}
}
static void level_update_bouncing_tiles(struct player_t *player) {
const int offset = player_tilemap_offset(&player->obj);
uint8_t *p = &g_vars.tilemap_data[offset];
int num = *p;
int adj = 2;
if (num > 0x20 && (num <= 0x22 || num > 0x9C)) {
adj = -adj;
}
if ((num & 1) == 0) {
--p;
}
p[0] += adj;
p[1] += adj;
p[g_vars.tilemap_w] += adj;
p[g_vars.tilemap_w + 1] += adj;
}
static int level_player_update_flags(struct player_t *player) {
if (player_flags2(&player->obj) & 0x40) {
return 1;
}
if (abs(player_x_delta(&player->obj)) < 24) {
return 0;
}
player_flags(&player->obj) &= ~PLAYER_FLAGS_FACING_LEFT;
if (player_x_delta(&player->obj) < 0) {
player_flags(&player->obj) |= PLAYER_FLAGS_FACING_LEFT;
}
if (g_vars.level_loop_counter - player->ticks_counter < 4) {
return 1;
}
player->ticks_counter = g_vars.level_loop_counter;
if (player_flags2(&player->obj) & 2) {
return 1;
}
level_init_object_spr_num_98(player->obj.x_pos, player->obj.y_pos);
play_sound(SOUND_11);
return 1;
}
static void level_player_update_hdirection(struct player_t *player) {
if (g_vars.input_key_right) {
level_player_update_xvelocity(player, 2);
} else if (g_vars.input_key_left) {
level_player_update_xvelocity(player, -2);
} else {
level_player_update_xvelocity(player, 0);
}
}
static void level_update_player_anim_1(struct player_t *player) {
player->obj.spr_num = 1;
level_player_update_xvelocity(player, 0);
if (player_throw_counter(&player->obj) != 0) {
player->obj.spr_num = 0;
}
}
static void level_update_player_anim_5(struct player_t *player) {
level_player_update_hdirection(player);
player->obj.spr_num = 5;
if (player_jump_counter(&player->obj) != 0 && (player_flags2(&player->obj) & 1) == 0 && (g_options.jump_button ? g_vars.input_key_jump : g_vars.input_key_up)) {
--player_jump_counter(&player->obj);
const int dy = (int8_t)player_anim_dy[player_jump_counter(&player->obj)] + player_y_delta(&player->obj);
if (dy >= -70) {
player_y_delta(&player->obj) = dy;
}
}
}
static void level_update_player_anim_2(struct player_t *player) {
if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_5(player);
} else {
player->obj.spr_num = 2;
level_player_update_hdirection(player);
}
}
static void level_update_player_anim_3(struct player_t *player) {
level_player_update_hdirection(player);
player->obj.spr_num = 3;
}
static void level_update_player_anim_4(struct player_t *player) {
player->obj.spr_num = 4;
level_player_update_xvelocity(player, 0);
}
static void level_update_player_anim_34(struct player_t *player) {
player->obj.spr_num = 0;
if (level_player_update_flags(player)) {
player->obj.spr_num = 34;
}
level_player_update_xvelocity(player, 0);
}
static void level_update_player_anim_0(struct player_t *player) {
if (player_flags2(&player->obj) & 1) {
level_update_player_anim_34(player);
} else if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_5(player);
} else if (player->obj.spr_num == 1) {
level_update_player_anim_1(player);
} else if (player_idle_counter(&player->obj) != 90) {
level_update_player_anim_34(player);
} else {
player->obj.spr_num = 1;
level_player_update_xvelocity(player, 0);
}
}
// throw vinyl
static void level_update_player_anim_6(struct player_t *player) {
if (player->vinyls_count == 0) {
play_sound(SOUND_8);
level_update_player_anim_0(player);
} else {
level_player_update_xvelocity(player, 0);
player->obj.spr_num = 6;
if (player_throw_counter(&player->obj) == 0) {
return;
}
if (g_vars.level_loop_counter - g_vars.throw_vinyl_counter <= _vinyl_throw_delay) {
return;
}
g_vars.throw_vinyl_counter = g_vars.level_loop_counter;
struct object_t *obj = find_object(22, 10, 1);
if (obj) {
obj->spr_num = 42; // vinyl
object_blinking_counter(obj) = 0;
object22_player_num(obj) = player - &g_vars.players_table[0];
obj->x_pos = player->obj.x_pos;
obj->y_pos = player->obj.y_pos - 12;
object22_xvelocity(obj) = (player_flags(&player->obj) & PLAYER_FLAGS_FACING_LEFT) ? -24 : 24;
int ax = ((player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) != 0) ? 0x200 : 0x100;
if (player_power(&player->obj) >= 33) {
ax = 0x4000;
if ((g_options.cheats & CHEATS_UNLIMITED_VINYLS) == 0) {
player->vinyls_count -= 4;
}
}
if ((g_options.cheats & CHEATS_UNLIMITED_VINYLS) == 0) {
--player->vinyls_count;
}
object22_damage(obj) = ax;
player_flags(&player->obj) |= PLAYER_FLAGS_THROW_VINYL;
player_power(&player->obj) = 0;
play_sound(SOUND_9);
}
}
}
static void level_update_player_anim_7(struct player_t *player) {
if (player->vinyls_count == 0) {
play_sound(SOUND_8);
level_update_player_anim_0(player);
} else {
player->obj.y_pos += 12;
level_update_player_anim_6(player);
player->obj.y_pos -= 12;
}
player->obj.spr_num = 7;
}
static void level_update_player_anim_8(struct player_t *player) {
player->obj.spr_num = 8;
player->obj.y_pos -= 2;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_9(struct player_t *player) {
player->obj.spr_num = 9;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_10(struct player_t *player, int spr_num) {
player->obj.spr_num = spr_num;
player->obj.y_pos += 2;
const int x = (player->obj.x_pos & 15) - 8;
if (x < 0) {
++player->obj.x_pos;
} else if (x > 0) {
--player->obj.x_pos;
}
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_11(struct player_t *player) {
player->obj.spr_num = 9;
--player->obj.x_pos;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_12(struct player_t *player) {
player->obj.spr_num = 9;
++player->obj.x_pos;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_18(struct player_t *player) {
level_player_update_hdirection(player);
player->obj.spr_num = 18;
if (player_jump_counter(&player->obj) != 0 && (g_options.jump_button ? g_vars.input_key_jump : g_vars.input_key_up)) {
--player_jump_counter(&player->obj);
player_y_delta(&player->obj) += (int8_t)player_anim_dy[player_jump_counter(&player->obj)];
}
}
static void level_update_player_anim_15(struct player_t *player) {
if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_18(player);
} else {
player->obj.spr_num = 15;
if (g_vars.input_key_right) {
level_player_update_xvelocity(player, 2);
} else if (g_vars.input_key_left) {
level_player_update_xvelocity(player, -2);
} else {
level_player_update_xvelocity(player, 0);
}
}
}
static void level_update_player_anim_13(struct player_t *player) {
if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_18(player);
} else {
player->obj.spr_num = 13;
level_player_update_flags(player);
level_player_update_xvelocity(player, 0);
}
}
static void level_update_player_anim_19(struct player_t *player) {
if (player->vinyls_count == 0) {
play_sound(SOUND_8);
level_update_player_anim_13(player);
} else {
level_update_player_anim_6(player);
player->obj.spr_num = 19;
}
}
static void level_update_player_anim_26(struct player_t *player) {
level_player_update_hdirection(player);
player->obj.spr_num = 26;
if (player_jump_counter(&player->obj) == 0 || (g_options.jump_button ? !g_vars.input_key_jump : !g_vars.input_key_up)) {
return;
}
--player_jump_counter(&player->obj);
player_y_delta(&player->obj) += (int8_t)player_anim_dy[player_jump_counter(&player->obj)];
}
static void level_update_player_anim_27(struct player_t *player) {
player->obj.spr_num = 27;
level_player_update_xvelocity(player, 0);
if (player_throw_counter(&player->obj) == 0) {
return;
}
if (player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) {
player->obj.spr_num = 35;
}
player_flags(&player->obj) |= PLAYER_FLAGS_THROW_VINYL;
player_flags(&player->obj) &= ~0x40;
// struct player_t *other_player = player->other_player;
}
static void level_update_player_anim_28(struct player_t *player) {
player->obj.y_pos += 12;
level_update_player_anim_27(player);
player->obj.y_pos -= 12;
if (player_throw_counter(&player->obj) != 0 && (player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) != 0) {
player_obj_num(&player->obj) = 35;
} else {
player_obj_num(&player->obj) = 28;
}
}
static void level_update_player_anim_29(struct player_t *player) {
player->obj.spr_num = 29;
player->obj.y_pos -= 2;
const int x = (player->obj.x_pos & 15) - 8;
if (x < 0) {
++player->obj.x_pos;
} else if (x > 0) {
--player->obj.x_pos;
}
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_30(struct player_t *player) {
player->obj.spr_num = 30;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_32(struct player_t *player) {
player->obj.spr_num = 30;
--player->obj.x_pos;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_anim_33(struct player_t *player) {
player->obj.spr_num = 30;
++player->obj.x_pos;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
}
static void level_update_player_from_input(struct player_t *player) {
if (player_flags2(&player->obj) & 8) { // dead
if ((player_flags2(&player->obj) & 1) == 0) {
player_y_delta(&player->obj) = -120;
player_x_delta(&player->obj) = (player->obj.x_pos < (g_vars.tilemap_x * 16 + TILEMAP_SCREEN_W / 2)) ? 24 : -24;
player_flags2(&player->obj) |= 1;
}
player->obj.x_pos += player_x_delta(&player->obj) >> 3;
player->obj.y_pos += player_y_delta(&player->obj) >> 3;
player_y_delta(&player->obj) = MIN(player_y_delta(&player->obj) + 7, 120);
if ((player->obj.y_pos >> 4) - g_vars.tilemap_y > TILEMAP_SCREEN_H / 16 + 3) {
player_flags2(&player->obj) |= 0x10;
}
player_flags(&player->obj) &= ~PLAYER_FLAGS_THROW_VINYL;
if (g_vars.input_key_space) {
player_flags(&player->obj) |= PLAYER_FLAGS_THROW_VINYL;
}
return;
}
if (player->unk_counter != 0) {
--player->unk_counter;
}
if (player->change_hdir_counter != 0) {
--player->change_hdir_counter;
}
player_flags2(&player->obj) &= ~4;
player_flags2(&player->obj) |= (g_vars.input_key_down & 4);
if ((player_flags(&player->obj) & PLAYER_FLAGS_THROW_VINYL) != 0 && player->vinyls_count > 4) {
if (player_power(&player->obj) < 255) {
++player_power(&player->obj);
}
} else {
player_power(&player->obj) = 0;
}
uint8_t flags = player_flags(&player->obj);
if (g_vars.input_key_right) {
player_flags(&player->obj) &= ~PLAYER_FLAGS_FACING_LEFT;
}
if (g_vars.input_key_left) {
player_flags(&player->obj) |= PLAYER_FLAGS_FACING_LEFT;
}
if (((flags ^ player_flags(&player->obj)) & PLAYER_FLAGS_FACING_LEFT) != 0) { // horizontal direction changed
player->change_hdir_counter += 4;
}
// flags = player_flags(&player->obj);
uint8_t mask = 0;
if (flags & PLAYER_FLAGS_STAIRS) {
mask |= 4;
}
if (flags & 0x40) {
mask |= 2;
}
if (flags & PLAYER_FLAGS_POWERUP) {
mask |= 1;
}
flags = g_vars.input_key_space & 1;
if (flags == 0) {
if (player_power(&player->obj) >= 33 && (player_flags(&player->obj) & PLAYER_FLAGS_THROW_VINYL) != 0) {
flags = 1;
} else {
player_flags(&player->obj) &= ~PLAYER_FLAGS_THROW_VINYL;
}
} else {
if (player_flags(&player->obj) & PLAYER_FLAGS_THROW_VINYL) {
flags = 0;
}
}
mask = (mask << 1) | flags;
if (player_flags2(&player->obj) & 1) {
mask <<= 4;
mask |= 2;
} else {
mask <<= 4;
if ((player_flags(&player->obj) & 0x10) == 0) {
mask |= (g_options.jump_button ? g_vars.input_key_jump : g_vars.input_key_up) & 8;
}
mask |= (g_vars.input_key_right & 4);
if (player_jump_counter(&player->obj) == 7) {
mask |= ((player_flags(&player->obj) & 0x10) == 0) ? (g_vars.input_key_down & 2) : 2;
}
mask |= (g_vars.input_key_left & 1);
}
if (player_flags2(&player->obj) & 0x40) {
player->dir_mask = mask;
mask = 0;
}
switch (player_anim_lut[mask]) {
case 0:
level_update_player_anim_0(player);
break;
case 1:
level_update_player_anim_1(player);
break;
case 2:
level_update_player_anim_2(player);
break;
case 3:
level_update_player_anim_3(player);
break;
case 4:
level_update_player_anim_4(player);
break;
case 5:
level_update_player_anim_5(player);
break;
case 6:
level_update_player_anim_6(player);
break;
case 7:
level_update_player_anim_7(player);
break;
case 8:
level_update_player_anim_8(player);
break;
case 9:
level_update_player_anim_9(player);
break;
case 10:
level_update_player_anim_10(player, 10);
break;
case 11:
level_update_player_anim_11(player);
break;
case 12:
level_update_player_anim_12(player);
break;
case 13:
case 35:
level_update_player_anim_13(player);
break;
case 15:
level_update_player_anim_15(player);
break;
case 17:
if (player_y_delta(&player->obj) != 0) {
level_update_player_anim_13(player);
} else {
player->obj.spr_num = 17;
level_player_update_xvelocity(player, 0);
}
break;
case 18:
level_update_player_anim_18(player);
break;
case 19:
level_update_player_anim_19(player);
break;
case 20:
if (player->vinyls_count == 0) {
play_sound(SOUND_8);
level_update_player_anim_13(player);
} else {
player->obj.y_pos += 12;
level_update_player_anim_6(player);
player->obj.y_pos -= 12;
}
player->obj.spr_num = 20;
break;
case 21:
if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_26(player);
} else {
player->obj.spr_num = 21;
level_player_update_flags(player);
level_player_update_xvelocity(player, 0);
}
break;
case 23:
if (player_jump_counter(&player->obj) != 7) {
level_update_player_anim_26(player);
} else {
player->obj.spr_num = 23;
if (g_vars.input_key_right) {
level_player_update_xvelocity(player, 2);
} else if (g_vars.input_key_left) {
level_player_update_xvelocity(player, -2);
} else {
level_player_update_xvelocity(player, 0);
}
}
break;
case 25:
if (player_y_delta(&player->obj) != 0) {
level_update_player_anim_0(player);
} else {
player->obj.spr_num = 25;
level_player_update_xvelocity(player, 0);
}
break;
case 26:
level_update_player_anim_26(player);
break;
case 27:
level_update_player_anim_27(player);
break;
case 28:
level_update_player_anim_28(player);
break;
case 29:
level_update_player_anim_29(player);
break;
case 30:
level_update_player_anim_30(player);
break;
case 31:
level_update_player_anim_10(player, 31);
break;
case 32:
level_update_player_anim_32(player);
break;
case 33:
level_update_player_anim_33(player);
break;
default:
print_warning("Unhandled anim_lut %d mask %d", player_anim_lut[mask], mask);
break;
}
if (g_vars.tilemap_h <= (player->obj.y_pos >> 4)) {
level_player_die(player);
}
int ax = player_x_delta(&player->obj) >> 3;
player->obj.x_pos += ax;
int dx = g_vars.tilemap_w << 4;
if (player->obj.x_pos >= dx || player->obj.x_pos < 0) {
player->obj.x_pos -= ax;
ax = 0;
}
if (g_vars.player_xscroll != 0) {
g_vars.player_xscroll += ax;
}
player->obj.y_pos += player_y_delta(&player->obj) >> 3;
int x = (g_vars.tilemap_x << 4) + 4;
if (player_x_delta(&player->obj) <= 0 && player->obj.x_pos < x) {
player->obj.x_pos = x;
}
if (player_x_delta(&player->obj) >= 0) {
x += TILEMAP_SCREEN_W - 4;
if (player->obj.x_pos > x) {
player->obj.x_pos = x;
}
}
}
static void level_update_player_position() {
assert(g_vars.player != 2);
struct player_t *bp = &g_vars.players_table[0];
if ((player_flags2(&bp->obj) & 8) == 0) {
if ((g_vars.input_key_right | g_vars.input_key_left) == 0) {
}
}
assert(g_vars.player != 2);
struct object_t *obj = &g_vars.players_table[0].obj;
if (player_flags2(obj) & 8) {
return;
}
if (player_flags2(obj) & 1) {
obj = &g_vars.players_table[1].obj;
}
if ((player_flags(obj) & 1) == 0 && (player_x_delta(obj) == 0)) {
g_vars.player_xscroll = 0;
}
if (g_vars.player_xscroll != 0) {
const int dx = MIN(abs(g_vars.player_xscroll) >> 4, 2);
if (dx == 0) {
g_vars.player_xscroll = obj->x_pos - (g_vars.tilemap_x << 4) - TILEMAP_SCREEN_W / 2;
} else {
if (g_vars.player_xscroll > 0) {
g_vars.player_xscroll -= dx << 2;
if (g_vars.player_xscroll < 0) {
g_vars.player_xscroll = 0;
}
level_adjust_hscroll_right(dx);
} else {
g_vars.player_xscroll += dx << 2;
if (g_vars.player_xscroll > 0) {
g_vars.player_xscroll = 0;
}
level_adjust_hscroll_left(dx);
}
}
} else {
g_vars.player_xscroll += obj->x_pos - (g_vars.tilemap_x << 4) - TILEMAP_SCREEN_W / 2;
}
const int y = (g_vars.tilemap_y << 4) + g_vars.tilemap_scroll_dy + (TILEMAP_SCREEN_H / 2) - obj->y_pos;
if (y >= 0) {
const int dy = (int8_t)vscroll_offsets_table[y];
level_adjust_vscroll_up(dy);
} else {
const int dy = (int8_t)vscroll_offsets_table[-y];
level_adjust_vscroll_down(dy);
}
}
static void level_update_input() {
if (g_vars.input_keystate[0x19]) {
}
if (g_vars.input_keystate[0x3B]) {
if (player_flags2(&g_vars.players_table[0].obj) & 8) {
level_player_die(&g_vars.players_table[0]);
}
assert(g_vars.player != 2);
}
if (g_vars.input_keystate[0x3C]) {
g_vars.players_table[0].lifes_count = 0;
g_vars.players_table[1].lifes_count = 0;
player_flags2(&g_vars.players_table[0].obj) = 8;
player_flags2(&g_vars.players_table[1].obj) = 8;
}
level_update_player_from_input(&g_vars.players_table[0]);
level_update_player_position();
}
static int level_collide_objects(const struct object_t *si, const struct object_t *di) {
if (di->spr_num == 0xFFFF) {
return 0;
}
const int dx = abs(si->x_pos - di->x_pos);
if (dx >= 80) {
return 0;
}
const int dy = abs(si->y_pos - di->y_pos);
if (dy >= 70) {
return 0;
}
// if (g_vars.input_keystate[0x2A]) {
int d = di->y_pos + (sprite_offsets[di->spr_num & 0x1FFF] >> 8) - (sprite_sizes[di->spr_num & 0x1FFF] >> 8);
int a = si->y_pos + (sprite_offsets[si->spr_num & 0x1FFF] >> 8) - (sprite_sizes[si->spr_num & 0x1FFF] >> 8);
if (a < d) {
SWAP(a, d);
SWAP(si, di);
}
a -= sprite_offsets[si->spr_num & 0x1FFF] >> 8;
if (a >= d) {
return 0;
}
d = si->x_pos - (sprite_sizes[si->spr_num & 0x1FFF] & 255);
a = di->x_pos - (sprite_sizes[di->spr_num & 0x1FFF] & 255);
int b = sprite_offsets[di->spr_num & 0x1FFF] & 255;
if (a >= d) {
SWAP(a, d);
b = sprite_offsets[si->spr_num & 0x1FFF] & 255;
}
return (a + b >= d);
}
static int level_is_object_visible(int x_pos, int y_pos) {
const int x = abs((x_pos >> 4) - g_vars.tilemap_x - (TILEMAP_SCREEN_W / 32));
if (x < (TILEMAP_SCREEN_W / 32 + 4)) {
const int y = abs((y_pos >> 4) - g_vars.tilemap_y - (TILEMAP_SCREEN_H / 32));
return (y < 10) ? 1 : 0;
}
return 0;
}
static int level_is_respawn_object_visible(const uint8_t *data) {
const int x_pos = READ_LE_UINT16(data);
const int y_pos = READ_LE_UINT16(data + 2);
if (g_vars.level_loop_counter != 0) {
const int x = abs((x_pos >> 4) - g_vars.tilemap_x - (TILEMAP_SCREEN_W / 32));
if (x <= (TILEMAP_SCREEN_W / 32 + 2)) {
const int y = (y_pos >> 4) - g_vars.tilemap_y;
return (y >= 0 && y <= 12) ? 1 : 0;
}
}
return !level_is_object_visible(x_pos, y_pos);
}
static void init_object82(int type, int x_pos, int y_pos, struct object_t *obj, uint16_t *ptr) {
obj->data[2].w = ptr - g_vars.buffer;
object82_state(obj) = 0;
object82_type(obj) = type;
obj->x_pos = x_pos;
obj->y_pos = y_pos;
*ptr = obj - g_vars.objects_table;
object_blinking_counter(obj) = 0;
}
static uint8_t get_random_number() {
static uint8_t random_a = 5;
static uint8_t random_b = 34;
static uint8_t random_c = 134;
static uint16_t random_d = 58765;
random_d += random_a;
random_a += 3 + (random_d >> 8);
random_b += random_c;
random_b *= 2;
random_b += random_a;
random_c ^= random_a;
random_c ^= random_b;
return random_b;
}
static void level_init_object102_1(struct object_t *obj22, int x, int y, int num) {
struct object_t *obj = find_object(102, 10, 1);
if (obj) {
obj->x_pos = x;
obj->y_pos = y;
obj->spr_num = num;
object_blinking_counter(obj) = 0;
const int n = (get_random_number() & 7) << 3;
object102_x_delta(obj) = (object22_xvelocity(obj22) < 0) ? -n : n;
object102_y_delta(obj) = -(((get_random_number() & 7) + 4) << 3);
}
}
static void level_init_object102_2(struct object_t *obj22, int x, int y) {
struct object_t *obj = find_object(102, 10, 1);
if (obj) {
obj->x_pos = x + 8;
obj->y_pos = y + 12;
obj->spr_num = (get_random_number() & 1) + 96;
object_blinking_counter(obj) = 0;
object102_x_delta(obj) = (object22_xvelocity(obj22) < 0) ? -16 : 16;
object102_y_delta(obj) = -80;
}
}
static void level_player_collide_object(struct player_t *player, struct object_t *obj) {
if ((player_flags2(&player->obj) & 0x20) == 0) {
return;
}
if ((obj->y_pos & 15) < 12) {
return;
}
struct object_t *player_obj = &g_vars.objects_table[player_obj_num(&player->obj)];
if (level_collide_objects(obj, player_obj)) {
level_hit_player(player);
}
}
static bool level_collide_vinyl_decor(int x, int y, struct object_t *obj) {
const int offset = (y >> 4) * g_vars.tilemap_w + (x >> 4);
uint8_t al = g_vars.tilemap_data[offset];
al = g_vars.level_tiles_lut[al];
if (al == 3) {
object22_damage(obj) = 0;
obj->spr_num = 0xFFFF;
g_vars.tilemap_flags ^= 1;
play_sound(SOUND_3);
} else if (al == 9) {
g_vars.tilemap_data[offset] = 0;
level_init_object102_2(obj, x, y);
play_sound(SOUND_1);
} else if (al == 10) {
obj->spr_num = 0xFFFF;
} else {
return false;
}
if (object22_damage(obj) <= 0x200) {
obj->spr_num = 0xFFFF;
}
level_init_object_spr_num_98_align_pos(x, y);
return true;
}
static struct object_t *level_collide_vinyl_objects82(struct object_t *obj) {
for (int i = 0; i < 20; ++i) {
struct object_t *obj2 = &g_vars.objects_table[82 + i];
if (obj2->x_pos != -1 && object82_type(obj2) != 16 && object82_type(obj2) != 7) {
if (level_collide_objects(obj, obj2)) {
return obj2;
}
}
}
return 0;
}
static struct object_t *level_collide_vinyl_objects64(struct object_t *obj) {
for (int i = 0; i < 8; ++i) {
struct object_t *obj2 = &g_vars.objects_table[64 + i];
if (obj2->x_pos != -1 && (obj2->spr_num & 0x1FFF) == 199) {
if (level_collide_objects(obj, obj2)) {
return obj2;
}
}
}
return 0;
}
static int level_update_object82_position(struct object_t *obj, int8_t dx, int8_t dy) {
int ret = 0;
object82_hflip(obj) = dx;
obj->x_pos += dx >> 3;
if (obj->x_pos < 0) {
obj->x_pos = 0;
++ret;
}
if (g_vars.tilemap_w <= (obj->x_pos >> 4)) {
obj->x_pos -= 8;
++ret;
}
obj->y_pos += dy >> 3;
if (obj->y_pos < 0) {
obj->y_pos = 0;
}
return ret;
}
static struct player_t *level_get_closest_player(struct object_t *obj, int x_dist) {
struct player_t *player = &g_vars.players_table[0];
int dx = abs(obj->x_pos - player->obj.x_pos);
if (dx < x_dist) {
return player;
}
if (g_vars.objects_table[1].spr_num != 0xFFFF) {
++player;
dx = abs(obj->x_pos - player->obj.x_pos);
if (dx < x_dist) {
return player;
}
}
return 0;
}
static void level_update_object82_type0(struct object_t *obj) {
if (object82_anim_data(obj) != &monster_spr_anim_data0[2] && !level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
g_vars.buffer[128 + obj->data[2].w] = g_vars.level_loop_counter;
} else {
const int state = object82_state(obj);
if (state == 0) {
const int angle = object82_counter(obj);
const int16_t r1 = (60 * (int16_t)rotation_table[angle + 65]) >> 8;
object82_hflip(obj) = r1 >> 8;
const int16_t r2 = (60 * (int16_t)rotation_table[angle]) >> 8;
object82_counter(obj) += 2;
const uint8_t *p = object82_type0_init_data(obj);
obj->x_pos = r1 + READ_LE_UINT16(p);
obj->y_pos = r2 + READ_LE_UINT16(p + 2);
struct player_t *player = level_get_closest_player(obj, 80);
if (player) {
object82_type0_player_num(obj) = player - &g_vars.players_table[0];
object82_state(obj) = 1;
obj->data[6].b[0] = 0;
obj->data[7].b[0] = 0;
}
} else if (state == 1) {
struct player_t *player = &g_vars.players_table[object82_type0_player_num(obj)];
int16_t x_pos = player->obj.x_pos;
if ((player_flags2(&player->obj) & 0x40) != 0) {
x_pos += 60;
if ((player->dir_mask & 4) == 0) {
x_pos -= 120;
if ((player->dir_mask & 1) == 0) {
x_pos += 60;
if ((player_flags(&player->obj) & PLAYER_FLAGS_FACING_LEFT) != 0) {
x_pos -= 20;
} else {
x_pos = player->obj.x_pos;
}
}
}
}
x_pos -= obj->x_pos;
x_pos = (x_pos >> 8) | (1 + object82_type0_x_delta(obj));
int dx = 32;
if (x_pos < dx) {
dx = -dx;
if (x_pos > dx) {
dx = x_pos;
}
}
object82_type0_x_delta(obj) = dx;
int16_t y_pos = player->obj.y_pos;
if ((player_flags2(&player->obj) & 0x40) != 0) {
y_pos -= 60;
if ((player->dir_mask & 8) == 0) {
y_pos += 120;
if ((player->dir_mask & 2) == 0) {
y_pos -= 60;
}
}
}
y_pos -= obj->y_pos;
y_pos = (y_pos >> 8) | (1 + object82_type0_y_delta(obj));
int dy = 32;
if (y_pos < dy) {
dy = -dy;
if (y_pos > dy) {
dy = y_pos;
}
}
object82_type0_y_delta(obj) = dy;
if (level_update_object82_position(obj, object82_type0_x_delta(obj), object82_type0_y_delta(obj)) != 0) {
object82_type0_x_delta(obj) = -object82_type0_x_delta(obj);
}
}
}
}
// snail
static void level_update_object82_type1(struct object_t *obj) {
if (!level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
} else {
int dx = object82_x_delta(obj) + (int8_t)object82_type1_hdir(obj);
if (dx < 128 && dx > -128) {
object82_x_delta(obj) += dx;
}
if ((g_vars.level_loop_counter & 3) == 0) {
++object82_counter(obj);
if (object82_counter(obj) >= 75) {
object82_type1_hdir(obj) = -object82_type1_hdir(obj);
object82_counter(obj) = 0;
}
}
dx = obj->data[7].b[0] + object82_x_delta(obj);
obj->data[7].b[0] = dx;
obj->x_pos += (dx >> 8);
object82_hflip(obj) = object82_type1_hdir(obj);
level_update_tiles(obj, obj->x_pos, obj->y_pos, _undefined);
obj->y_pos += object82_y_delta(obj) >> 3;
}
}
static void level_update_object82_type3(struct object_t *obj) {
if (!level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
} else {
const int al = object82_state(obj);
if (al == 0) {
if (level_get_closest_player(obj, 160)) {
object82_state(obj) = 1;
object82_anim_data(obj) = monster_spr_anim_data3 + 4;
object82_counter(obj) = 40;
}
} else if (al == 1) {
level_update_tiles(obj, obj->x_pos, obj->y_pos, _undefined);
level_update_object82_position(obj, 0, object82_y_delta(obj));
if (--object82_counter(obj) == 0) {
object82_counter(obj) = 40;
object82_y_delta(obj) = -80;
}
}
}
}
static int level_is_object_on_tile_5dc8(struct object_t *obj) {
uint8_t tile_num;
const int offset = g_vars.player_map_offset;
tile_num = level_get_tile(offset - g_vars.tilemap_w);
if (tile_funcs2[tile_num] == 0x5DC8) {
return 0;
}
tile_num = level_get_tile(offset - g_vars.tilemap_w - 1);
if (tile_funcs2[tile_num] == 0x5DC8) {
return 1;
}
tile_num = level_get_tile(offset - g_vars.tilemap_w + 1);
if (tile_funcs2[tile_num] == 0x5DC8) {
return 1;
}
obj->data[0].b[1] = 0x10;
return 1;
}
static void level_update_object82_type2(struct object_t *obj) {
if (!level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
} else {
int state = object82_state(obj);
if (state == 0) {
struct player_t *player = level_get_closest_player(obj, 160);
if (player) {
object82_state(obj) = 1;
obj->data[5].w = -56;
obj->data[4].b[0] = obj->data[4].b[1];
obj->data[4].b[1] = -obj->data[4].b[1];
object82_anim_data(obj) += 2;
object82_counter(obj) = 4;
}
} else {
if (state == 1) {
if (obj->data[5].w == 0 && --object82_counter(obj) == 0) {
object82_state(obj) = state = 2;
object82_counter(obj) = 20;
}
}
if (state == 2) {
object82_anim_data(obj) = monster_spr_anim_data3;
if (--object82_counter(obj) != 0) {
goto update_tiles; // do not update position
}
object82_state(obj) = 0;
obj->data[5].w = 0;
obj->data[4].b[0] = 0;
}
}
if (level_update_object82_position(obj, obj->data[4].b[0], 0) != 0) {
obj->data[4].b[0] = -obj->data[4].b[0];
}
update_tiles:
level_update_tiles(obj, obj->x_pos, obj->y_pos, _undefined);
if (level_is_object_on_tile_5dc8(obj)) {
object82_x_delta(obj) = -object82_x_delta(obj);
if (level_update_object82_position(obj, obj->data[4].b[0], 0) != 0) {
obj->data[4].b[0] = -obj->data[4].b[0];
}
}
obj->y_pos += object82_y_delta(obj) >> 3;
}
}
// grass cutter
static void level_update_object82_type4_6(struct object_t *obj) {
if (!level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
} else {
const int state = object82_state(obj);
if (state == 0) {
struct player_t *player = level_get_closest_player(obj, 300);
if (player) {
object82_x_delta(obj) = (obj->x_pos < player->obj.x_pos) ? 8 : -8;
object82_state(obj) = 1;
}
} else if (state == 1) { // head to player
struct player_t *player = level_get_closest_player(obj, 300);
if (player) {
object82_x_delta(obj) <<= 2; // faster
object82_anim_data(obj) = (object82_type(obj) == 4) ? (monster_spr_anim_data6 + 10) : (monster_spr_anim_data8 + 26);
object82_state(obj) = 2;
}
}
level_update_tiles(obj, obj->x_pos, obj->y_pos, _undefined);
if (level_is_object_on_tile_5dc8(obj)) {
object82_x_delta(obj) = -object82_x_delta(obj);
}
object82_hflip(obj) = object82_x_delta(obj) >> 8;
if (level_update_object82_position(obj, object82_x_delta(obj), object82_y_delta(obj)) != 0) {
object82_x_delta(obj) = -object82_x_delta(obj);
}
}
}
// dragon
static void level_update_object82_type7(struct object_t *obj) {
level_update_object82_type0(obj);
int num = g_vars.dragon_coords[0];
const int x_pos = obj->x_pos;
const int y_pos = obj->y_pos;
g_vars.dragon_coords[(num + 2) / 2] = x_pos;
g_vars.dragon_coords[(num + 4) / 2] = y_pos;
if (player_flags2(&g_vars.players_table[0].obj) & 0x40) {
g_vars.players_table[0].obj.x_pos = x_pos;
g_vars.players_table[0].obj.y_pos = y_pos;
int spr_num = 6;
if (obj->spr_num & 0x8000) {
spr_num |= 0x8000;
}
if (player_flags(&g_vars.players_table[0].obj) & PLAYER_FLAGS_JAKE) {
spr_num += 50;
}
const int dx = (int8_t)obj->data[6].b[0];
player_x_delta(&g_vars.players_table[0].obj) = (dx >> 2) + dx;
}
if (player_flags2(&g_vars.players_table[1].obj) & 0x40) {
g_vars.players_table[1].obj.x_pos = x_pos;
g_vars.players_table[1].obj.y_pos = y_pos;
const int dx = (int8_t)obj->data[6].b[0];
player_x_delta(&g_vars.players_table[1].obj) = (dx >> 2) + dx;
}
num = (num + 4) & 0x7F;
g_vars.dragon_coords[0] = num;
for (int i = 0; i < 8; ++i) {
obj = &g_vars.objects_table[121 + i];
num -= 12;
if (num < 0) {
num += 128;
}
obj->x_pos = g_vars.dragon_coords[(num + 2) / 2];
obj->y_pos = g_vars.dragon_coords[(num + 4) / 2];
object_blinking_counter(obj) = 0;
}
}
// dead monster
static void level_update_object82_type16(struct object_t *obj) {
if (!level_is_object_visible(obj->x_pos, obj->y_pos)) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[2].w] = 0xFFFF;
object82_counter(obj) = 0;
g_vars.buffer[128 + obj->data[2].w] = g_vars.level_loop_counter;
} else {
obj->y_pos += object82_energy(obj) >> 3;
const int y_vel = object82_energy(obj) + 4;
if (y_vel < 128) {
object82_energy(obj) = y_vel;
}
}
}
static void level_update_triggers() {
const int count = g_vars.triggers_table[18];
const uint8_t *start = g_vars.triggers_table + 19;
const uint8_t *data = start;
uint16_t *ptr = g_vars.buffer;
for (int i = 0; i < count; ++i, data += 16, ++ptr) {
if (*ptr != 0xFFFF) {
continue;
}
const int x_pos = READ_LE_UINT16(data);
const int y_pos = READ_LE_UINT16(data + 2);
if (READ_LE_UINT16(data + 4) == 0 && READ_LE_UINT16(data + 6) == 9) {
const uint16_t num = READ_LE_UINT16(data + 8);
if (num == 12 || num == 25 || num == 8) {
if (!level_is_object_visible(x_pos, y_pos)) {
continue;
}
struct object_t *obj = find_object(32, 8, 4);
if (obj) {
int dx = 205;
if (num != 8) {
dx = 224; // ball with spikes
if (num == 12) {
WRITE_LE_UINT16(&g_vars.triggers_table[19] + i * 16 + 14, 0x260);
}
}
obj[0].spr_num = dx;
object_blinking_counter(&obj[0]) = 0;
obj[0].data[1].w = READ_LE_UINT16(data + 14);
obj[1].spr_num = 204;
object_blinking_counter(&obj[1]) = 0;
obj[2].spr_num = 204;
object_blinking_counter(&obj[2]) = 0;
obj[3].spr_num = 204;
object_blinking_counter(&obj[3]) = 0;
obj->data[2].b[1] = 128; // angle
obj->data[2].b[0] = 0;
obj->data[4].b[1] = 58; // radius
obj->data[4].b[0] = 0;
obj->data[5].w = 8; // angle step
*ptr = obj - g_vars.objects_table;
obj->data[0].w = data - start;
}
} else if (num == 2 || num == 24) {
if (level_is_respawn_object_visible(data)) {
continue;
}
struct object_t *obj = find_object(64, 8, 1);
if (obj) {
obj->spr_num = (num == 2) ? 199 : 205; // crate
obj->x_pos = x_pos;
obj->y_pos = y_pos;
object64_counter(obj) = READ_LE_UINT16(data + 14);
object_blinking_counter(obj) = 0;
*ptr = obj - g_vars.objects_table;
obj->data[0].w = data - start;
object64_yvelocity(obj) = 0;
}
} else if (num == 15) {
struct object_t *obj = find_object(82, 20, 1);
if (obj) {
obj->spr_num = 198; // jukebox
init_object82(5, x_pos, y_pos, obj, ptr);
object82_anim_data(obj) = monster_spr_anim_data9;
}
} else if (num == 19 || num == 3 || num == 16) {
if (abs((int16_t)ptr[128] - g_vars.level_loop_counter) < _object_respawn_delay) {
continue;
}
if (level_is_respawn_object_visible(data)) {
continue;
}
struct object_t *obj = find_object(82, 20, 1);
if (obj) {
obj->spr_num = 214; // mosquito
object82_anim_data(obj) = monster_spr_anim_data1;
int type = 0;
if (num == 3) {
obj->spr_num = 230; // dragon
object82_anim_data(obj) = monster_spr_anim_data0;
for (int i = 0; i < 128; ++i) {
g_vars.dragon_coords[1 + i] = -1;
}
g_vars.dragon_coords[0] = 0;
g_vars.objects_table[121].spr_num = 233;
g_vars.objects_table[122].spr_num = 233;
g_vars.objects_table[123].spr_num = 233;
g_vars.objects_table[124].spr_num = 233;
g_vars.objects_table[125].spr_num = 233;
g_vars.objects_table[126].spr_num = 233;
g_vars.objects_table[127].spr_num = 234;
g_vars.objects_table[128].spr_num = 235;
type = 7; // fall-through
obj->y_pos -= 60;
}
init_object82(type, x_pos, y_pos, obj, ptr);
object82_energy(obj) = 128;
object82_counter(obj) = 0;
object82_type0_init_data(obj) = data;
}
} else if (num == 20) {
if (level_is_respawn_object_visible(data)) {
continue;
}
struct object_t *obj = find_object(82, 20, 1);
if (obj) {
obj->spr_num = 217; // snail
init_object82(1, x_pos, y_pos, obj, ptr);
object82_anim_data(obj) = monster_spr_anim_data2;
object82_type1_hdir(obj) = -32;
object82_x_delta(obj) = 0;
obj->data[7].w = 0;
object82_counter(obj) = 0;
object82_y_delta(obj) = 0;
object82_energy(obj) = 512;
}
} else if (num == 21 || num == 18) {
if (abs((int16_t)ptr[128] - g_vars.level_loop_counter) < _object_respawn_delay) {
continue;
}
if (level_is_respawn_object_visible(data)) {
continue;
}
struct object_t *obj = find_object(82, 20, 1);
if (obj) {
uint8_t type;
if (num == 21) {
obj->spr_num = 228; // closed trap
type = 3;
object82_anim_data(obj) = monster_spr_anim_data3 + 24;
} else {
obj->spr_num = 225; // open trap
object82_anim_data(obj) = monster_spr_anim_data3;
type = 2;
}
init_object82(type, x_pos, y_pos, obj, ptr);
object82_y_delta(obj) = 0;
obj->data[4].b[0] = 0;
obj->data[7].p = data;
obj->data[4].b[1] = ((data[11] & 1) != 0) ? -40 : 40;
object82_energy(obj) = 768;
}
} else if (num == 26 || num == 5 || num == 17) {
if (abs((int16_t)ptr[128] - g_vars.level_loop_counter) < _object_respawn_delay) {
continue;
}
if (level_is_respawn_object_visible(data)) {
continue;
}
struct object_t *obj = find_object(82, 20, 1);
if (obj) {
obj->spr_num = 225; // grass cutter
uint8_t type;
if (num == 17) {
type = 4;
object82_anim_data(obj) = monster_spr_anim_data6;
} else {
type = 6;
object82_anim_data(obj) = monster_spr_anim_data8;
}
init_object82(type, x_pos, y_pos, obj, ptr);
obj->data[5].w = 0;
obj->data[4].b[0] = 0;
object82_energy(obj) = 512;
}
} else if (num == 14) {
if (!level_is_object_visible(x_pos, y_pos)) {
continue;
}
struct object_t *obj = find_object(112, 9, 1);
if (obj) {
obj->spr_num = 242; // vertical bar base
obj->x_pos = x_pos;
obj->y_pos = y_pos + 16;
obj->data[3].w = obj->y_pos;
obj->data[0].w = READ_LE_UINT16(data + 14);
object_blinking_counter(obj) = 0;
obj->data[2].w = 0;
*ptr = obj - g_vars.objects_table;
obj->data[1].w = data - start;
}
}
} else if (READ_LE_UINT16(data + 4) == 2 && level_is_object_visible(x_pos, y_pos)) { // bonus
struct object_t *obj = find_object(72, 10, 1);
if (obj) {
const uint16_t num = READ_LE_UINT16(data + 8);
obj->spr_num = 190 + bonus_spr_table[num];
object_blinking_counter(obj) = 0;
obj->x_pos = x_pos;
obj->y_pos = y_pos;
*ptr = obj - g_vars.objects_table;
obj->data[0].w = data - start;
}
}
}
for (int i = 0; i < 8; ++i) {
struct object_t *obj = &g_vars.objects_table[32 + i * 4];
if (obj->spr_num == 0xFFFF) {
continue;
}
obj->data[1].w += (obj->data[2].w & 0x8000) ? -3 : 3;
const uint8_t *_di = start + obj->data[0].w;
if (!level_is_object_visible(READ_LE_UINT16(_di), READ_LE_UINT16(_di + 2))) {
obj[0].spr_num = 0xFFFF;
obj[1].spr_num = 0xFFFF;
obj[2].spr_num = 0xFFFF;
obj[3].spr_num = 0xFFFF;
g_vars.buffer[obj->data[0].w / 16] = 0xFFFF;
} else {
if (READ_LE_UINT16(_di + 8) == 25) {
obj->data[4].w += obj->data[5].w;
if (obj->data[4].w == 24000 || obj->data[4].w == 10240) {
obj->data[5].w = -obj->data[5].w;
}
obj->data[1].w = (-(obj->data[4].w - 30000)) >> 4;
}
const int x_pos = obj->x_pos;
obj[0].x_pos = obj[1].x_pos = obj[2].x_pos = obj[3].x_pos = READ_LE_UINT16(_di);
obj[0].y_pos = obj[1].y_pos = obj[2].y_pos = obj[3].y_pos = READ_LE_UINT16(_di + 2);
int dx, ax = obj[0].data[1].w * 2;
obj[0].data[2].w += ax;
const int angle = obj[0].data[2].b[1];
const int radius = obj[0].data[4].b[1];
ax = (radius * (int16_t)rotation_table[angle + 65]) >> 8;
obj[0].x_pos += ax;
ax >>= 1;
dx = ax;
obj[2].x_pos += ax;
ax >>= 1;
dx += ax;
obj[3].x_pos += ax;
obj[1].x_pos += dx;
ax = (radius * (int16_t)rotation_table[angle]) >> 8;
obj[0].y_pos += ax;
ax >>= 1;
dx = ax;
obj[2].y_pos += ax;
ax >>= 1;
dx += ax;
obj[3].y_pos += ax;
obj[1].y_pos += dx;
obj[0].data[3].w = x_pos - obj[0].x_pos;
}
}
for (int i = 0; i < 8; ++i) {
struct object_t *obj = &g_vars.objects_table[64 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
const int timer = object64_counter(obj);
if (timer == 0) { // fall
++object64_yvelocity(obj);
if (obj->y_pos < 2048) {
obj->y_pos += object64_yvelocity(obj);
}
} else if (timer <= 20) {
obj->x_pos ^= (g_vars.level_loop_counter & 1);
}
const uint8_t *p = start + obj->data[0].w;
if (!level_is_object_visible(READ_LE_UINT16(p), READ_LE_UINT16(p + 2))) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[0].w / 16] = 0xFFFF;
}
}
for (int i = 0; i < 10; ++i) {
struct object_t *obj = &g_vars.objects_table[72 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
const uint8_t *p = start + obj->data[0].w;
if (!level_is_object_visible(READ_LE_UINT16(p), READ_LE_UINT16(p + 2))) {
obj->spr_num = 0xFFFF;
g_vars.buffer[obj->data[0].w / 16] = 0xFFFF;
}
}
for (int i = 0; i < 20; ++i) {
struct object_t *obj = &g_vars.objects_table[2 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
if ((object2_spr_tick(obj) & g_vars.level_loop_counter) == 0) {
--object2_spr_count(obj);
if (object2_spr_count(obj) != 0) {
++obj->spr_num;
} else {
obj->spr_num = 0xFFFF;
}
}
}
for (int i = 0; i < 9; ++i) {
struct object_t *obj = &g_vars.objects_table[112 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
int offset;
while (1) {
obj->data[2].w += obj->data[0].w;
obj->y_pos += obj->data[2].w >> 8;
obj->data[2].w &= 0xFF;
offset = (obj->y_pos >> 4) * g_vars.tilemap_w + (obj->x_pos >> 4);
if (offset < 0) {
print_warning("Invalid object #112 position %d,%d", obj->x_pos, obj->y_pos);
break;
}
if (obj->data[0].w < 0) {
offset -= g_vars.tilemap_w;
if (obj->y_pos > obj->data[3].w) {
break;
}
} else {
level_player_collide_object(&g_vars.players_table[0], obj);
level_player_collide_object(&g_vars.players_table[1], obj);
uint8_t ah = g_vars.level_tiles_lut[g_vars.tilemap_data[offset]];
uint8_t al = g_vars.level_tiles_lut[g_vars.tilemap_data[offset + 1]];
if (ah != 1 && ah != 2 && al != 1 && al != 2) {
break;
}
}
obj->data[0].w = -obj->data[0].w;
}
if (obj->data[0].w < 0) {
offset += g_vars.tilemap_w;
if (g_vars.tilemap_data[offset + 1] != 0x95 && g_vars.tilemap_data[offset] != 0x95) {
continue;
}
WRITE_LE_UINT16(g_vars.tilemap_data + offset - 2, 0);
WRITE_LE_UINT16(g_vars.tilemap_data + offset, 0);
} else {
offset -= g_vars.tilemap_w;
uint16_t tiles = READ_LE_UINT16(g_vars.tilemap_data + offset - 2);
if (tiles != 0x9594 && tiles != 0) {
level_init_object102_2(obj, offset % g_vars.tilemap_w - 2, offset / g_vars.tilemap_w);
}
WRITE_LE_UINT16(g_vars.tilemap_data + offset - 2, 0x9594);
tiles = READ_LE_UINT16(g_vars.tilemap_data + offset);
if (tiles != 0x9695 && tiles != 0) {
level_init_object102_2(obj, offset % g_vars.tilemap_w, offset / g_vars.tilemap_w);
}
WRITE_LE_UINT16(g_vars.tilemap_data + offset, 0x9695);
}
}
for (int i = 0; i < 10; ++i) {
struct object_t *obj = &g_vars.objects_table[102 + i];
if (obj->spr_num != 0xFFFF) {
object102_y_delta(obj) += 8;
obj->x_pos += object102_x_delta(obj) >> 3;
obj->y_pos += object102_y_delta(obj) >> 3;
const int y = (obj->y_pos >> 4) - g_vars.tilemap_y;
if (y < 0 || y > TILEMAP_SCREEN_H / 16 + 3) {
obj->spr_num = 0xFFFF;
}
}
}
for (int i = 0; i < 10; ++i) { // vinyls
struct object_t *obj = &g_vars.objects_table[22 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
if (level_collide_vinyl_decor(obj->x_pos - 8, obj->y_pos - 8, obj)) {
continue;
}
int16_t ax, dx;
struct object_t *obj2 = level_collide_vinyl_objects82(obj);
if (obj2 && object_blinking_counter(obj2) == 0) {
level_init_object_spr_num_92(obj->x_pos, obj->y_pos);
object82_y_delta(obj2) = -48;
const int num = object82_type(obj2);
if (num == 6 || num == 4 || num == 1) {
ax = abs(object82_x_delta(obj2));
object82_x_delta(obj2) = (obj->data[0].w < 0) ? -ax : ax;
} else if (num == 2) {
ax = abs((int8_t)obj2->data[4].b[0]);
obj2->data[4].b[0] = (obj->data[0].w < 0) ? -ax : ax;
}
object_blinking_counter(obj2) = 3;
if (obj2->spr_num == 198) {
obj->spr_num = 0xFFFF;
continue;
}
dx = object82_energy(obj2);
ax = object82_energy(obj2) - object22_damage(obj);
if (ax <= 0 || (g_options.cheats & CHEATS_ONE_HIT_VINYL) != 0) {
play_sound(SOUND_1);
ax = -80;
object82_type(obj2) = 16;
// seek to ko animation
const uint8_t *p = object82_anim_data(obj2);
while (READ_LE_UINT16(p) != 0x7D00) {
p += 2;
}
object82_anim_data(obj2) = p + 2;
}
object82_energy(obj2) = ax;
object22_damage(obj) -= dx;
if (object22_damage(obj) <= 0) {
obj->spr_num = 0xFFFF;
object22_damage(obj) = 0;
continue;
}
}
obj2 = level_collide_vinyl_objects64(obj);
if (obj2) {
play_sound(SOUND_0);
level_init_object102_1(obj, obj->x_pos, obj->y_pos, 200);
level_init_object102_1(obj, obj->x_pos, obj->y_pos, 201);
level_init_object102_1(obj, obj->x_pos, obj->y_pos, 202);
level_init_object102_1(obj, obj->x_pos, obj->y_pos, 203);
obj2->spr_num = 0xFFFF;
g_vars.buffer[obj->data[0].w / 16] = 0xFFFF;
if (object22_damage(obj) > 0x200) {
level_init_object_spr_num_46(obj->x_pos, obj->y_pos);
} else {
obj->spr_num = 0xFFFF;
continue;
}
} else {
assert(g_vars.player != 2);
if (object22_damage(obj) > 0x200) {
level_init_object_spr_num_46(obj->x_pos, obj->y_pos);
}
}
if ((g_vars.level_loop_counter & 1) == 0) {
int num = (obj->spr_num & 0x1FFF) + 1;
if (num > 45) { // rolling vinyl
num = 42;
}
obj->spr_num = num;
object_blinking_counter(obj) = 0;
}
ax = object22_xvelocity(obj);
dx = 4;
if (ax < 0) {
ax = -ax;
dx = -dx;
}
ax += 4;
if (ax > 96) {
dx = 0;
}
object22_xvelocity(obj) += dx;
obj->x_pos += object22_xvelocity(obj) >> 3;
ax = abs((obj->x_pos >> 4) - g_vars.tilemap_x - (TILEMAP_SCREEN_W / 32));
if (ax >= (TILEMAP_SCREEN_W / 32 + 2)) {
obj->spr_num = 0xFFFF;
}
}
if (player_bounce_counter(&g_vars.players_table[0].obj) != 0) {
--player_bounce_counter(&g_vars.players_table[0].obj);
}
if (player_bounce_counter(&g_vars.players_table[0].obj) != 0) {
level_update_bouncing_tiles(&g_vars.players_table[0]);
}
assert(g_vars.player != 2);
// update monsters
for (int i = 0; i < 20; ++i) {
struct object_t *obj = &g_vars.objects_table[82 + i];
if (obj->spr_num != 0xFFFF) {
const uint8_t *p = object82_anim_data(obj);
int num;
while (1) {
num = (int16_t)READ_LE_UINT16(p);
if (num >= 0) {
break;
}
p += num << 1; // loop
}
obj->spr_num = num | ((object82_hflip(obj) & 0x80) << 8);
object82_anim_data(obj) = p + 2;
switch (object82_type(obj)) {
case 0:
level_update_object82_type0(obj);
break;
case 1:
level_update_object82_type1(obj);
break;
case 2:
level_update_object82_type2(obj);
break;
case 3:
level_update_object82_type3(obj);
break;
case 4:
case 6:
level_update_object82_type4_6(obj);
break;
case 5:
level_update_tiles(obj, obj->x_pos, obj->y_pos, _undefined);
level_update_object82_position(obj, 0, obj->data[5].b[0]);
break;
case 7:
level_update_object82_type7(obj);
break;
case 16:
level_update_object82_type16(obj);
break;
default:
print_warning("object %d unhandled type %d", 82 + i, object82_type(obj));
break;
}
}
}
// collision with monsters
for (int i = 0; i < 20; ++i) {
struct object_t *obj = &g_vars.objects_table[82 + i];
if (obj->spr_num == 0xFFFF) {
continue;
}
struct player_t *player = &g_vars.players_table[0];
if (object82_type(obj) == 5) { // jukebox for end of level
if ((player_flags2(&player->obj) & 8) == 0) {
if (level_collide_objects(obj, &g_vars.objects_table[0])) {
do_end_of_level();
g_vars.change_next_level_flag = 1;
return;
}
}
} else {
if ((player_flags2(&player->obj) & 9) == 0) {
if (object82_type(obj) == 16) { // monster ko
continue;
}
if (player_hit_counter(&player->obj) == 0 && level_collide_objects(obj, &g_vars.objects_table[0])) {
if (object82_type(obj) != 7) {
level_player_hit_object(player, &g_vars.objects_table[0]);
} else if (player_y_delta(&player->obj) > 0 && obj->y_pos > player->obj.y_pos) {
player_flags2(&player->obj) |= 0x40;
}
}
}
assert(g_vars.player != 2);
}
}
}
static void level_update_player(struct player_t *player) {
if (player_flags2(&player->obj) & 8) {
return;
}
struct object_t *player_obj = &g_vars.objects_table[player_obj_num(&player->obj)];
player_flags(&player->obj) &= ~1;
player_obj->y_pos += 8;
for (int i = 0; i < 8; ++i) { // platforms
struct object_t *obj = &g_vars.objects_table[32 + i * 4];
const uint16_t spr_num = player_obj->spr_num;
player_obj->spr_num = 0;
const bool ret = level_collide_objects(player_obj, obj);
player_obj->spr_num = spr_num;
if (!ret) {
continue;
}
if ((obj->spr_num & 0x1FFF) == 224) { // ball with spikes
player_x_delta(&player->obj) = (obj->x_pos <= player_obj->x_pos) ? 40 : -40;
player_flags2(&player->obj) |= 2;
level_player_hit_object(player, obj);
continue;
} else { // rotating platforms
if (player_y_delta(&player->obj) < 0) {
goto bonus_objects;
}
if (obj->y_pos < player_obj->y_pos) {
continue;
}
player->obj.x_pos -= obj->data[3].w;
player->obj.y_pos = obj->y_pos - 15;
player_obj->y_pos = player->obj.y_pos;
if (player_hit_counter(&player->obj) == 0) {
player_flags2(&player->obj) &= ~2;
}
player_y_delta(&player->obj) = 0;
player_flags(&player->obj) |= 1;
player_jump_counter(&player->obj) = 7;
goto bonus_objects;
}
}
if (player_y_delta(&player->obj) >= 0) {
for (int i = 0; i < 8; ++i) { // crates
struct object_t *obj = &g_vars.objects_table[64 + i];
if (obj->y_pos < player_obj->y_pos) {
continue;
}
const int num = obj->spr_num;
obj->spr_num = 0;
const int ret = level_collide_objects(player_obj, obj);
obj->spr_num = num;
if (!ret) {
continue;
}
const int dl = sprite_offsets[obj->spr_num & 0x1FFF] >> 8;
if (dl > 16 && player->obj.y_pos > obj->y_pos - 16) {
continue;
}
player->obj.y_pos = obj->y_pos - dl;
player_obj->y_pos = player->obj.y_pos;
if (player_hit_counter(&player->obj) == 0) {
player_flags2(&player->obj) &= ~2;
}
player_y_delta(&player->obj) = 0;
player_flags(&player->obj) |= 1;
player_jump_counter(&player->obj) = 7;
if (object64_counter(obj) != 0) {
--object64_counter(obj);
}
goto bonus_objects;
}
}
player_obj->y_pos -= 8;
bonus_objects:
for (int i = 0; i < 10; ++i) { // bonuses
struct object_t *obj = &g_vars.objects_table[72 + i];
if (obj->spr_num == 0xFFFF || !level_collide_objects(player_obj, obj)) {
continue;
}
const int spr_num = (obj->spr_num & 0x1FFF);
if (spr_num == 194) { // cake, power up
level_player_draw_powerup_animation(player, obj);
} else {
play_sound(SOUND_10);
if (spr_num == 197) { // checkpoint
g_vars.level_start_1p_x_pos = obj->x_pos;
g_vars.level_start_1p_y_pos = obj->y_pos;
g_vars.level_start_2p_player1_x_pos = obj->x_pos;
g_vars.level_start_2p_player1_y_pos = obj->y_pos;
g_vars.level_start_2p_player2_x_pos = obj->x_pos;
g_vars.level_start_2p_player2_y_pos = obj->y_pos;
} else if (spr_num == 195) { // shield
struct object_t *player_obj = &g_vars.objects_table[player_obj_num(&player->obj)];
object_blinking_counter(player_obj) = 240;
} else if (spr_num == 190) {
++player->lifes_count;
} else if (spr_num == 193) {
g_vars.level_time += 50;
} else if (spr_num == 210) {
player->vinyls_count += 20;
} else if (spr_num == 192) {
if (player->energy < 4) {
++player->energy;
}
} else if (spr_num == 191) {
player->energy = 4;
} else if (spr_num == 196) {
player->vinyls_count += 20;
}
obj->spr_num = 0xFFFF;
}
}
}
static void level_update_players() {
if ((player_flags2(&g_vars.players_table[0].obj) & 1) == 0) {
level_update_player(&g_vars.players_table[0]);
}
if (g_vars.player == 2 && (player_flags2(&g_vars.players_table[1].obj) & 1) == 0) {
level_update_player(&g_vars.players_table[1]);
}
}
static void init_panel(int x_offset) {
for (int y = 0; y < PANEL_H; ++y) {
memcpy(g_res.vga + (GAME_SCREEN_H - PANEL_H + y) * GAME_SCREEN_W + x_offset, g_res.board + y * 320, 320);
}
if (g_vars.player == 1) {
video_draw_dot_pattern(x_offset);
} else if (g_vars.player == 0) {
video_draw_dot_pattern(x_offset + 44 * 4);
}
}
static void draw_panel_vinyl(int offset, int spr, int x_offset) {
const uint8_t *src = g_res.board + 0x1E00 + spr * 16;
const int y_dst = (GAME_SCREEN_H - PANEL_H) + offset / 80;
const int x_dst = (offset % 80) * 4 + 2 + x_offset;
for (int y = 0; y < 16; ++y) {
memcpy(g_res.vga + (y_dst + y) * GAME_SCREEN_W + x_dst, src, 16);
src += 320;
}
}
static void draw_panel_energy(int offset, int count, int x_offset) {
const uint8_t *src = g_res.board + 0x2840 + count * 40;
const int y_dst = (GAME_SCREEN_H - PANEL_H) + offset / 80;
const int x_dst = (offset % 80) * 4 + x_offset;
for (int y = 0; y < 6; ++y) {
memcpy(g_res.vga + (y_dst + y) * GAME_SCREEN_W + x_dst, src, 40);
src += 320;
}
}
static void draw_panel_number(int bp, int di, int num, int x_offset) {
int y_dst = (GAME_SCREEN_H - PANEL_H) + di / 80;
int x_dst = (di % 80) * 4 + x_offset;
int digits[3];
int count = 0;
do {
digits[count++] = num % 10;
num /= 10;
} while (num != 0 && count < 3);
if (bp == 0x1E40) { // center time
x_dst += (3 - count) * 8 / 2;
}
for (; count > 0; --count) {
const uint8_t *src = g_res.board + bp + digits[count - 1] * 8;
for (int y = 0; y < 8; ++y) {
memcpy(g_res.vga + (y_dst + y) * GAME_SCREEN_W + x_dst, src, 8);
src += 320;
}
x_dst += 8;
}
}
static void draw_panel() {
const int x_offset = (GAME_SCREEN_W - 320) / 2;
init_panel(x_offset);
draw_panel_number(0x1E40, 0x435, MIN(g_vars.level_time, 999), x_offset);
const int bp = 0x1E90;
if (g_vars.player != 1) {
draw_panel_number(bp, 0x29F, MIN(g_vars.players_table[0].lifes_count, 99), x_offset);
draw_panel_number(bp, 0x288, g_vars.players_table[0].vinyls_count, x_offset);
draw_panel_energy(0x41E, g_vars.players_table[0].energy, x_offset);
const uint8_t al = player_power(&g_vars.players_table[0].obj);
if (al > 8) {
uint8_t cl = 0;
if (al < 33) {
++cl;
if (al < 20) {
++cl;
}
}
draw_panel_vinyl(0x141, (g_vars.level_loop_counter >> cl) & 3, x_offset);
}
}
if (g_vars.player != 0) {
struct player_t *player = &g_vars.players_table[0];
if (g_vars.player != 1) {
player = &g_vars.players_table[1];
}
draw_panel_number(bp, 0x2CB, MIN(player->lifes_count, 99), x_offset);
draw_panel_number(bp, 0x2B4, player->vinyls_count, x_offset);
draw_panel_energy(0x44A, player->energy, x_offset);
const uint8_t al = player_power(&player->obj);
if (al > 8) {
uint8_t cl = 0;
if (al < 33) {
++cl;
if (al < 20) {
++cl;
}
}
draw_panel_vinyl(0x16D, (g_vars.level_loop_counter >> cl) & 3, x_offset);
}
}
}
static void level_draw_objects() {
for (int i = OBJECTS_COUNT - 1; i >= 0; --i) {
struct object_t *obj = &g_vars.objects_table[i];
if (obj->spr_num == 0xFFFF) {
continue;
}
const uint16_t num = obj->spr_num & 0x1FFF;
if (object_blinking_counter(obj) != 0) {
--object_blinking_counter(obj);
}
if (object_blinking_counter(obj) != 0 && (g_vars.level_loop_counter & 1) != 0) {
// video_set_palette_index(1);
continue;
} else {
// video_set_palette_index(sprite_palettes[num]);
}
const int spr_hflip = ((obj->spr_num & 0x8000) != 0) ? 1 : 0;
const int spr_y_pos = obj->y_pos - ((g_vars.tilemap_y << 4) + g_vars.tilemap_scroll_dy) - (sprite_offsets[num] >> 8);
int spr_x_pos = obj->x_pos - ((g_vars.tilemap_x << 4) + (g_vars.tilemap_scroll_dx << 2));
int spr_w = (sprite_sizes[num] & 255);
if (spr_hflip) {
spr_w = (sprite_offsets[num] & 255) - spr_w;
}
spr_x_pos -= spr_w;
video_draw_sprite(num, spr_x_pos, spr_y_pos, spr_hflip);
}
}
static int16_t level_player_update_anim(struct player_t *player) {
const uint8_t *p;
if (player_prev_spr_num(&player->obj) != player->obj.spr_num) {
player_idle_counter(&player->obj) = 0;
player_prev_spr_num(&player->obj) = player->obj.spr_num;
p = player_anim_table[player_prev_spr_num(&player->obj)];
} else {
++player_idle_counter(&player->obj);
p = player_anim_data(&player->obj);
}
int num;
while ((num = (int16_t)READ_LE_UINT16(p)) < 0) {
p += num * 2;
}
if (num & 0x2000) {
++player_throw_counter(&player->obj);
}
player_anim_data(&player->obj) = p + 2;
return num;
}
static void level_player_update_spr(struct player_t *player, struct object_t *obj) {
int num;
player_throw_counter(&player->obj) = 0;
if ((player_flags2(&player->obj) & 1) == 0) {
if (player->obj.spr_num != 6 && player->obj.spr_num != 19 && player_y_delta(&player->obj) > 64) {
num = abs(player_x_delta(&player->obj));
num = (num == 32) ? 0 : 4;
if (player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) {
num |= 2;
}
if (player_flags(&player->obj) & 0x40) {
num |= 1;
}
static const uint8_t data[] = { 0x0B, 0x93, 0x24, 0x93, 0x0C, 0x94, 0x24, 0x94 };
num = data[num];
} else {
if (player_hit_counter(&player->obj) != 0) {
--player_hit_counter(&player->obj);
num = 19;
} else {
num = level_player_update_anim(player);
}
}
} else {
num = level_player_update_anim(player);
}
const int _dx = num & 0x4000;
num &= 0x1FFF;
num |= (player_flags(&player->obj) & PLAYER_FLAGS_FACING_LEFT) << 13;
if (_dx) {
num ^= 0x8000;
}
obj->spr_num = num;
}
// crates
static void level_tile_func_5dc9(struct object_t *obj, int bp) {
if (bp != _undefined && (player_flags2(obj) & 0x40) != 0) {
level_init_object_spr_num_92(obj->x_pos, obj->y_pos);
play_sound(SOUND_4);
player_flags2(obj) &= ~0x40;
}
g_vars.player_xscroll = 0;
obj->x_pos -= player_x_delta(obj) >> 3;
player_x_delta(obj) = 0;
if (bp == _undefined) {
return;
}
const int offset = g_vars.player_map_offset;
uint8_t al;
al = g_vars.tilemap_data[offset - g_vars.tilemap_w];
al = g_vars.level_tiles_lut[al];
al = tiles_5dc9_lut[al];
if (al == 0) {
return;
}
al = g_vars.tilemap_data[offset - g_vars.tilemap_w - 1];
al = g_vars.level_tiles_lut[al];
al = tiles_5dc9_lut[al];
if (al == 0) {
obj->x_pos -= 2;
return;
}
al = g_vars.tilemap_data[offset - g_vars.tilemap_w + 1];
al = g_vars.level_tiles_lut[al];
al = tiles_5dc9_lut[al];
if (al != 0) {
if ((player_flags(obj) & 0x10) == 0) {
return;
}
level_player_die((struct player_t *)obj);
}
obj->x_pos += 2;
}
static void level_tile_func_5daa(struct object_t *obj, int bp) {
if (bp != _undefined && (player_flags2(obj) & 0x40) != 0) {
return;
}
player_y_delta(obj) = MIN(player_y_delta(obj) + 7, 120);
}
static void level_tile_func_5e58(struct object_t *obj, int bp) {
if (bp != _undefined) {
player_flags2(obj) &= ~0x40;
level_init_object_spr_num_92(obj->x_pos, obj->y_pos - 24);
play_sound(SOUND_4);
}
player_y_delta(obj) = 0;
if (player_flags(obj) & PLAYER_FLAGS_STAIRS) {
obj->y_pos += 2;
}
}
static void level_tile_func_5e83(struct object_t *obj, int bp) {
if (bp != _undefined) {
player_flags2(obj) &= ~0x40;
}
if (player_y_delta(obj) < 0) {
level_tile_func_5daa(obj, bp);
} else {
player_flags2(obj) &= ~2;
player_y_delta(obj) = 0;
obj->y_pos &= ~15;
if (bp == _undefined) { // (obj < &g_vars.players_table[0])
return;
}
if (player_jump_counter(obj) != 7) {
player_jump_counter(obj) = 7;
player_x_delta(obj) >>= 1;
}
}
}
// stairs
static void level_tile_func_5d3e(struct object_t *obj, int bp) {
if (bp == _undefined) {
level_tile_func_5daa(obj, bp);
return;
}
player_flags2(obj) &= ~2;
if (player_flags2(obj) & 0x40) {
return;
}
if (player_hit_counter(obj) != 0) {
level_tile_func_5daa(obj, bp);
return;
}
if (player_x_delta(obj) != 0 && player_y_delta(obj) < 0) {
level_tile_func_5daa(obj, bp);
return;
}
const int offset = g_vars.player_map_offset;
uint8_t ah = (player_flags(obj) & 0x40) | (player_flags2(obj) & 1);
uint8_t al = g_vars.tilemap_data[offset - g_vars.tilemap_w];
al = g_vars.level_tiles_lut[al];
if (al == 8) {
if (ah != 0) {
level_tile_func_5daa(obj, bp);
return;
}
} else {
player_y_delta(obj) = 0;
if (ah != 0 || (player_flags2(obj) & 4) == 0) {
level_tile_func_5e83(obj, bp);
return;
}
}
player_y_delta(obj) = 0;
player_flags(obj) |= PLAYER_FLAGS_STAIRS;
}
// bouncing mushrooms
static void level_tile_func_5f16(struct object_t *obj, int bp) {
if (player_y_delta(obj) < 0) {
return;
} else if (player_y_delta(obj) == 0) {
level_tile_func_5e83(obj, bp);
} else {
if (obj != &g_vars.players_table[0].obj) {
print_warning("Unexpected object #%d spr %d on tile 5f16", obj - g_vars.objects_table, obj->spr_num);
return;
}
player_flags2(obj) &= ~2;
obj->y_pos &= ~15;
player_y_delta(obj) = MAX(-player_y_delta(obj) - 16, -112);
player_jump_counter(obj) = 0;
player_bounce_counter(obj) = 7;
player_tilemap_offset(obj) = g_vars.player_map_offset;
const int x = (obj->x_pos >> 4) - g_vars.tilemap_x;
if (x < 0 || x > TILEMAP_SCREEN_W / 16) {
return;
}
const int y = (obj->y_pos >> 4) - g_vars.tilemap_y;
if (y < 0 || y > (TILEMAP_SCREEN_H / 16) + 1) {
return;
}
play_sound(SOUND_2);
}
}
static void level_tile_func_5eec(struct object_t *obj, int bp) {
assert(bp != _undefined);
struct player_t *player = (struct player_t *)obj;
const int offset = g_vars.player_map_offset;
g_vars.tilemap_data[offset] = 0;
++player->vinyls_count;
level_init_object_spr_num_104(offset % g_vars.tilemap_w, offset / g_vars.tilemap_w);
play_sound(SOUND_5);
level_tile_func_5daa(obj, bp);
}
static void level_tile_func_5f0e(struct object_t *obj, int bp) {
level_tile_func_5f16(obj, bp);
obj->data[7].b[0] = 0;
}
// slopes
static void level_tile_func_5f7b(struct object_t *obj, int bp, int bx) {
if (bp != _undefined) {
player_flags2(obj) &= ~0x40;
}
if (player_y_delta(obj) < 0) {
return;
}
player_flags2(obj) &= ~2;
player_y_delta(obj) = 0;
obj->y_pos &= ~15;
if (bp == _undefined) { // (obj < &g_vars.players_table[0])
return;
}
struct player_t *player = (struct player_t *)obj;
if (player_jump_counter(&player->obj) != 7) {
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) >>= 1;
}
assert(bx >= 18);
const uint8_t *p = tiles_yoffset_table + ((bx - 18) << 3);
player->obj.y_pos += p[player->obj.x_pos & 15];
}
// electricity
static void level_tile_func_5f94(struct object_t *obj) {
if (g_options.cheats & CHEATS_DECOR_NO_HIT) {
return;
}
level_player_die((struct player_t *)obj);
}
// spikes
static void level_tile_func_5f9e(struct object_t *obj, int bp) {
level_player_hit_object(&g_vars.players_table[0], &g_vars.objects_table[0]);
}
static void level_tile_func_5fa7(struct object_t *obj, int bp) {
level_player_hit_object(&g_vars.players_table[0], &g_vars.objects_table[0]);
level_tile_func_5e83(obj, bp);
}
static void level_tile_func(int ax, struct object_t *obj, int bp, int bx) {
switch (ax) {
case 0x5D3E:
level_tile_func_5d3e(obj, bp);
break;
case 0x5DC8:
break;
case 0x5DC9:
level_tile_func_5dc9(obj, bp);
break;
case 0x5DAA:
level_tile_func_5daa(obj, bp);
break;
case 0x5E58:
level_tile_func_5e58(obj, bp);
break;
case 0x5E83:
level_tile_func_5e83(obj, bp);
break;
case 0x5EEC:
level_tile_func_5eec(obj, bp);
break;
case 0x5F0E:
level_tile_func_5f0e(obj, bp);
break;
case 0x5F16:
level_tile_func_5f16(obj, bp);
break;
case 0x5F73:
level_tile_func_5f7b(obj, bp, 18);
break;
case 0x5F78:
level_tile_func_5f7b(obj, bp, 20);
break;
case 0x5F7B:
level_tile_func_5f7b(obj, bp, bx);
break;
case 0x5F94:
level_tile_func_5f94(obj);
break;
case 0x5F9E:
level_tile_func_5f9e(obj, bp);
break;
case 0x5FA7:
level_tile_func_5fa7(obj, bp);
break;
default:
print_warning("Unimplemented tile func %x", ax);
break;
}
}
static void level_update_tiles(struct object_t *obj, int ax, int dx, int bp) {
const int level_update_tiles_x = ax;
const int level_update_tiles_y = dx;
g_vars.player_map_offset = (dx >> 4) * g_vars.tilemap_w + (ax >> 4);
uint8_t _al = 0;
while (1) {
const int offset = (dx >> 4) * g_vars.tilemap_w + (ax >> 4);
_al = level_get_tile(offset);
if (_al != 5) {
break;
}
obj->y_pos -= 16;
dx = obj->y_pos;
}
const int num = _al << 1;
ax = 0x5DAA;
if (obj->y_pos > 0) {
if (bp == _undefined) {
ax = tile_funcs0[_al];
} else {
ax = tile_funcs1[_al]; // player
}
if (ax == 0 && (obj->data[6].b[0] & 1) != 0) {
return;
}
}
level_tile_func(ax, obj, bp, num);
if (bp == _undefined) {
return; // not player
}
ax = level_update_tiles_x + ((player_x_delta(obj) < 0) ? -6 : 6);
dx = level_update_tiles_y;
const int offset = (dx >> 4) * g_vars.tilemap_w + (ax >> 4);
_al = level_get_tile(offset - g_vars.tilemap_w);
level_tile_func(tile_funcs2[_al], obj, bp, _al * 2);
if (obj->spr_num != 3) {
_al = level_get_tile(offset - g_vars.tilemap_w * 2);
level_tile_func(tile_funcs2[_al], obj, bp, _al * 2);
}
player_flags(obj) &= ~0x10;
player_flags2(obj) &= ~0x20;
if (player_jump_counter(obj) == 7) {
_al = level_get_tile(offset - g_vars.tilemap_w * 2);
if (_al == 10) {
player_flags2(obj) |= 0x20;
}
if (tile_funcs3[_al] == 0x5E58) {
player_flags(obj) |= 0x10;
player_flags2(obj) &= ~1;
}
}
if ((player_flags(obj) & PLAYER_FLAGS_STAIRS) == 0 && player_y_delta(obj) >= 0) {
return;
}
_al = level_get_tile(offset - g_vars.tilemap_w * 2);
level_tile_func(tile_funcs3[_al], obj, bp, _al * 2);
}
static bool level_adjust_player_spr_num_helper(struct player_t *player, int ax, int dx) {
if ((player_flags2(&player->obj) & 1) == 0) {
return false;
}
if (player->change_hdir_counter < 30) {
const int offset = (dx >> 4) * g_vars.tilemap_w + (ax >> 4);
const uint8_t num = level_get_tile(offset - g_vars.tilemap_w);
if (tile_funcs3[num] != 0x5E58) {
return true;
}
}
level_hit_player(player);
return true;
}
static void level_adjust_player_spr_num(struct player_t *player) {
struct object_t *obj = &g_vars.objects_table[player_obj_num(&player->obj)];
obj->x_pos = player->obj.x_pos;
obj->y_pos = player->obj.y_pos;
if (player_flags2(&player->obj) & 8) {
obj->spr_num = 19;
} else {
level_player_update_spr(player, obj);
const int tile_x = (player->obj.x_pos >> 4);
const int tile_y = (player->obj.y_pos >> 4);
int offset = tile_y * g_vars.tilemap_w + tile_x;
const int spr_h = sprite_offsets[player->obj.spr_num & 0x1FFF] >> 8;
for (int i = 0; i < (spr_h >> 4); ++i) {
offset -= g_vars.tilemap_w;
if (offset < 0) {
break;
}
uint8_t _al = g_vars.tilemap_data[offset];
_al = g_vars.level_tiles_lut[_al];
if (_al == 6) {
g_vars.tilemap_data[offset] = 0;
++player->vinyls_count;
level_init_object_spr_num_104(tile_x, tile_y - i - 1);
play_sound(SOUND_5);
}
}
player_flags(&player->obj) &= ~PLAYER_FLAGS_STAIRS;
if (!level_adjust_player_spr_num_helper(player, player->obj.x_pos, player->obj.y_pos)) {
level_update_tiles(&player->obj, player->obj.x_pos, player->obj.y_pos, 0);
}
}
obj->y_pos = player->obj.y_pos;
const int spr_num = obj->spr_num & 0x1FFF;
if ((player_flags(&player->obj) & 0x60) == 0x60) {
obj->spr_num += (spr_num <= 148) ? 13 : 12;
}
if ((player_flags(&player->obj) & PLAYER_FLAGS_JAKE) != 0) {
int d = (spr_num < 50) ? 50 : 25;
if (spr_num == 138) {
d = 1;
}
obj->spr_num += d;
}
}
static void level_adjust_player_position() {
level_adjust_player_spr_num(&g_vars.players_table[0]);
assert(g_vars.player < 2);
}
static void level_sync() {
++g_vars.level_time_counter;
if (g_vars.level_time_counter >= 28) {
g_vars.level_time_counter = 0;
if ((g_options.cheats & CHEATS_UNLIMITED_TIME) == 0) {
--g_vars.level_time;
}
}
g_sys.copy_bitmap(g_res.vga, GAME_SCREEN_W, GAME_SCREEN_H);
g_sys.update_screen();
g_sys.render_clear_sprites();
const int diff = (g_vars.timestamp + (1000 / 30)) - g_sys.get_timestamp();
g_sys.sleep(MAX(diff, 10));
g_vars.timestamp = g_sys.get_timestamp();
}
static void load_level_data(uint16_t level) {
g_vars.level_num = (level >> 8);
const uint8_t triggers_count = (level & 255);
g_vars.level_tiles_lut = (level & 0xFF00) + level_data_tiles_lut;
load_file(_background[g_vars.level_num]);
video_copy_vga(0xB500);
load_file(_decor[g_vars.level_num]);
const uint8_t *buffer = g_res.tmp;
memcpy(g_vars.tile_palette_table, buffer + 0x8000, 0x100);
int bp = 0;
const uint8_t *data = buffer + 0x8100;
for (int i = 0; i < triggers_count; ++i) {
const int num = data[18] * 16;
data += num + 24;
const int w = (int16_t)READ_LE_UINT16(data - 3);
const int h = (int16_t)READ_LE_UINT16(data - 5);
print_debug(DBG_GAME, "level len:%d w:%d h:%d", num, w, h);
bp += w * h;
}
memcpy(g_vars.triggers_table, data, 19); data += 19;
uint8_t *dst = g_vars.triggers_table + 19;
int count = data[-1]; // data[18]
print_debug(DBG_GAME, "level 0x%x count %d", level, count);
assert(count <= TRIGGERS_COUNT);
const int stride = count * sizeof(int16_t);
for (int i = 0; i < count; ++i, data += 2) {
for (int i = 0; i < 8; ++i) {
memcpy(dst, data + stride * i, sizeof(int16_t));
dst += 2;
}
}
data += 7 * stride;
if (g_debug_mask & DBG_GAME) {
for (int i = 0; i < g_vars.triggers_table[18]; ++i) {
const uint8_t *obj_data = g_vars.triggers_table + 19 + i * 16;
const int x_pos = READ_LE_UINT16(obj_data);
const int y_pos = READ_LE_UINT16(obj_data + 2);
const int type1 = READ_LE_UINT16(obj_data + 4);
const int type2 = READ_LE_UINT16(obj_data + 6);
const int num = READ_LE_UINT16(obj_data + 8);
print_debug(DBG_GAME, "trigger #%d pos %d,%d type %d,%d num %d", i, x_pos, y_pos, type1, type2, num);
}
}
g_vars.tilemap_h = READ_LE_UINT16(data); data += 2;
g_vars.tilemap_w = READ_LE_UINT16(data); data += 2;
g_vars.tilemap_type = *data++;
print_debug(DBG_GAME, "tilemap w:%d h:%d type:%d", g_vars.tilemap_w, g_vars.tilemap_h, g_vars.tilemap_type);
data = buffer + 0x8100;
int offset = 0;
count = level_data3[g_vars.level_num];
print_debug(DBG_GAME, "level_data[3] %d bp %d", count, bp);
for (int i = 0; i < count; ++i) {
const int num = data[offset + 18] * 16;
offset += num + 24;
}
offset += bp;
assert(offset >= g_vars.tilemap_h);
g_vars.tilemap_data = g_res.tmp + 0x8100 + offset;
for (int i = 0; i < 0x600; ++i) {
g_vars.tilemap_lut_init[i] = (i & 0xFF);
}
g_vars.tilemap_current_lut = g_vars.tilemap_lut_init;
const uint8_t *tiles_lut = 0;
switch (g_vars.tilemap_type & 0x3F) {
case 0: tiles_lut = tile_lut_data0; break;
case 1: tiles_lut = tile_lut_data1; break;
case 2: tiles_lut = tile_lut_data2; break;
case 3: tiles_lut = tile_lut_data3; break;
default:
print_error("load_level_data(): Invalid tilemap type %d", g_vars.tilemap_type);
break;
}
for (int i = 0; i < tiles_lut[0]; ++i) {
const uint8_t start = tiles_lut[i * 3 + 1];
const uint8_t count = tiles_lut[i * 3 + 2];
const uint8_t value = tiles_lut[i * 3 + 3];
print_debug(DBG_GAME, "tiles start:%d count:%d value:0x%x", start, count, value);
memset(g_vars.tilemap_lut_init2 + start, value, count);
}
const uint8_t *src = tiles_lut;
int total_count = src[0]; // total_count
while (1) {
const uint8_t start = src[1];
const uint8_t count = src[2];
src += 3;
--total_count;
if (total_count < 0) {
break;
}
uint8_t bh = 0;
do {
uint8_t ch = 0;
do {
assert(bh < 6);
uint8_t dh = ch;
for (int i = 0; i < count; ++i) {
g_vars.tilemap_lut_init[(bh << 8) | (start + i)] = start + dh;
++dh;
if (dh == count) {
dh = 0;
}
}
++bh;
++ch;
if (bh == 6) {
break;
}
} while (bh != count);
} while (bh == 3);
}
const uint8_t *p = g_vars.tilemap_lut_init;
for (int i = 0; i < 256; ++i) {
const uint8_t num = p[i];
if (p[0x100 + i] != num) {
if (p[0x300 + i] == num) {
g_vars.tilemap_lut_type[i] = 2;
} else {
g_vars.tilemap_lut_type[i] = 1;
}
} else {
g_vars.tilemap_lut_type[i] = 0;
}
}
g_vars.tilemap_lut_type[0xF8] = 1;
g_vars.tilemap_end_x = g_vars.tilemap_w - (TILEMAP_SCREEN_W / 16 + 1);
g_vars.tilemap_end_y = g_vars.tilemap_h - (TILEMAP_SCREEN_H / 16 + 1);
g_vars.players_table[0].vinyls_count = READ_LE_UINT16(g_vars.triggers_table + 0xC);
g_vars.players_table[1].vinyls_count = READ_LE_UINT16(g_vars.triggers_table + 0xE);
g_vars.level_time = g_vars.level_time2 = READ_LE_UINT16(g_vars.triggers_table + 0x10);
g_vars.level_time_counter = 0;
const uint16_t num = (g_vars.level_num << 8) | triggers_count;
if (num != g_vars.level_pos_num) {
g_vars.level_start_2p_player1_x_pos = READ_LE_UINT16(g_vars.triggers_table);
g_vars.level_start_2p_player1_y_pos = READ_LE_UINT16(g_vars.triggers_table + 0x4);
g_vars.level_start_2p_player2_x_pos = READ_LE_UINT16(g_vars.triggers_table + 0x2);
g_vars.level_start_2p_player2_y_pos = READ_LE_UINT16(g_vars.triggers_table + 0x6);
g_vars.level_start_1p_x_pos = READ_LE_UINT16(g_vars.triggers_table + 0x8);
g_vars.level_start_1p_y_pos = READ_LE_UINT16(g_vars.triggers_table + 0xA);
}
g_vars.level_pos_num = num;
g_vars.level_loop_counter = 0;
}
static void reset_level_data() {
memset(g_vars.objects_table, 0xFF, sizeof(g_vars.objects_table));
g_vars.change_next_level_flag = 0;
g_vars.player_xscroll = 0;
g_vars.player_map_offset = 0;
g_vars.throw_vinyl_counter = 0;
g_vars.tilemap_flags = 0;
memset(&g_vars.objects_table[32], 0xFF, 40 * sizeof(struct object_t));
g_vars.tilemap_flags |= g_vars.tilemap_type >> 5;
player_flags(&g_vars.players_table[0].obj) = 0;
player_prev_spr_num(&g_vars.players_table[0].obj) = 0;
player_flags(&g_vars.players_table[1].obj) = 0;
player_prev_spr_num(&g_vars.players_table[1].obj) = 0;
}
static void level_init_players() {
for (int i = 0; i < 128; ++i) {
g_vars.buffer[i] = 0xFFFF;
g_vars.buffer[128 + i] = 0xFF20;
}
object_blinking_counter(&g_vars.objects_table[0]) = 0;
object_blinking_counter(&g_vars.objects_table[1]) = 0;
assert(g_vars.player != 2);
struct player_t *player = &g_vars.players_table[0];
player->obj.x_pos = g_vars.level_start_1p_x_pos;
player->obj.y_pos = g_vars.level_start_1p_y_pos;
player_prev_spr_num(&player->obj) = _undefined;
player_obj_num(&player->obj) = 0;
player_jump_counter(&player->obj) = 7;
player_x_delta(&player->obj) = 0;
player_y_delta(&player->obj) = 0;
player->unk_counter = 0;
player->change_hdir_counter = 0;
player_bounce_counter(&player->obj) = 0;
player_hit_counter(&player->obj) = 0;
player_flags2(&player->obj) = 0;
const uint8_t flag = ((g_vars.player & 1) != 0) ? PLAYER_FLAGS_JAKE : 0;
player_flags(&player->obj) |= flag;
player_flags2(&player->obj) = flag;
player->energy = 4;
g_vars.objects_table[1].spr_num = 0xFFFF;
}
static void level_init_tilemap() {
g_vars.tilemap_x = 0;
g_vars.tilemap_y = 0;
g_vars.tilemap_scroll_dx = 0;
g_vars.tilemap_scroll_dy = 0;
while (1) {
const int bottom_y = g_vars.tilemap_y + 11;
const int player_y = g_vars.players_table[0].obj.y_pos >> 4;
if (bottom_y > player_y) {
break;
}
const int current_y = g_vars.tilemap_y;
level_adjust_vscroll_down(16);
if (g_vars.tilemap_y == current_y) {
break;
}
}
level_adjust_vscroll_down(16);
level_adjust_vscroll_down(16);
int prev_x = _undefined;
while (1) {
const int right_x = g_vars.tilemap_x + 10;
const int player_x = g_vars.players_table[0].obj.x_pos >> 4;
if (right_x >= player_x || prev_x == right_x) {
break;
}
level_adjust_hscroll_right(4);
prev_x = right_x;
}
}
static void level_player_draw_powerdown_animation(struct player_t *player) {
if (player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) {
player_flags(&player->obj) &= ~PLAYER_FLAGS_POWERUP;
struct object_t *player_obj = &g_vars.objects_table[player_obj_num(&player->obj)];
player_jump_counter(player_obj) = 0;
static const uint8_t data[] = { // death animation sprites
0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x79, 0x7B, 0x7B, 0x7B,
0x7B, 0x7B, 0x7B, 0x7A, 0x7A, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7B,
0x7B, 0x7B, 0x7D, 0x7D, 0x7D, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7D,
0x7D, 0x7D, 0x7C, 0x8A, 0xFF
};
for (int i = 0; data[i] != 0xFF; ++i) {
level_draw_tilemap();
draw_panel();
level_draw_objects();
level_sync();
player_obj->spr_num = data[i];
if (player_flags(&player->obj) & PLAYER_FLAGS_JAKE) {
player_obj->spr_num += 11;
}
}
}
}
static void level_player_draw_powerup_animation(struct player_t *player, struct object_t *obj) {
if ((player_flags(&player->obj) & PLAYER_FLAGS_POWERUP) == 0) {
play_sound(SOUND_10);
if (player_flags(&player->obj) & 0x40) {
player_flags(&player->obj) &= ~0x40;
}
player_flags(&player->obj) |= PLAYER_FLAGS_POWERUP;
obj->spr_num = 0xFFFF;
static const uint8_t data[] = { // power animation sprites
0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x74, 0x75, 0x74, 0x75, 0x7C, 0x7C,
0x7C, 0x7C, 0x75, 0x75, 0x76, 0x75, 0x76, 0x7B, 0x7B, 0x7B, 0x7B,
0x76, 0x76, 0x77, 0x76, 0x77, 0x7A, 0x7A, 0x7A, 0x77, 0x77, 0x77,
0x78, 0x77, 0x78, 0x7E, 0x78, 0x78, 0x78, 0x78, 0x79, 0x77, 0x79,
0x79, 0xFF
};
struct object_t *player_obj = &g_vars.objects_table[player_obj_num(&player->obj)];
for (int i = 0; data[i] != 0xFF; ++i) {
level_draw_tilemap();
draw_panel();
level_draw_objects();
level_sync();
player_obj->spr_num = data[i];
if (player_flags(&player->obj) & PLAYER_FLAGS_JAKE) {
player_obj->spr_num += 11;
}
}
}
}
static void init_level(uint16_t level) {
load_level_data(level);
reset_level_data();
level_init_players();
level_init_tilemap();
level_draw_tilemap();
g_vars.reset_palette_flag = 0;
}
static uint16_t get_level() {
const uint8_t *data = (g_vars.player == 2) ? level_data2p : level_data1p;
const uint16_t level = READ_BE_UINT16(data + (g_vars.level - 1) * 2);
return level;
}
static bool change_level(bool first_level) {
if (first_level || ((g_vars.level & 7) == 0 && (g_vars.level >> 3) < 4)) {
do_difficulty_screen();
}
if ((g_vars.level & 3) == 0 && g_vars.level < LEVELS_COUNT) {
// do_level_password_screen();
}
const uint16_t level = get_level();
if (level == 0xFFFF) {
do_game_win_screen();
return false;
} else {
do_level_number_screen();
const int num = ((g_vars.level >> 3) & 3) + 1;
play_music(num);
init_level(level);
return true;
}
}
void do_level() {
change_level(true);
while (!g_sys.input.quit) {
g_vars.timestamp = g_sys.get_timestamp();
update_input();
level_update_palette();
level_update_input();
level_update_triggers();
if (g_vars.change_next_level_flag) {
++g_vars.level;
if (change_level(false)) {
continue;
}
break;
}
level_update_players();
level_draw_tilemap();
draw_panel();
level_reset_palette();
level_draw_objects();
level_adjust_player_position();
level_sync();
++g_vars.level_loop_counter;
assert(g_vars.player != 2);
if ((player_flags2(&g_vars.players_table[0].obj) & 0x10) == 0) {
continue;
}
// player fell or no energy left
g_sys.fade_out_palette();
assert(g_vars.player != 2);
if (g_vars.players_table[0].lifes_count != 0) {
init_level(get_level()); // restart
continue;
}
do_game_over_screen();
break;
}
}