phantasia

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

area.c (9380B)


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