Mirai's Miscellaneous Misadventures

M47 / core / sprites.c

/* license: AGPLv3 or later */
/* copyright 2023 zamfofex */

#include <mimimi.h>

/*
	todo: Physics behaves strangely near the left and top edges of a given ground.
	That is because division rounds towards zero rather than towards negative infinity.
	Is this worhtwhile fixing?
	It is usually easy to avoid this by simply having the leftmost and topmost edges be zeroed out or inaccessible.
*/

static void mimimi_wall_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	int x0, y0;
	int x1, y1;
	int left, right;
	int top, bottom;
	int top2, bottom2;
	
	x0 = position->x;
	y0 = position->y;
	x1 = physics->width / 2;
	y1 = physics->height;
	
	top = (y0 - y1) / 128;
	top2 = top - 1;
	bottom = (y0 - 127) / 128;
	bottom2 = bottom + 1;
	left = (x0 - x1) / 128;
	right = (x0 + x1) / 128;
	
	if (
		(mimimi_ground_tile(ground, left, bottom) != 0 && mimimi_ground_tile(ground, left, bottom2) != 0) ||
		(mimimi_ground_tile(ground, left, top) != 0 && mimimi_ground_tile(ground, left, top2) != 0)
	)
	{
		if (physics->dx < 0) physics->dx = 0;
		position->x = (left + 1) * 128 + x1;
	}
	
	if (
		(mimimi_ground_tile(ground, right, bottom) != 0 && mimimi_ground_tile(ground, right, bottom2) != 0) ||
		(mimimi_ground_tile(ground, right, top) != 0 && mimimi_ground_tile(ground, right, top2) != 0)
	)
	{
		if (physics->dx > 0) physics->dx = 0;
		position->x = right * 128 - x1 - 1;
	}
}

static void mimimi_slope_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	int x0, y0;
	int x1, y1;
	int top, bottom;
	int left, right;
	
	x0 = position->x;
	y0 = position->y;
	x1 = physics->width / 2;
	y1 = physics->height;
	
	top = (y0 - y1) / 128;
	bottom = (y0 - 127) / 128;
	left = (x0 - x1) / 128;
	right = (x0 + x1) / 128;
	
	if (mimimi_ground_tile(ground, left, bottom) != 0)
	if (mimimi_ground_tile(ground, left, top) == 0)
		position->y = bottom * 128;
	
	if (mimimi_ground_tile(ground, right, bottom) != 0)
	if (mimimi_ground_tile(ground, right, top) == 0)
		position->y = bottom * 128;
}

static void mimimi_ceiling_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	int x0, y0;
	int x1, y1;
	int top, top2;
	int left, right;
	
	x0 = position->x;
	y0 = position->y;
	x1 = physics->width / 2;
	y1 = physics->height;
	
	top = (y0 - y1) / 128;
	top2 = top - 1;
	left = (x0 - x1) / 128;
	right = (x0 + x1) / 128;
	
	if (mimimi_ground_tile(ground, left, top) != 0 && mimimi_ground_tile(ground, right, top) != 0)
	if (mimimi_ground_tile(ground, left, top2) != 0 || mimimi_ground_tile(ground, right, top2) != 0)
	{
		if (physics->dy < 0) physics->dy = 0;
		position->y = (top + 1) * 128 + y1;
	}
}

static void mimimi_landing_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	int x0, y0;
	int x1;
	int left, right;
	int bottom;
	
	x0 = position->x;
	y0 = position->y;
	x1 = physics->width / 2;
	
	bottom = y0 / 128;
	left = (x0 - x1) / 128;
	right = (x0 + x1) / 128;
	
	if (physics->dy < 0) return;
	
	if (mimimi_ground_tile(ground, left, bottom) != 0 || mimimi_ground_tile(ground, right, bottom) != 0)
	{
		physics->airborne = 0;
		physics->dy = 0;
		position->y = bottom * 128;
	}
}

static void mimimi_fall_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	int x0, y0;
	int x1;
	int bottom, bottom2;
	int center;
	int left, right;
	
	x0 = position->x;
	y0 = position->y;
	x1 = physics->width / 2;
	
	bottom = y0 / 128;
	bottom2 = bottom + 1;
	center = x0 / 128;
	left = (x0 - x1) / 128;
	right = (x0 + x1) / 128;
	
	if (mimimi_ground_tile(ground, left, bottom) == 0 && mimimi_ground_tile(ground, right, bottom) == 0)
	{
		if (mimimi_ground_tile(ground, center, bottom2) == 0)
			/* fall */
			physics->airborne = 1;
		else
			/* step down */
			position->y = bottom2 * 128;
	}
}

static void mimimi_physics_dynamics(struct mimimi_physics *physics, struct mimimi_position *position)
{
	position->x += physics->dx;
	position->y += physics->dy;
}

static void mimimi_ground_collision_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	mimimi_slope_physics(physics, position, ground);
	mimimi_wall_physics(physics, position, ground);
	mimimi_fall_physics(physics, position, ground);
	
	if (physics->airborne == 0)
	{
		position->y /= 128;
		position->y *= 128;
	}
}

