phantasia

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

save.c (11404B)


      1 #define _POSIX_C_SOURCE 200809L
      2 
      3 #include "engine/save.h"
      4 
      5 #include <errno.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <unistd.h>
     11 
     12 enum { PH_SAVE_MAGIC = 0x50485356, PH_SAVE_RECORD_LIMIT = 1000000 };
     13 
     14 static int
     15 ph_write_u32(FILE *fp, uint32_t value)
     16 {
     17 	unsigned char bytes[4] = {
     18 		(unsigned char)value, (unsigned char)(value >> 8),
     19 		(unsigned char)(value >> 16), (unsigned char)(value >> 24),
     20 	};
     21 	return fwrite(bytes, 1, sizeof(bytes), fp) == sizeof(bytes) ? 0 : -1;
     22 }
     23 
     24 static int
     25 ph_read_u32(FILE *fp, uint32_t *value)
     26 {
     27 	unsigned char bytes[4];
     28 
     29 	if (fread(bytes, 1, sizeof(bytes), fp) != sizeof(bytes)) return -1;
     30 	*value = (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 |
     31 		(uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24;
     32 	return 0;
     33 }
     34 
     35 static int ph_write_int(FILE *fp, int value) { return ph_write_u32(fp, (uint32_t)value); }
     36 
     37 static int
     38 ph_read_int(FILE *fp, int *value)
     39 {
     40 	uint32_t raw;
     41 	if (ph_read_u32(fp, &raw) < 0) return -1;
     42 	*value = (int)(int32_t)raw;
     43 	return 0;
     44 }
     45 
     46 static int
     47 ph_write_float(FILE *fp, float value)
     48 {
     49 	uint32_t raw;
     50 	memcpy(&raw, &value, sizeof(raw));
     51 	return ph_write_u32(fp, raw);
     52 }
     53 
     54 static int
     55 ph_read_float(FILE *fp, float *value)
     56 {
     57 	uint32_t raw;
     58 	if (ph_read_u32(fp, &raw) < 0) return -1;
     59 	memcpy(value, &raw, sizeof(raw));
     60 	return 0;
     61 }
     62 
     63 static int
     64 ph_write_vec(FILE *fp, PhVec2 value)
     65 {
     66 	return ph_write_float(fp, value.x) < 0 || ph_write_float(fp, value.y) < 0 ? -1 : 0;
     67 }
     68 
     69 static int
     70 ph_read_vec(FILE *fp, PhVec2 *value)
     71 {
     72 	return ph_read_float(fp, &value->x) < 0 || ph_read_float(fp, &value->y) < 0 ? -1 : 0;
     73 }
     74 
     75 static int
     76 ph_write_entity(FILE *fp, const PhEntity *entity)
     77 {
     78 	int i;
     79 
     80 	if (ph_write_int(fp, entity->type_id) < 0 || ph_write_vec(fp, entity->pos) < 0 ||
     81 			ph_write_vec(fp, entity->motion.target) < 0 ||
     82 			ph_write_float(fp, entity->motion.credit) < 0 ||
     83 			ph_write_float(fp, entity->motion.animation_distance) < 0 ||
     84 			ph_write_int(fp, entity->motion.facing_x) < 0 ||
     85 			ph_write_int(fp, entity->motion.facing_y) < 0 ||
     86 			ph_write_int(fp, entity->motion.moving) < 0 ||
     87 			ph_write_vec(fp, entity->behavior.home) < 0 ||
     88 			ph_write_vec(fp, entity->behavior.last_seen) < 0 ||
     89 			ph_write_int(fp, entity->behavior.territory_radius) < 0 ||
     90 			ph_write_int(fp, entity->behavior.chase_turns) < 0 ||
     91 			ph_write_int(fp, entity->vitals.hp) < 0 ||
     92 			ph_write_int(fp, entity->vitals.mp) < 0 ||
     93 			ph_write_int(fp, entity->xp) < 0 || ph_write_int(fp, entity->gold) < 0 ||
     94 			ph_write_int(fp, entity->accuracy.value) < 0 ||
     95 			ph_write_int(fp, entity->accuracy.turns) < 0) return -1;
     96 	for (i = 0; i < PH_EQUIP_COUNT; ++i)
     97 		if (ph_write_int(fp, entity->equipment[i]) < 0) return -1;
     98 	return 0;
     99 }
    100 
    101 static int
    102 ph_read_entity(FILE *fp, PhEntity *entity, int area_id)
    103 {
    104 	int i;
    105 
    106 	memset(entity, 0, sizeof(*entity));
    107 	if (ph_read_int(fp, &entity->type_id) < 0 || ph_read_vec(fp, &entity->pos) < 0 ||
    108 			ph_read_vec(fp, &entity->motion.target) < 0 ||
    109 			ph_read_float(fp, &entity->motion.credit) < 0 ||
    110 			ph_read_float(fp, &entity->motion.animation_distance) < 0 ||
    111 			ph_read_int(fp, &entity->motion.facing_x) < 0 ||
    112 			ph_read_int(fp, &entity->motion.facing_y) < 0 ||
    113 			ph_read_int(fp, &entity->motion.moving) < 0 ||
    114 			ph_read_vec(fp, &entity->behavior.home) < 0 ||
    115 			ph_read_vec(fp, &entity->behavior.last_seen) < 0 ||
    116 			ph_read_int(fp, &entity->behavior.territory_radius) < 0 ||
    117 			ph_read_int(fp, &entity->behavior.chase_turns) < 0 ||
    118 			ph_read_int(fp, &entity->vitals.hp) < 0 ||
    119 			ph_read_int(fp, &entity->vitals.mp) < 0 ||
    120 			ph_read_int(fp, &entity->xp) < 0 || ph_read_int(fp, &entity->gold) < 0 ||
    121 			ph_read_int(fp, &entity->accuracy.value) < 0 ||
    122 			ph_read_int(fp, &entity->accuracy.turns) < 0) return -1;
    123 	for (i = 0; i < PH_EQUIP_COUNT; ++i)
    124 		if (ph_read_int(fp, &entity->equipment[i]) < 0) return -1;
    125 	entity->area_id = area_id;
    126 	entity->active = 1;
    127 	return 0;
    128 }
    129 
    130 static int
    131 ph_write_item(FILE *fp, const PhGroundItem *item)
    132 {
    133 	return ph_write_int(fp, item->item_id) < 0 || ph_write_vec(fp, item->pos) < 0 ||
    134 		ph_write_int(fp, item->amount) < 0 ? -1 : 0;
    135 }
    136 
    137 static int
    138 ph_read_item(FILE *fp, PhGroundItem *item, int area_id)
    139 {
    140 	memset(item, 0, sizeof(*item));
    141 	if (ph_read_int(fp, &item->item_id) < 0 || ph_read_vec(fp, &item->pos) < 0 ||
    142 			ph_read_int(fp, &item->amount) < 0) return -1;
    143 	item->area_id = area_id;
    144 	item->active = 1;
    145 	return 0;
    146 }
    147 
    148 static int
    149 ph_save_stream(FILE *fp, PhWorld *world, const PhSpellBook *spells)
    150 {
    151 	const PhEntity *player = ph_world_player(world);
    152 	int inventory_count = 0;
    153 	int i;
    154 	int j;
    155 
    156 	if (!player || ph_world_capture_area(world) < 0) return -1;
    157 	for (i = 0; i < world->content.item_count; ++i)
    158 		if (world->inventory[i] > 0) ++inventory_count;
    159 	if (ph_write_u32(fp, PH_SAVE_MAGIC) < 0 || ph_write_u32(fp, PH_SAVE_VERSION) < 0 ||
    160 			ph_write_int(fp, world->area_id) < 0 || ph_write_entity(fp, player) < 0 ||
    161 			ph_write_int(fp, inventory_count) < 0) return -1;
    162 	for (i = 0; i < world->content.item_count; ++i) {
    163 		if (world->inventory[i] <= 0) continue;
    164 		if (ph_write_int(fp, world->content.items[i].id) < 0 ||
    165 				ph_write_int(fp, world->inventory[i]) < 0) return -1;
    166 	}
    167 	if (ph_write_int(fp, spells->learned_count) < 0) return -1;
    168 	for (i = 0; i < spells->learned_count; ++i)
    169 		if (ph_write_int(fp, spells->learned[i]) < 0) return -1;
    170 	if (ph_write_int(fp, world->initialized_area_count) < 0) return -1;
    171 	for (i = 0; i < world->initialized_area_count; ++i) {
    172 		const PhAreaState *state = &world->area_states[i];
    173 
    174 		if (ph_write_int(fp, state->area_id) < 0 ||
    175 				ph_write_int(fp, state->entity_count) < 0 ||
    176 				ph_write_int(fp, state->ground_item_count) < 0) return -1;
    177 		for (j = 0; j < state->entity_count; ++j)
    178 			if (ph_write_entity(fp, &state->entities[j]) < 0) return -1;
    179 		for (j = 0; j < state->ground_item_count; ++j)
    180 			if (ph_write_item(fp, &state->ground_items[j]) < 0) return -1;
    181 	}
    182 	return 0;
    183 }
    184 
    185 int
    186 ph_save_write(const char *path, PhWorld *world, const PhSpellBook *spells)
    187 {
    188 	FILE *fp;
    189 	char *temporary;
    190 	size_t length;
    191 	int result = -1;
    192 
    193 	if (!path || !world || world->combat_active || !spells ||
    194 			spells->learned_count < 0 || spells->learned_count > PH_SPELL_MAX)
    195 		return -1;
    196 	length = strlen(path);
    197 	temporary = malloc(length + 5);
    198 	if (!temporary) return -1;
    199 	memcpy(temporary, path, length);
    200 	memcpy(temporary + length, ".tmp", 5);
    201 	fp = fopen(temporary, "wb");
    202 	if (!fp) goto done;
    203 	if (ph_save_stream(fp, world, spells) == 0 && fflush(fp) == 0 &&
    204 			fsync(fileno(fp)) == 0) result = 0;
    205 	if (fclose(fp) != 0) result = -1;
    206 	fp = NULL;
    207 	if (result == 0 && rename(temporary, path) != 0) result = -1;
    208 	if (result < 0) remove(temporary);
    209 done:
    210 	free(temporary);
    211 	return result;
    212 }
    213 
    214 static int
    215 ph_item_index(const PhWorld *world, int item_id)
    216 {
    217 	int i;
    218 	for (i = 0; i < world->content.item_count; ++i)
    219 		if (world->content.items[i].id == item_id) return i;
    220 	return -1;
    221 }
    222 
    223 static int
    224 ph_area_known(const PhAreaCatalog *catalog, int area_id)
    225 {
    226 	return ph_area_catalog_area(catalog, area_id) != NULL;
    227 }
    228 
    229 static int
    230 ph_load_stream(FILE *fp, PhWorld *world, const PhWorldSetup *setup,
    231 	PhSpellBook *spells, float viewport_w, float viewport_h)
    232 {
    233 	uint32_t magic;
    234 	uint32_t version;
    235 	PhArea area;
    236 	PhEntity player;
    237 	const PhEntityDef *player_def;
    238 	int area_id;
    239 	int count;
    240 	int i;
    241 	int j;
    242 
    243 	if (ph_read_u32(fp, &magic) < 0 || ph_read_u32(fp, &version) < 0 ||
    244 			magic != PH_SAVE_MAGIC || version != PH_SAVE_VERSION ||
    245 			ph_read_int(fp, &area_id) < 0 || !ph_area_known(setup->areas, area_id) ||
    246 			ph_read_entity(fp, &player, area_id) < 0 ||
    247 			ph_area_catalog_load(&area, setup->areas, area_id) < 0) return -1;
    248 	if (ph_world_init(world, area, area_id, setup->content, viewport_w, viewport_h) < 0) {
    249 		ph_area_free(&area);
    250 		return -1;
    251 	}
    252 	player_def = ph_world_entity_def(world, player.type_id);
    253 	if (!player_def || player_def->kind != PH_ENTITY_PLAYER) goto fail;
    254 	world->entities[0] = player;
    255 	world->entity_count = 1;
    256 	world->player_index = 0;
    257 	if (ph_read_int(fp, &count) < 0 || count < 0 || count > world->content.item_count)
    258 		goto fail;
    259 	for (i = 0; i < count; ++i) {
    260 		int item_id;
    261 		int amount;
    262 		int index;
    263 		if (ph_read_int(fp, &item_id) < 0 || ph_read_int(fp, &amount) < 0 ||
    264 				amount <= 0 || (index = ph_item_index(world, item_id)) < 0 ||
    265 				world->inventory[index] != 0) goto fail;
    266 		world->inventory[index] = amount;
    267 	}
    268 	if (ph_read_int(fp, &count) < 0 || count < 0 || count > PH_SPELL_MAX) goto fail;
    269 	spells->learned_count = 0;
    270 	for (i = 0; i < count; ++i) {
    271 		int spell_id;
    272 		if (ph_read_int(fp, &spell_id) < 0 ||
    273 				ph_spellbook_learn(spells, spell_id) < 0) goto fail;
    274 	}
    275 	if (ph_read_int(fp, &count) < 0 || count <= 0 || count > PH_SAVE_RECORD_LIMIT)
    276 		goto fail;
    277 	world->area_states = calloc((size_t)count, sizeof(*world->area_states));
    278 	if (!world->area_states) goto fail;
    279 	world->area_state_capacity = count;
    280 	for (i = 0; i < count; ++i) {
    281 		PhAreaState *state = &world->area_states[i];
    282 		if (ph_read_int(fp, &state->area_id) < 0 ||
    283 				!ph_area_known(setup->areas, state->area_id) ||
    284 				ph_read_int(fp, &state->entity_count) < 0 || state->entity_count < 0 ||
    285 				state->entity_count > PH_MAX_ENTITIES - 1 ||
    286 				ph_read_int(fp, &state->ground_item_count) < 0 ||
    287 				state->ground_item_count < 0 ||
    288 				state->ground_item_count > PH_MAX_GROUND_ITEMS) goto fail;
    289 		if (i > 0 && world->area_states[i - 1].area_id >= state->area_id) goto fail;
    290 		world->initialized_area_count = i + 1;
    291 		if (state->entity_count) {
    292 			state->entities = calloc((size_t)state->entity_count, sizeof(*state->entities));
    293 			if (!state->entities) goto fail;
    294 		}
    295 		if (state->ground_item_count) {
    296 			state->ground_items = calloc((size_t)state->ground_item_count,
    297 				sizeof(*state->ground_items));
    298 			if (!state->ground_items) goto fail;
    299 		}
    300 		for (j = 0; j < state->entity_count; ++j)
    301 			if (ph_read_entity(fp, &state->entities[j], state->area_id) < 0 ||
    302 					!ph_world_entity_def(world, state->entities[j].type_id)) goto fail;
    303 		for (j = 0; j < state->ground_item_count; ++j)
    304 			if (ph_read_item(fp, &state->ground_items[j], state->area_id) < 0 ||
    305 					!ph_world_item_def(world, state->ground_items[j].item_id) ||
    306 					state->ground_items[j].amount <= 0) goto fail;
    307 	}
    308 	for (i = 0; i < world->initialized_area_count; ++i)
    309 		if (world->area_states[i].area_id == area_id) break;
    310 	if (i == world->initialized_area_count) goto fail;
    311 	if (world->area_states[i].entity_count) {
    312 		memcpy(&world->entities[1], world->area_states[i].entities,
    313 			(size_t)world->area_states[i].entity_count * sizeof(*world->entities));
    314 		world->entity_count += world->area_states[i].entity_count;
    315 	}
    316 	if (world->area_states[i].ground_item_count) {
    317 		memcpy(world->ground_items, world->area_states[i].ground_items,
    318 			(size_t)world->area_states[i].ground_item_count * sizeof(*world->ground_items));
    319 		world->ground_item_count = world->area_states[i].ground_item_count;
    320 	}
    321 	ph_world_set_player(world, 0);
    322 	return 0;
    323 
    324 fail:
    325 	ph_world_destroy(world);
    326 	return -1;
    327 }
    328 
    329 int
    330 ph_save_load(const char *path, PhWorld *world, const PhWorldSetup *setup,
    331 	PhSpellBook *spells, float viewport_w, float viewport_h)
    332 {
    333 	FILE *fp;
    334 	int result;
    335 
    336 	if (!path || !world || !setup || !setup->areas || !spells) return -1;
    337 	fp = fopen(path, "rb");
    338 	if (!fp) return errno == ENOENT ? 0 : -1;
    339 	result = ph_load_stream(fp, world, setup, spells, viewport_w, viewport_h);
    340 	if (fclose(fp) != 0) {
    341 		if (result == 0) ph_world_destroy(world);
    342 		result = -1;
    343 	}
    344 	return result < 0 ? -1 : 1;
    345 }