phantasia

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

area.c (9316B)


      1 #include "engine/area.h"
      2 
      3 #include <math.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 
      7 const PhAreaDef *
      8 ph_area_catalog_area(const PhAreaCatalog *catalog, int area_id)
      9 {
     10 	int i;
     11 
     12 	for (i = 0; i < catalog->area_count; ++i)
     13 		if (catalog->areas[i].id == area_id) return &catalog->areas[i];
     14 	return NULL;
     15 }
     16 
     17 const PhMarkerDef *
     18 ph_area_catalog_marker(const PhAreaCatalog *catalog, int area_id,
     19 	unsigned char symbol)
     20 {
     21 	int i;
     22 
     23 	for (i = 0; i < catalog->marker_count; ++i)
     24 		if (catalog->markers[i].area_id == area_id &&
     25 				catalog->markers[i].symbol == symbol) return &catalog->markers[i];
     26 	return NULL;
     27 }
     28 
     29 int
     30 ph_area_catalog_load(PhArea *area, const PhAreaCatalog *catalog, int area_id)
     31 {
     32 	const PhAreaDef *def = ph_area_catalog_area(catalog, area_id);
     33 
     34 	if (!def || ph_area_load(area, def->path) < 0) return -1;
     35 	area->tile_defs = catalog->tiles;
     36 	area->tile_def_count = catalog->tile_count;
     37 	return 0;
     38 }
     39 
     40 int
     41 ph_world_start(PhWorld *world, const PhWorldSetup *setup)
     42 {
     43 	PhArea area;
     44 	int player = -1;
     45 	int i;
     46 
     47 	if (!setup || !setup->areas || setup->equipment_count < 0 ||
     48 			(setup->equipment_count && !setup->equipment) ||
     49 			ph_area_catalog_load(&area, setup->areas, setup->start_area_id) < 0)
     50 		return -1;
     51 	if (ph_world_init(world, area, setup->start_area_id, setup->content) < 0) {
     52 		ph_area_free(&area);
     53 		return -1;
     54 	}
     55 	if (ph_world_prepare_area(world, &world->area, setup->areas,
     56 			setup->start_area_id, &player) < 0 || player < 0) goto fail;
     57 	for (i = 0; i < setup->equipment_count; ++i)
     58 		if (setup->equipment[i] &&
     59 				ph_world_equip_item(world, player, setup->equipment[i]) < 0)
     60 			goto fail;
     61 	ph_world_set_player(world, player);
     62 	return 0;
     63 
     64 fail:
     65 	ph_area_free(&world->area);
     66 	return -1;
     67 }
     68 
     69 static PhVec2
     70 ph_marker_pos(const PhAreaMarker *marker)
     71 {
     72 	return (PhVec2){ PH_TILE_CENTER(marker->tile_x),
     73 		PH_TILE_CENTER(marker->tile_y) };
     74 }
     75 
     76 static int
     77 ph_world_add_area_content(PhWorld *world, const PhArea *area,
     78 	const PhAreaCatalog *catalog, int area_id, int *player)
     79 {
     80 	int i;
     81 
     82 	for (i = 0; i < area->marker_count; ++i) {
     83 		const PhAreaMarker *marker = &area->markers[i];
     84 		const PhMarkerDef *def = ph_area_catalog_marker(catalog, area_id,
     85 			marker->symbol);
     86 		int entity;
     87 
     88 		if (!def) return -1;
     89 		if (def->kind == PH_MARKER_ITEM) {
     90 			if (ph_world_drop_item(world, def->content_id, ph_marker_pos(marker),
     91 					def->amount, area_id) < 0) return -1;
     92 			continue;
     93 		}
     94 		if (def->kind != PH_MARKER_ENTITY && def->kind != PH_MARKER_PLAYER)
     95 			continue;
     96 		if (def->kind == PH_MARKER_PLAYER && *player >= 0) return -1;
     97 		entity = ph_world_spawn_entity(world, def->content_id,
     98 			ph_marker_pos(marker), def->territory_radius, area_id);
     99 		if (entity < 0) return -1;
    100 		if (def->kind == PH_MARKER_PLAYER) *player = entity;
    101 	}
    102 	return 0;
    103 }
    104 
    105 static PhAreaState *
    106 ph_world_area_state(PhWorld *world, int area_id)
    107 {
    108 	int low = 0;
    109 	int high = world->initialized_area_count;
    110 
    111 	while (low < high) {
    112 		int middle = low + (high - low) / 2;
    113 		if (world->area_states[middle].area_id < area_id) low = middle + 1;
    114 		else high = middle;
    115 	}
    116 	if (low < world->initialized_area_count &&
    117 			world->area_states[low].area_id == area_id) return &world->area_states[low];
    118 	return NULL;
    119 }
    120 
    121 static PhAreaState *
    122 ph_world_add_area_state(PhWorld *world, int area_id)
    123 {
    124 	PhAreaState *states;
    125 	int capacity;
    126 	int position = 0;
    127 
    128 	if (ph_world_area_state(world, area_id)) return NULL;
    129 	if (world->initialized_area_count >= world->area_state_capacity) {
    130 		capacity = world->area_state_capacity ? world->area_state_capacity * 2 : 8;
    131 		states = realloc(world->area_states, (size_t)capacity * sizeof(*states));
    132 		if (!states) return NULL;
    133 		world->area_states = states;
    134 		world->area_state_capacity = capacity;
    135 	}
    136 	while (position < world->initialized_area_count &&
    137 			world->area_states[position].area_id < area_id) ++position;
    138 	memmove(&world->area_states[position + 1], &world->area_states[position],
    139 		(size_t)(world->initialized_area_count - position) * sizeof(*states));
    140 	states = &world->area_states[position];
    141 	++world->initialized_area_count;
    142 	memset(states, 0, sizeof(*states));
    143 	states->area_id = area_id;
    144 	return states;
    145 }
    146 
    147 int
    148 ph_world_capture_area(PhWorld *world)
    149 {
    150 	PhAreaState *state;
    151 	PhEntity *entities = NULL;
    152 	PhGroundItem *items = NULL;
    153 	int entity_count = 0;
    154 	int item_count = 0;
    155 	int i;
    156 
    157 	if (!world) return -1;
    158 	state = ph_world_area_state(world, world->area_id);
    159 	if (!state && !(state = ph_world_add_area_state(world, world->area_id)))
    160 		return -1;
    161 	for (i = 0; i < world->entity_count; ++i)
    162 		if (i != world->player_index && world->entities[i].active &&
    163 				world->entities[i].area_id == world->area_id) ++entity_count;
    164 	for (i = 0; i < world->ground_item_count; ++i)
    165 		if (world->ground_items[i].active &&
    166 				world->ground_items[i].area_id == world->area_id) ++item_count;
    167 	if (entity_count && !(entities = malloc((size_t)entity_count * sizeof(*entities))))
    168 		return -1;
    169 	if (item_count && !(items = malloc((size_t)item_count * sizeof(*items)))) {
    170 		free(entities);
    171 		return -1;
    172 	}
    173 	entity_count = 0;
    174 	item_count = 0;
    175 	for (i = 0; i < world->entity_count; ++i)
    176 		if (i != world->player_index && world->entities[i].active &&
    177 				world->entities[i].area_id == world->area_id)
    178 			entities[entity_count++] = world->entities[i];
    179 	for (i = 0; i < world->ground_item_count; ++i)
    180 		if (world->ground_items[i].active &&
    181 				world->ground_items[i].area_id == world->area_id)
    182 			items[item_count++] = world->ground_items[i];
    183 	free(state->entities);
    184 	free(state->ground_items);
    185 	state->entities = entities;
    186 	state->ground_items = items;
    187 	state->entity_count = entity_count;
    188 	state->ground_item_count = item_count;
    189 	return 0;
    190 }
    191 
    192 static void
    193 ph_world_clear_area_content(PhWorld *world, int *player)
    194 {
    195 	PhEntity saved_player;
    196 	int has_player = world->player_index >= 0 &&
    197 		world->player_index < world->entity_count;
    198 
    199 	if (has_player) saved_player = world->entities[world->player_index];
    200 	memset(world->entities, 0, sizeof(world->entities));
    201 	memset(world->ground_items, 0, sizeof(world->ground_items));
    202 	world->entity_count = 0;
    203 	world->ground_item_count = 0;
    204 	world->player_index = -1;
    205 	*player = -1;
    206 	if (has_player) {
    207 		world->entities[0] = saved_player;
    208 		world->entity_count = 1;
    209 		world->player_index = *player = 0;
    210 	}
    211 }
    212 
    213 static int
    214 ph_world_restore_area(PhWorld *world, PhAreaState *state, int *player)
    215 {
    216 	if (!state || state->entity_count > PH_MAX_ENTITIES - world->entity_count ||
    217 			state->ground_item_count > PH_MAX_GROUND_ITEMS) return -1;
    218 	if (state->entity_count) {
    219 		memcpy(&world->entities[world->entity_count], state->entities,
    220 			(size_t)state->entity_count * sizeof(*state->entities));
    221 		world->entity_count += state->entity_count;
    222 	}
    223 	if (state->ground_item_count) {
    224 		memcpy(world->ground_items, state->ground_items,
    225 			(size_t)state->ground_item_count * sizeof(*state->ground_items));
    226 		world->ground_item_count = state->ground_item_count;
    227 	}
    228 	*player = world->player_index;
    229 	return 0;
    230 }
    231 
    232 int
    233 ph_world_prepare_area(PhWorld *world, const PhArea *area,
    234 	const PhAreaCatalog *catalog, int area_id, int *player)
    235 {
    236 	PhAreaState *state;
    237 	int entity_count = world->entity_count;
    238 	int ground_item_count = world->ground_item_count;
    239 	int old_player = *player;
    240 
    241 	state = ph_world_area_state(world, area_id);
    242 	if (state && area_id == world->area_id) return 0;
    243 	if (state) return ph_world_restore_area(world, state, player);
    244 	if (ph_world_add_area_content(world, area, catalog, area_id, player) < 0 ||
    245 			!ph_world_add_area_state(world, area_id)) {
    246 		world->entity_count = entity_count;
    247 		world->ground_item_count = ground_item_count;
    248 		*player = old_player;
    249 		return -1;
    250 	}
    251 	return 0;
    252 }
    253 
    254 int
    255 ph_world_update_portal(PhWorld *world, const PhAreaCatalog *catalog)
    256 {
    257 	const PhEntity *player = ph_world_player(world);
    258 	int tile_x;
    259 	int tile_y;
    260 	int i;
    261 
    262 	if (!player) return -1;
    263 	tile_x = (int)floorf(player->pos.x / PH_TILE_SIZE);
    264 	tile_y = (int)floorf(player->pos.y / PH_TILE_SIZE);
    265 	for (i = 0; i < world->area.marker_count; ++i) {
    266 		const PhAreaMarker *marker = &world->area.markers[i];
    267 		const PhMarkerDef *portal = ph_area_catalog_marker(catalog, world->area_id,
    268 			marker->symbol);
    269 		PhArea area;
    270 		PhVec2 destination;
    271 		int player_index = world->player_index;
    272 		int j;
    273 
    274 		if (!portal || portal->kind != PH_MARKER_PORTAL ||
    275 				marker->tile_x != tile_x || marker->tile_y != tile_y) continue;
    276 		if (ph_area_catalog_load(&area, catalog, portal->destination_area_id) < 0)
    277 			return -1;
    278 		for (j = 0; j < area.marker_count; ++j) {
    279 			const PhMarkerDef *arrival = ph_area_catalog_marker(catalog,
    280 				portal->destination_area_id, area.markers[j].symbol);
    281 
    282 			if (area.markers[j].symbol == portal->destination_symbol && arrival &&
    283 					arrival->kind == PH_MARKER_ARRIVAL) break;
    284 		}
    285 		if (j == area.marker_count || ph_world_capture_area(world) < 0) {
    286 			ph_area_free(&area);
    287 			return -1;
    288 		}
    289 		ph_world_clear_area_content(world, &player_index);
    290 		if (ph_world_prepare_area(world, &area, catalog,
    291 				portal->destination_area_id, &player_index) < 0) {
    292 			PhAreaState *old = ph_world_area_state(world, world->area_id);
    293 
    294 			ph_world_clear_area_content(world, &player_index);
    295 			ph_world_restore_area(world, old, &player_index);
    296 			ph_area_free(&area);
    297 			return -1;
    298 		}
    299 		destination = ph_marker_pos(&area.markers[j]);
    300 		ph_world_enter_area(world, area, portal->destination_area_id, destination);
    301 		return 1;
    302 	}
    303 	return 0;
    304 }