phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

world.c (8852B)


      1 #include "engine/world.h"
      2 
      3 #include <stdint.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <math.h>
      7 #include <string.h>
      8 
      9 enum {
     10 	PH_MAP_MAGIC = 0x50484d50,
     11 	PH_MAP_VERSION = 1,
     12 	PH_AREA_NAME_MAX = 64,
     13 	PH_ENTITY_BLOCK_RADIUS = 10,
     14 };
     15 
     16 typedef struct {
     17 	uint32_t magic;
     18 	uint32_t version;
     19 	uint32_t width;
     20 	uint32_t height;
     21 	char name[PH_AREA_NAME_MAX];
     22 } PhMapHeader;
     23 
     24 static PhVec2
     25 ph_vec2(float x, float y)
     26 {
     27 	PhVec2 v = { x, y };
     28 	return v;
     29 }
     30 
     31 static float
     32 ph_clampf(float value, float min, float max)
     33 {
     34 	if (value < min) {
     35 		return min;
     36 	}
     37 	if (value > max) {
     38 		return max;
     39 	}
     40 	return value;
     41 }
     42 
     43 static float
     44 ph_dist2(PhVec2 a, PhVec2 b)
     45 {
     46 	float dx = a.x - b.x;
     47 	float dy = a.y - b.y;
     48 	return dx * dx + dy * dy;
     49 }
     50 
     51 static int
     52 ph_entity_def_index(const PhWorld *world, int type_id)
     53 {
     54 	int i;
     55 
     56 	for (i = 0; i < world->entity_def_count; ++i) {
     57 		if (world->entity_defs[i].id == type_id) {
     58 			return i;
     59 		}
     60 	}
     61 	return -1;
     62 }
     63 
     64 static int
     65 ph_item_def_index(const PhWorld *world, int item_id)
     66 {
     67 	int i;
     68 
     69 	for (i = 0; i < world->item_def_count; ++i) {
     70 		if (world->item_defs[i].id == item_id) {
     71 			return i;
     72 		}
     73 	}
     74 	return -1;
     75 }
     76 
     77 static void
     78 ph_camera_follow_player(PhWorld *world)
     79 {
     80 	const PhEntity *player;
     81 	float max_x;
     82 	float max_y;
     83 
     84 	player = ph_world_player(world);
     85 	if (!player) {
     86 		return;
     87 	}
     88 
     89 	max_x = (float)(world->area.width * PH_TILE_SIZE) - world->camera.viewport_w;
     90 	max_y = (float)(world->area.height * PH_TILE_SIZE) - world->camera.viewport_h;
     91 	if (max_x < 0.0f) {
     92 		max_x = 0.0f;
     93 	}
     94 	if (max_y < 0.0f) {
     95 		max_y = 0.0f;
     96 	}
     97 
     98 	world->camera.pos.x = ph_clampf(player->pos.x - world->camera.viewport_w * 0.5f, 0.0f, max_x);
     99 	world->camera.pos.y = ph_clampf(player->pos.y - world->camera.viewport_h * 0.5f, 0.0f, max_y);
    100 }
    101 
    102 static int
    103 ph_position_blocked(const PhWorld *world, PhVec2 pos)
    104 {
    105 	int tx = (int)floorf(pos.x / (float)PH_TILE_SIZE);
    106 	int ty = (int)floorf(pos.y / (float)PH_TILE_SIZE);
    107 
    108 	return ph_area_tile_blocked(&world->area, tx, ty);
    109 }
    110 
    111 static void
    112 ph_world_set_notice(PhWorld *world, const char *text)
    113 {
    114 	snprintf(world->notice, sizeof(world->notice), "%s", text);
    115 	world->notice_seconds = 1.25f;
    116 }
    117 
    118 static const PhEntity *
    119 ph_world_blocking_entity(const PhWorld *world, int self_index, PhVec2 pos)
    120 {
    121 	int i;
    122 
    123 	for (i = 0; i < world->entity_count; ++i) {
    124 		const PhEntity *entity = &world->entities[i];
    125 		const PhEntityDef *def;
    126 
    127 		if (i == self_index || !entity->active) {
    128 			continue;
    129 		}
    130 
    131 		def = ph_world_entity_def(world, entity->type_id);
    132 		if (!def || !def->blocks_movement) {
    133 			continue;
    134 		}
    135 
    136 		if (ph_dist2(entity->pos, pos) <
    137 				(float)(PH_ENTITY_BLOCK_RADIUS * PH_ENTITY_BLOCK_RADIUS)) {
    138 			return entity;
    139 		}
    140 	}
    141 
    142 	return NULL;
    143 }
    144 
    145 static int
    146 ph_player_step_blocked(PhWorld *world, int player_index, PhVec2 pos)
    147 {
    148 	const PhEntity *blocker;
    149 	const PhEntityDef *def;
    150 
    151 	if (ph_position_blocked(world, pos)) {
    152 		return 1;
    153 	}
    154 
    155 	blocker = ph_world_blocking_entity(world, player_index, pos);
    156 	if (!blocker) {
    157 		return 0;
    158 	}
    159 
    160 	def = ph_world_entity_def(world, blocker->type_id);
    161 	if (def && def->name) {
    162 		char text[PH_NOTICE_SIZE];
    163 
    164 		snprintf(text, sizeof(text), "%s blocks your path.", def->name);
    165 		ph_world_set_notice(world, text);
    166 	}
    167 	return 1;
    168 }
    169 
    170 static void
    171 ph_try_pickup(PhWorld *world, PhEntity *entity)
    172 {
    173 	int i;
    174 
    175 	if (!entity || !entity->active) {
    176 		return;
    177 	}
    178 
    179 	for (i = 0; i < world->ground_item_count; ++i) {
    180 		PhGroundItem *drop = &world->ground_items[i];
    181 		const PhItemDef *item;
    182 
    183 		if (!drop->active) {
    184 			continue;
    185 		}
    186 		if (ph_dist2(entity->pos, drop->pos) > 18.0f * 18.0f) {
    187 			continue;
    188 		}
    189 
    190 		item = ph_world_item_def(world, drop->item_id);
    191 		if (item) {
    192 			world->player_talent_points += item->talent_points * drop->amount;
    193 		}
    194 		drop->active = 0;
    195 	}
    196 }
    197 
    198 static PhInput
    199 ph_orthogonal_input(PhInput input, const PhEntity *entity)
    200 {
    201 	if (input.move_x == 0 || input.move_y == 0) {
    202 		return input;
    203 	}
    204 
    205 	if (entity->facing_x != 0) {
    206 		input.move_y = 0;
    207 	} else {
    208 		input.move_x = 0;
    209 	}
    210 	return input;
    211 }
    212 
    213 void
    214 ph_world_init(PhWorld *world, PhArea area, float viewport_w, float viewport_h)
    215 {
    216 	memset(world, 0, sizeof(*world));
    217 	world->area = area;
    218 	world->camera.viewport_w = viewport_w;
    219 	world->camera.viewport_h = viewport_h;
    220 	world->player_index = -1;
    221 }
    222 
    223 int
    224 ph_area_load(PhArea *area, const char *path)
    225 {
    226 	FILE *fp;
    227 	PhMapHeader header;
    228 	size_t tile_count;
    229 	size_t name_len;
    230 	unsigned char *tiles;
    231 	char *name;
    232 
    233 	memset(area, 0, sizeof(*area));
    234 
    235 	fp = fopen(path, "rb");
    236 	if (!fp) {
    237 		return -1;
    238 	}
    239 
    240 	if (fread(&header, sizeof(header), 1, fp) != 1 ||
    241 			header.magic != PH_MAP_MAGIC ||
    242 			header.version != PH_MAP_VERSION ||
    243 			header.width == 0 ||
    244 			header.height == 0) {
    245 		fclose(fp);
    246 		return -1;
    247 	}
    248 
    249 	tile_count = (size_t)header.width * (size_t)header.height;
    250 	tiles = malloc(tile_count);
    251 	if (!tiles) {
    252 		fclose(fp);
    253 		return -1;
    254 	}
    255 
    256 	if (fread(tiles, 1, tile_count, fp) != tile_count) {
    257 		free(tiles);
    258 		fclose(fp);
    259 		return -1;
    260 	}
    261 	fclose(fp);
    262 
    263 	header.name[PH_AREA_NAME_MAX - 1] = '\0';
    264 	name_len = strlen(header.name) + 1;
    265 	name = malloc(name_len);
    266 	if (!name) {
    267 		free(tiles);
    268 		return -1;
    269 	}
    270 	memcpy(name, header.name, name_len);
    271 
    272 	area->name = name;
    273 	area->width = (int)header.width;
    274 	area->height = (int)header.height;
    275 	area->tiles = tiles;
    276 	area->owns_tiles = 1;
    277 	return 0;
    278 }
    279 
    280 void
    281 ph_area_free(PhArea *area)
    282 {
    283 	if (area->owns_tiles) {
    284 		free(area->tiles);
    285 		free((char *)area->name);
    286 	}
    287 	memset(area, 0, sizeof(*area));
    288 }
    289 
    290 int
    291 ph_world_add_entity_def(PhWorld *world, PhEntityDef def)
    292 {
    293 	if (world->entity_def_count >= PH_MAX_ENTITY_TYPES) {
    294 		return -1;
    295 	}
    296 	world->entity_defs[world->entity_def_count++] = def;
    297 	return 0;
    298 }
    299 
    300 int
    301 ph_world_add_item_def(PhWorld *world, PhItemDef def)
    302 {
    303 	if (world->item_def_count >= PH_MAX_ITEM_TYPES) {
    304 		return -1;
    305 	}
    306 	world->item_defs[world->item_def_count++] = def;
    307 	return 0;
    308 }
    309 
    310 int
    311 ph_world_spawn_entity(PhWorld *world, int type_id, PhVec2 pos)
    312 {
    313 	PhEntity *entity;
    314 	const PhEntityDef *def;
    315 
    316 	if (world->entity_count >= PH_MAX_ENTITIES) {
    317 		return -1;
    318 	}
    319 
    320 	def = ph_world_entity_def(world, type_id);
    321 	if (!def) {
    322 		return -1;
    323 	}
    324 
    325 	entity = &world->entities[world->entity_count];
    326 	entity->type_id = type_id;
    327 	entity->pos = pos;
    328 	entity->hp = def->max_hp;
    329 	entity->facing_x = 0;
    330 	entity->facing_y = 1;
    331 	entity->active = 1;
    332 
    333 	return world->entity_count++;
    334 }
    335 
    336 int
    337 ph_world_drop_item(PhWorld *world, int item_id, PhVec2 pos, int amount)
    338 {
    339 	PhGroundItem *drop;
    340 
    341 	if (world->ground_item_count >= PH_MAX_GROUND_ITEMS) {
    342 		return -1;
    343 	}
    344 	if (!ph_world_item_def(world, item_id)) {
    345 		return -1;
    346 	}
    347 
    348 	drop = &world->ground_items[world->ground_item_count];
    349 	drop->item_id = item_id;
    350 	drop->pos = pos;
    351 	drop->amount = amount;
    352 	drop->active = 1;
    353 
    354 	return world->ground_item_count++;
    355 }
    356 
    357 void
    358 ph_world_set_player(PhWorld *world, int entity_index)
    359 {
    360 	if (entity_index >= 0 && entity_index < world->entity_count) {
    361 		world->player_index = entity_index;
    362 		ph_camera_follow_player(world);
    363 	}
    364 }
    365 
    366 void
    367 ph_world_tick(PhWorld *world, PhInput input, float dt)
    368 {
    369 	PhEntity *player;
    370 	const PhEntityDef *def;
    371 	PhVec2 next;
    372 
    373 	if (world->player_index < 0 || world->player_index >= world->entity_count) {
    374 		return;
    375 	}
    376 
    377 	player = &world->entities[world->player_index];
    378 	def = ph_world_entity_def(world, player->type_id);
    379 	if (!player->active || !def) {
    380 		return;
    381 	}
    382 
    383 	if (world->notice_seconds > 0.0f) {
    384 		world->notice_seconds -= dt;
    385 		if (world->notice_seconds <= 0.0f) {
    386 			world->notice_seconds = 0.0f;
    387 			world->notice[0] = '\0';
    388 		}
    389 	}
    390 
    391 	input = ph_orthogonal_input(input, player);
    392 	next = player->pos;
    393 	if (input.move_x != 0 || input.move_y != 0) {
    394 		float speed = (float)def->move_speed;
    395 
    396 		player->facing_x = input.move_x;
    397 		player->facing_y = input.move_y;
    398 		next.x += (float)input.move_x * speed * dt;
    399 		next.y += (float)input.move_y * speed * dt;
    400 
    401 		if (!ph_player_step_blocked(world, world->player_index,
    402 				ph_vec2(next.x, player->pos.y))) {
    403 			player->pos.x = next.x;
    404 		}
    405 		if (!ph_player_step_blocked(world, world->player_index,
    406 				ph_vec2(player->pos.x, next.y))) {
    407 			player->pos.y = next.y;
    408 		}
    409 	}
    410 
    411 	if (input.interact) {
    412 		ph_try_pickup(world, player);
    413 	}
    414 
    415 	ph_camera_follow_player(world);
    416 }
    417 
    418 const PhEntity *
    419 ph_world_player(const PhWorld *world)
    420 {
    421 	if (world->player_index < 0 || world->player_index >= world->entity_count) {
    422 		return NULL;
    423 	}
    424 	return &world->entities[world->player_index];
    425 }
    426 
    427 const PhEntityDef *
    428 ph_world_entity_def(const PhWorld *world, int type_id)
    429 {
    430 	int i = ph_entity_def_index(world, type_id);
    431 	return i >= 0 ? &world->entity_defs[i] : NULL;
    432 }
    433 
    434 const PhItemDef *
    435 ph_world_item_def(const PhWorld *world, int item_id)
    436 {
    437 	int i = ph_item_def_index(world, item_id);
    438 	return i >= 0 ? &world->item_defs[i] : NULL;
    439 }
    440 
    441 int
    442 ph_area_tile_blocked(const PhArea *area, int tx, int ty)
    443 {
    444 	if (tx < 0 || ty < 0 || tx >= area->width || ty >= area->height) {
    445 		return 1;
    446 	}
    447 	return area->tiles[(size_t)ty * (size_t)area->width + (size_t)tx] == '#';
    448 }