static void mimimi_airborne_collision_physics(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	mimimi_ceiling_physics(physics, position, ground);
	mimimi_wall_physics(physics, position, ground);
	mimimi_landing_physics(physics, position, ground);
}

static void mimimi_ground_physics(struct mimimi_physics *physics, struct mimimi_position *position)
{
	physics->dx *= 5;
	physics->dx /= 6;
	physics->dy = 0;
	mimimi_physics_dynamics(physics, position);
}

static void mimimi_airborne_physics(struct mimimi_physics *physics, struct mimimi_position *position)
{
	physics->dx *= 17;
	physics->dx /= 18;
	physics->dy += physics->gravity;
	mimimi_physics_dynamics(physics, position);
}

void mimimi_collision_physics_tick(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	if (physics->airborne == 0)
		mimimi_ground_collision_physics(physics, position, ground);
	else
		mimimi_airborne_collision_physics(physics, position, ground);
}

void mimimi_dynamics_tick(struct mimimi_physics *physics, struct mimimi_position *position)
{
	if (physics->airborne == 0)
		mimimi_ground_physics(physics, position);
	else
		mimimi_airborne_physics(physics, position);
}

void mimimi_physics_tick(struct mimimi_physics *physics, struct mimimi_position *position, struct mimimi_ground *ground)
{
	mimimi_dynamics_tick(physics, position);
	mimimi_collision_physics_tick(physics, position, ground);
}

void mimimi_physics(struct mimimi_physics *physics, int width, int height)
{
	physics->dx = 0;
	physics->dy = 0;
	physics->airborne = 1;
	physics->width = width;
	physics->height = height;
	physics->gravity = 3;
}

void mimimi_life_tick(struct mimimi_life *life)
{
	if (life->knocked_time != 0)
	{
		life->knocked_time--;
		life->immunity_time = life->recovery_time;
	}
	
	if (life->immunity_time != 0)
	{
		life->pristinity = life->max_pristinity;
		life->immunity_time--;
	}
	
	if (life->pristinity < life->max_pristinity)
		life->pristinity++;
}

void mimimi_fall_tick(struct mimimi_fall *fall, struct mimimi_physics *physics, struct mimimi_life *life)
{
	if (physics->dy > fall->max_speed && life->knocked_time < fall->knocked_time)
		life->knocked_time = fall->knocked_time;
}

void mimimi_walk_tick(struct mimimi_walk *walk, struct mimimi_physics *physics)
{
	int ax;
	
	if (walk->direction == 0) return;
	
	if (physics->airborne == 0)
		ax = walk->ground_speed;
	else
		ax = walk->airborne_speed;
	
	if (walk->direction == 1)
		physics->dx -= ax;
	if (walk->direction == 2)
		physics->dx += ax;
}

void mimimi_sprite(struct mimimi_sprite *sprite, struct mimimi_ground *ground, int x, int y, int width, int height)
{
	sprite->position.x = x;
	sprite->position.y = y;
	
	mimimi_physics(&sprite->physics, width, height);
	
	sprite->life.knocked_time = 0;
	sprite->life.immunity_time = 0;
	sprite->life.pristinity = 256;
	sprite->life.max_pristinity = 256;
	sprite->life.recovery_time = 64;
	
	sprite->fall.max_speed = 128;
	sprite->fall.knocked_time = 64;
	
	sprite->walk.ground_speed = 6;
	sprite->walk.airborne_speed = 3;
	sprite->walk.direction = 0;
	
	sprite->offset.x = 0;
	sprite->offset.y = 0;
	
	sprite->ground = ground;
}

void mimimi_sprite_tick(struct mimimi_sprite *sprite)
{
	struct mimimi_position position;
	
	mimimi_life_tick(&sprite->life);
	if (sprite->life.knocked_time == 0) mimimi_walk_tick(&sprite->walk, &sprite->physics);
	
	position = sprite->position;
	position.x -= sprite->offset.x;
	position.y -= sprite->offset.y;
	mimimi_physics_tick(&sprite->physics, &position, sprite->ground);
	position.x += sprite->offset.x;
	position.y += sprite->offset.y;
	sprite->position = position;
	
	mimimi_fall_tick(&sprite->fall, &sprite->physics, &sprite->life);
}

void mimimi_jump(void *data)
{
	struct mimimi_sprite *sprite;
	sprite = data;
	if (sprite->life.knocked_time != 0) return;
	if (sprite->physics.airborne != 0) return;
	sprite->physics.airborne = 1;
	sprite->physics.dy = -56;
}

void mimimi_offset_tick(struct mimimi_offset *offset, int x, int y)
{
	int i;
	int count;
	struct mimimi_offset_threshold *thresholds;
	struct mimimi_sprite *sprite;
	
	count = offset->count;
	sprite = offset->sprite;
	thresholds = offset->thresholds;
	
	for (i = 0 ; i < count ; i++)
	{
		if (sprite->position.x < thresholds[i].x * 128)
			break;
	}
	
	sprite->offset = thresholds[i].offset;
	sprite->offset.x -= x;
	sprite->offset.y -= y;
}