Mirai's Miscellaneous Misadventures
M17 / core / game.c
#include <mimimi/engines.h>
#include <mimimi/chapters.h>
#include <mimimi/models.h>
#include <mimimi/appearances.h>
#include <mimimi/fonts.h>
#include "appearances.h"
struct mimimi_sprite
{
struct mimimi_sprite *next;
struct mimimi_game *game;
struct mimimi_sprite_appearance *appearance;
int x, y;
int width, height;
int dx, dy;
char airborne;
char moving_direction;
char facing_direction;
int knocked_time;
int immunity_time;
int pristinity;
void (*behave)(struct mimimi_sprite *sprite, void *data);
unsigned char animation_time;
void *data;
};
struct mimimi_game
{
struct mimimi_engine *engine;
struct mimimi_chapter *chapter;
struct mimimi_sprite *sprite;
unsigned int left_history:16;
unsigned int right_history:16;
struct mimimi_sprite *camera;
unsigned int ground_width;
unsigned int ground_height;
unsigned char *ground;
struct mimimi_image *background;
struct mimimi_image *overlay;
};
static struct mimimi_sprite mimimi_last_sprite;
static struct mimimi_image mimimi_empty_image_value;
int mimimi_game_size = sizeof (struct mimimi_game);
int mimimi_sprite_size = sizeof (struct mimimi_sprite);
int mimimi_width = 512;
int mimimi_height = 256;
struct mimimi_image *mimimi_empty_image = &mimimi_empty_image_value;
static void mimimi_stamp(struct mimimi_game *game, int x, int y, struct mimimi_image *image)
{
x -= game->camera->x;
x += mimimi_width * 4;
x -= image->width * 4;
y -= game->camera->y;
y += mimimi_height * 4;
y -= image->height * 8;
(*game->engine->stamp)(game->engine->data, x / 8, y / 8, image);
}
static void mimimi_stamp_image(struct mimimi_image *target, int x0, int y0, struct mimimi_image *source)
{
for (int x1 = 0 ; x1 < source->width ; x1++)
for (int y1 = 0 ; y1 < source->height ; y1++)
{
int x = x0 + x1;
int y = y0 + y1;
if (x < 0) continue;
if (y < 0) continue;
if (x >= target->width) continue;
if (y >= target->height) continue;
unsigned char color = source->colors[x1 + y1 * source->width];
if (color == 0) continue;
target->colors[x + y * target->width] = color;
}
}
static void mimimi_wall_physics(struct mimimi_sprite *sprite)
{
struct mimimi_game *game = sprite->game;
int x0 = sprite->x;
int x1 = sprite->width / 2;
int y0 = sprite->y;
int y1 = sprite->height;
int top = (y0 - y1) / 128;
int top2 = top - 1;
int bottom = (y0 - 127) / 128;
int bottom2 = bottom - 1;
int left = (x0 - x1) / 128;
int right = (x0 + x1) / 128;
if (
game->ground[left + bottom * game->ground_width] != 0 &&
game->ground[left + bottom2 * game->ground_width] != 0 ||
game->ground[left + top * game->ground_width] != 0 &&
game->ground[left + top2 * game->ground_width]
)
{
if (sprite->dx < 0) sprite->dx = 0;
sprite->x = (left + 1) * 128 + x1;
}
if (
game->ground[right + bottom * game->ground_width] != 0 &&
game->ground[right + bottom2 * game->ground_width] != 0 ||
game->ground[right + top * game->ground_width] != 0 &&
game->ground[right + top2 * game->ground_width]
)
{
if (sprite->dx > 0) sprite->dx = 0;
sprite->x = right * 128 - x1 - 1;
}
}
static void mimimi_step_physics(struct mimimi_sprite *sprite)
{
struct mimimi_game *game = sprite->game;
int x0 = sprite->x;
int x1 = sprite->width / 2;
int y0 = sprite->y;
int y1 = sprite->height;
int top = (y0 - y1) / 128;
int bottom = (y0 - 127) / 128;
int left = (x0 - x1) / 128;
int right = (x0 + x1) / 128;
if (game->ground[left + bottom * game->ground_width] != 0 && game->ground[left + top * game->ground_width] == 0)
sprite->y = bottom * 128;
if (game->ground[right + bottom * game->ground_width] != 0 && game->ground[right + top * game->ground_width] == 0)
sprite->y = bottom * 128;
}
static void mimimi_ceiling_physics(struct mimimi_sprite *sprite)
{
struct mimimi_game *game = sprite->game;
int x0 = sprite->x;
int x1 = sprite->width / 2;
int y0 = sprite->y;
int y1 = sprite->height;
int top = (y0 - y1) / 128;
int top2 = top - 1;
int left = (x0 - x1) / 128;
int right = (x0 + x1) / 128;
if (sprite->dy > 0) return;
if (game->ground[left + top * game->ground_width] != 0)
if (game->ground[right + top * game->ground_width] != 0)
if (game->ground[left + top2 * game->ground_width] != 0 || game->ground[right + top2 * game->ground_width] != 0)
{
sprite->dy = 0;
sprite->y = (top + 1) * 128 + y1;
}
}
static void mimimi_landing_physics(struct mimimi_sprite *sprite)
{
struct mimimi_game *game = sprite->game;
int x0 = sprite->x;
int x1 = sprite->width / 2;
int y0 = sprite->y;
int bottom = y0 / 128;
int left = (x0 - x1) / 128;
int right = (x0 + x1) / 128;
if (sprite->dy < 0) return;
if (game->ground[left + bottom * game->ground_width] != 0 || game->ground[right + bottom * game->ground_width] != 0)
{
sprite->airborne = 0;
sprite->dy = 0;
sprite->y = bottom * 128;
}
}
static void mimimi_fall_physics(struct mimimi_sprite *sprite)
{
struct mimimi_game *game = sprite->game;
int x0 = sprite->x;
int x1 = sprite->width / 2;
int y0 = sprite->y;
int bottom = y0 / 128;
int center = x0 / 128;
int left = (x0 - x1) / 128;
int right = (x0 + x1) / 128;
if (game->ground[left + bottom * game->ground_width] == 0 && game->ground[right + bottom * game->ground_width] == 0)
{
if (game->ground[center + (bottom + 1) * game->ground_width] == 0)
sprite->airborne = 1;
else
sprite->y = (bottom + 1) * 128;
}
}
static void mimimi_physics_dynamics(struct mimimi_sprite *sprite)
{
sprite->x += sprite->dx;
sprite->y += sprite->dy;
}
static void mimimi_ground_physics(struct mimimi_sprite *sprite)
{
sprite->dx *= 7;
sprite->dx /= 8;
sprite->dy = 0;
mimimi_physics_dynamics(sprite);
mimimi_step_physics(sprite);
mimimi_wall_physics(sprite);
mimimi_fall_physics(sprite);
}
static void mimimi_airborne_physics(struct mimimi_sprite *sprite, struct mimimi_physics_data *data)
{
sprite->dx *= 15;
sprite->dx /= 16;
sprite->dy += data->gravity;
mimimi_physics_dynamics(sprite);
mimimi_ceiling_physics(sprite);
mimimi_wall_physics(sprite);
mimimi_landing_physics(sprite);
}
static void mimimi_attack(struct mimimi_sprite *sprite)
{
mimimi_damage(sprite, 64, -256, 512, 512);
}
static void mimimi_rapid_attack(struct mimimi_sprite *sprite)
{
}
static unsigned char mimimi_count_oscillations(unsigned int history)
{
unsigned char count = 0;
unsigned char prev = history&1;
for (int i = 0 ; i < 16 ; i++)
{
if ((history&1) != prev) count++;
prev = history&1;
history >>= 1;
}
return count;
}
int mimimi_text(struct mimimi_image *image, struct mimimi_image *glyphs, int x, int y, char *text)
{
while (*text != 0)
{
char ch = *text++;
if (ch == 0x20)
{
x += 4;
continue;
}
if (ch <= 0x20) continue;
if (ch >= 0x7F) continue;
struct mimimi_image *glyph = glyphs + (ch - 0x20);
mimimi_stamp_image(image, x - 1, y - glyph->height + 6, glyph);
x += glyph->width - 1;
}
return x;
}
void mimimi_physics(struct mimimi_sprite *sprite, void *data)
{
if (sprite->airborne == 0)
mimimi_ground_physics(sprite);
else
mimimi_airborne_physics(sprite, data);
}
void mimimi_jump(struct mimimi_sprite *sprite)
{
if (sprite->airborne) return;
sprite->airborne = 1;
sprite->dy = -64;
}
void mimimi_damage(struct mimimi_sprite *sprite, int damage, int attack_y, int attack_width, int attack_height)
{
struct mimimi_game *game = sprite->game;
int x1 = sprite->x - attack_width;
int y1 = sprite->y + attack_y;
if (sprite->facing_direction == 2) x1 += attack_width;
int x2 = x1 + attack_width;
int y2 = y1 + attack_height;
for (struct mimimi_sprite *other = game->sprite ; other != &mimimi_last_sprite ; other = other->next)
{
if (other == sprite) continue;
if (other->immunity_time != 0) continue;
int x = other->x;
int y = other->y;
if (x > x1 && x < x2)
if (y > y1 && y < y2)
{
other->airborne = 1;
other->dy -= 32;
if (other->x > sprite->x)
other->dx += 32;
else
other->dx -= 32;
if (other->pristinity < damage)
other->knocked_time = 128;
else
other->pristinity -= damage;
}
}
}
void mimimi_controls(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_game *game = sprite->game;
unsigned char left_oscillations = mimimi_count_oscillations(game->left_history);
unsigned char right_oscillations = mimimi_count_oscillations(game->right_history);
switch (sprite->moving_direction)
{
case 0:
if ((game->left_history&1) != 0)
sprite->moving_direction = 1;
if ((game->right_history&1) != 0)
sprite->moving_direction = 2;
break;
case 1:
if ((game->left_history&1) == 0)
sprite->moving_direction = 0;
else if (left_oscillations > 1)
mimimi_jump(sprite);
if ((game->right_history&1) != 0)
{
if (right_oscillations == 0)
mimimi_rapid_attack(sprite);
}
if ((game->right_history&1) != 0 && (game->right_history&2) == 0)
mimimi_attack(sprite);
break;
case 2:
if ((game->right_history&1) == 0)
sprite->moving_direction = 0;
else if (right_oscillations > 1)
mimimi_jump(sprite);
if ((game->left_history&1) != 0)
{
if (left_oscillations == 0)
mimimi_rapid_attack(sprite);
}
if ((game->left_history&1) != 0 && (game->left_history&2) == 0)
mimimi_attack(sprite);
break;
}
}
void mimimi_enemy_ai(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_sprite *target = data;
int x = sprite->x - target->x;
int y = sprite->y - target->y;
int abs_x = x;
int abs_y = y;
if (abs_x < 0) abs_x = -abs_x;
if (abs_y < 0) abs_y = -abs_y;
sprite->moving_direction = 0;
if (x > 256)
sprite->moving_direction = 1;
else if (x < -256)
sprite->moving_direction = 2;
else
mimimi_attack( sprite);
if (y > 512) mimimi_jump(sprite);
}
void mimimi_stationary_ai(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_sprite *target = data;
int x = sprite->x - target->x;
if (x > 0) sprite->facing_direction = 1;
else sprite->facing_direction = 2;
}
void mimimi_spawn(struct mimimi_game *game, struct mimimi_sprite *sprite, int x, int y, int width, int height, struct mimimi_sprite_appearance *appearance, struct mimimi_behavior *behavior)
{
sprite->next = game->sprite;
game->sprite = sprite;
sprite->game = game;
sprite->appearance = appearance;
sprite->x = x;
sprite->y = y;
sprite->width = width;
sprite->height = height;
sprite->dx = 0;
sprite->dy = 0;
sprite->airborne = 1;
sprite->moving_direction = 0;
sprite->facing_direction = 1;
sprite->animation_time = 0;
sprite->immunity_time = 0;
sprite->pristinity = -1;
sprite->behave = behavior->behave;
sprite->data = behavior->data;
(*game->chapter->occurrence)(game->chapter, mimimi_sprite_spawned, sprite);
}
void mimimi_camera_sprite(struct mimimi_sprite *sprite)
{
sprite->game->camera = sprite;
}
void mimimi_fall(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_fall_data *fall_data = data;
if (sprite->dy > fall_data->max_speed)
if (sprite->knocked_time < fall_data->knocked_time)
sprite->knocked_time = fall_data->knocked_time;
}
void mimimi_live(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_live_data *live_data = data;
if (sprite->knocked_time != 0)
{
sprite->knocked_time--;
sprite->immunity_time = live_data->recovery_immunity_time;
}
if (sprite->immunity_time != 0)
{
sprite->pristinity = live_data->max_pristinity;
sprite->immunity_time--;
}
if (sprite->pristinity < live_data->max_pristinity)
sprite->pristinity++;
}
void mimimi_walk(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_walk_data *walk_data = data;
if (sprite->moving_direction != 0)
{
sprite->facing_direction = sprite->moving_direction;
int ax;
if (sprite->airborne)
ax = walk_data->airborne_speed;
else
ax = walk_data->ground_speed;
if (sprite->moving_direction == 1)
sprite->dx -= ax;
if (sprite->moving_direction == 2)
sprite->dx += ax;
}
}
void mimimi_camera(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_camera_data *camera_data = data;
sprite->x *= 3;
sprite->y *= 3;
sprite->x += camera_data->target->x;
sprite->y += camera_data->target->y - 512;
sprite->x /= 4;
sprite->y /= 4;
}
void mimimi_behave(struct mimimi_sprite *sprite, void *data)
{
struct mimimi_behave_data *behave_data = data;
mimimi_live(sprite, behave_data->live_data);
mimimi_fall(sprite, behave_data->fall_data);
if (sprite->knocked_time == 0)
{
(*behave_data->behavior->behave)(sprite, behave_data->behavior->data);
mimimi_walk(sprite, behave_data->walk_data);
}
mimimi_physics(sprite, behave_data->physics_data);
}
void mimimi_physical_attributes(struct mimimi_sprite *sprite, struct mimimi_physical_attributes *attributes)
{
attributes->x = sprite->x;
attributes->y = sprite->y;
attributes->width = sprite->width;
attributes->height = sprite->height;
attributes->dx = sprite->dx;
attributes->dy = sprite->dy;
attributes->airborne = sprite->airborne;
attributes->direction = sprite->facing_direction;
}
void mimimi_living_attributes(struct mimimi_sprite *sprite, struct mimimi_living_attributes *attributes)
{
attributes->knocked_time = sprite->knocked_time;
attributes->immunity_time = sprite->immunity_time;
attributes->pristinity = sprite->pristinity;
}
void mimimi_start(struct mimimi_game *game, struct mimimi_engine *engine, struct mimimi_chapter *chapter)
{
game->engine = engine;
game->chapter = chapter;
game->left_history = 0;
game->right_history = 0;
game->sprite = &mimimi_last_sprite;
game->camera = 0;
game->background = mimimi_empty_image;
(*chapter->start)(chapter, game);
}
void mimimi_ground(struct mimimi_game *game, unsigned char *ground, int width, int height)
{
game->ground = ground;
game->ground_width = width;
game->ground_height = height;
}
void mimimi_background(struct mimimi_game *game, struct mimimi_image *background)
{
game->background = background;
}
void mimimi_overlay(struct mimimi_game *game, struct mimimi_image *overlay)
{
game->overlay = overlay;
}
void mimimi_step(struct mimimi_game *game, struct mimimi_keys keys)
{
(game->chapter->step)(game->chapter);
game->left_history <<= 1;
game->left_history |= keys.left;
game->right_history <<= 1;
game->right_history |= keys.right;
mimimi_stamp(game, game->background->width * 4, game->background->height * 8 - 7, game->background);
for (struct mimimi_sprite *sprite = game->sprite ; sprite != &mimimi_last_sprite ; sprite = sprite->next)
{
(sprite->behave)(sprite, sprite->data);
int dx;
int dy = sprite->dy;
struct mimimi_animation_set *animations;
if (sprite->facing_direction == 1)
{
dx = -sprite->dx;
animations = &sprite->appearance->left;
}
if (sprite->facing_direction == 2)
{
dx = sprite->dx;
animations = &sprite->appearance->right;
}
struct mimimi_animation *animation;
if (dx > 32) dx = 32;
if (dx < 0) dx = 0;
if (dy < 0) dy = 0;
if (sprite->knocked_time == 0)
{
animation = animations->standing + dx * mimimi_animation_count / 32;
sprite->animation_time += dx / 4;
}
else
{
animation = &animations->knocked;
if (dy == 0) sprite->animation_time = 0;
else sprite->animation_time += dy / 8;
}
struct mimimi_image *image = &animation->images[sprite->animation_time * mimimi_image_count / 256].image;
int y = 0;
if (sprite->knocked_time != 0) y = image->height * 4 - 32;
mimimi_stamp(game, sprite->x, sprite->y + y, image);
}
(*game->engine->stamp)(game->engine->data, 0, 0, game->overlay);
}