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 }