player.c (15054B)
1 #include <stdlib.h> 2 #include <math.h> 3 4 #include "../include/player.h" 5 #include "../include/const.h" 6 #include "../include/tile.h" 7 #include "../include/util.h" 8 #include "../include/entity.h" 9 #include "../include/effect.h" 10 11 player_t *player_create(pos_t pos) { 12 player_t *player = malloc(sizeof(player_t)); 13 *player = (player_t) { 14 .pos = pos, 15 .health = PLAYER_MAX_HEALTH, 16 .max_health = PLAYER_MAX_HEALTH, 17 .on_ground = false, 18 .velocity = 0.0, 19 .gravity = 0.0, 20 .dir = 1, 21 .sneaking = false, 22 .shooting = false, 23 .damage_timer = 0, 24 .fall_height = 0.0, 25 .held_enemy = NULL, 26 .moving = false, 27 .timer_walking = fz_timer_create(9, 4), 28 .timer_sneaking = fz_timer_create(9, 8), 29 .target_entity = NULL, 30 .shooting_distance = PLAYER_SHOOTING_RANGE, 31 }; 32 return player; 33 } 34 35 void player_update(player_t *player, game_t *game) { 36 float move = 0.0; 37 bool go_down = false; 38 bool sneaking = false; 39 bool holding = false; 40 player->shooting = false; 41 player->moving = false; 42 tile_t *left_ground_tile = tile_get(game, player->pos.x + 0.2 * TILE_WIDTH, player->pos.y + PLAYER_HEIGHT + 1); 43 tile_t *right_ground_tile = tile_get(game, player->pos.x + PLAYER_WIDTH - 0.2 * TILE_WIDTH, player->pos.y + PLAYER_HEIGHT + 1); 44 // game over 45 if (player->health <= 0) { 46 game->defeat = true; 47 } 48 if (IsKeyDown(KEY_A) && !IsKeyDown(KEY_D)) { 49 move = -3.0; 50 player->dir = -1; 51 player->moving = true; 52 } 53 if (IsKeyDown(KEY_D) && !IsKeyDown(KEY_A)) { 54 move = 3.0; 55 player->dir = 1; 56 player->moving = true; 57 } 58 if (IsKeyDown(KEY_S)) { 59 sneaking = true; 60 } 61 if (IsKeyDown(KEY_S) && IsKeyDown(KEY_SPACE)) { 62 if ( 63 (left_ground_tile == NULL || !tile_solid(left_ground_tile)) && 64 (right_ground_tile == NULL || !tile_solid(right_ground_tile)) 65 ) { 66 go_down = true; 67 } 68 } 69 if (IsKeyPressed(KEY_SPACE)) { 70 if (player->on_ground && !player->sneaking && !go_down) { 71 player->velocity = 13.0; 72 } 73 } 74 if (IsKeyDown(KEY_ENTER)) { 75 player->shooting = true; 76 } 77 if (IsKeyPressed(KEY_W)) { 78 holding = true; 79 } 80 // detect on ground 81 bool on_ground = false; 82 if (!(player->velocity > 0.0)) { 83 if (!go_down) { 84 for (int i = 0; i < game->tiles_len; i++) { 85 tile_t *tile = game->tiles[i]; 86 if (tile_ground(tile)) { 87 if ( 88 player->pos.x + PLAYER_WIDTH > tile->pos.x && 89 player->pos.x < tile->pos.x + TILE_WIDTH 90 ) { 91 float tolerance = 4.0; 92 if (player->gravity > tolerance) { 93 tolerance = player->gravity; 94 } 95 if (fabs(player->pos.y + PLAYER_HEIGHT - tile->pos.y) < tolerance) { 96 player->pos.y = tile->pos.y - PLAYER_HEIGHT; 97 on_ground = true; 98 if (player->fall_height >= 1.5 * TILE_HEIGHT) { 99 effect_play(player->pos, EFFECT_FALL, game); 100 } 101 // fall damage 102 if (player->fall_height >= 6 * TILE_HEIGHT) { 103 if (player->damage_timer == 0) { 104 if (player->health > 0) { 105 player->health--; 106 player->damage_timer = 80; 107 player->velocity = 6.0; 108 if (player->held_enemy != NULL) { 109 player->held_enemy = NULL; 110 } 111 } 112 } 113 } 114 } 115 } 116 } 117 } 118 } 119 for (int i = 0; i < game->entities_len; i++) { 120 if (game->entities[i].type == ENTITY_ENEMY) { 121 enemy_t *enemy = game->entities[i].enemy; 122 if (enemy->frozen) { 123 if ( 124 player->pos.x + PLAYER_WIDTH > enemy->pos.x && 125 player->pos.x < enemy->pos.x + ENEMY_WIDTH 126 ) { 127 if (fabs(player->pos.y + PLAYER_HEIGHT - enemy->pos.y) < 4.0) { 128 player->pos.y = enemy->pos.y - PLAYER_HEIGHT; 129 on_ground = true; 130 if (player->sneaking && player->fall_height >= 2 * TILE_HEIGHT) { 131 effect_play(enemy->pos, EFFECT_BREAK, game); 132 effect_play(enemy->pos, EFFECT_PARTICLE, game); 133 enemy_kill(enemy, game); 134 i--; 135 player->velocity = 6.0; 136 } 137 } 138 } 139 } 140 } 141 } 142 } 143 // detect ceiling 144 if (player->velocity > 0.0) { 145 for (int i = 0; i < game->tiles_len; i++) { 146 tile_t *tile = game->tiles[i]; 147 if (tile_solid(tile)) { 148 if ( 149 player->pos.x + PLAYER_WIDTH - 0.2 * TILE_WIDTH > tile->pos.x && 150 player->pos.x + 0.2 * TILE_WIDTH < tile->pos.x + TILE_WIDTH 151 ) { 152 float tolerance = 4.0; 153 if (player->velocity > tolerance) { 154 tolerance = player->velocity; 155 } 156 if (fabs(player->pos.y - (tile->pos.y + 0.2 * TILE_HEIGHT)) < tolerance) { 157 player->pos.y = tile->pos.y + 0.2 * TILE_HEIGHT; 158 player->velocity = 0.0; 159 } 160 } 161 } 162 } 163 } 164 // detect wall 165 if (fabs(move) > 0.0) { 166 for (int i = 0; i < game->tiles_len; i++) { 167 tile_t *tile = game->tiles[i]; 168 if (tile_wall(tile)) { 169 if ( 170 player->pos.y + PLAYER_HEIGHT > tile->pos.y && 171 player->pos.y < tile->pos.y + TILE_HEIGHT 172 ) { 173 float tolerance = 4.0; 174 if (move > tolerance) { 175 tolerance = player->velocity; 176 } 177 if (player->dir > 0) { 178 if (fabs((player->pos.x + PLAYER_WIDTH - 0.2 * TILE_WIDTH) - tile->pos.x) < tolerance) { 179 player->pos.x = tile->pos.x - (PLAYER_WIDTH - 0.2 * TILE_WIDTH); 180 } 181 } 182 else { 183 if (fabs((player->pos.x + 0.2 * TILE_WIDTH) - (tile->pos.x + TILE_WIDTH)) < tolerance) { 184 player->pos.x = tile->pos.x + TILE_WIDTH - 0.2 * TILE_WIDTH; 185 } 186 } 187 } 188 } 189 } 190 } 191 player->on_ground = on_ground; 192 player->sneaking = sneaking; 193 // handle move 194 if (player->sneaking || player->shooting) { 195 player->pos.x += 0.5 * move; 196 } 197 else { 198 player->pos.x += move; 199 } 200 // handle map border 201 if (player->pos.x < 0.0) { 202 player->pos.x = 0.0; 203 } 204 if (player->pos.x + PLAYER_WIDTH > TILE_WIDTH * game->level->width) { 205 player->pos.x = TILE_WIDTH * game->level->width - PLAYER_WIDTH; 206 } 207 // handle jump 208 if (player->velocity > 0.0) { 209 player->pos.y -= player->velocity; 210 player->velocity -= 1.1; 211 } 212 if (player->on_ground) { 213 player->gravity = 0.0; 214 player->fall_height = 0.0; 215 } 216 else { 217 player->pos.y += player->gravity; 218 player->fall_height += player->gravity; 219 if (player->gravity < PLAYER_GRAVITY) { 220 player->gravity += 0.2; 221 } 222 else { 223 player->gravity = PLAYER_GRAVITY; 224 } 225 } 226 if (player->damage_timer > 0) { 227 player->damage_timer--; 228 } 229 // detect shooting 230 if (player->shooting) { 231 // determine target 232 float shooting_range = 2 * TILE_WIDTH; 233 rect_t shooting_rect = { 234 .x = player->dir > 0 ? player->pos.x + PLAYER_WIDTH : player->pos.x - shooting_range, 235 .y = player->sneaking ? player->pos.y + 8 * SCALE : player->pos.y + 7 * SCALE, 236 .width = shooting_range, 237 .height = SCALE, 238 }; 239 float closest_distance = shooting_range; 240 entity_t *closest_entity = NULL; 241 bool target_in_range = false; 242 for (int i = 0; i < game->entities_len; i++) { 243 entity_t *entity = &game->entities[i]; 244 // ignore held enemies 245 if (entity->type == ENTITY_ENEMY && player->held_enemy == entity->enemy) { 246 continue; 247 } 248 // ignore frozen gates 249 if (entity->type == ENTITY_GATE && entity->gate->frozen) { 250 continue; 251 } 252 // ignore non-bad entities 253 if (!entity->is_bad) { 254 continue; 255 } 256 // ignore bosses 257 if (entity->type == ENTITY_BOSS) { 258 continue; 259 } 260 rect_t entity_rect = entity_get_rect(entity); 261 if (rect_collide(shooting_rect, entity_rect)) { 262 float entity_x = entity_rect.x + entity_rect.width / 2.0; 263 float entity_distance = fabs(entity_x - (player->dir > 0 ? player->pos.x + PLAYER_WIDTH : player->pos.x)); 264 if (player->target_entity != NULL && player->target_entity == entity) { 265 target_in_range = true; 266 // if target entity set distance to that 267 player->shooting_distance = entity_distance; 268 } 269 if (entity_distance < closest_distance) { 270 closest_distance = entity_distance; 271 closest_entity = entity; 272 } 273 } 274 } 275 if (closest_entity != NULL) { 276 if (!target_in_range) { 277 // if no target entity use closest distance 278 player->shooting_distance = closest_distance; 279 player->target_entity = closest_entity; 280 } 281 } 282 else { 283 player->target_entity = NULL; 284 player->shooting_distance = PLAYER_SHOOTING_RANGE; 285 } 286 // detect shooting wall/solid 287 for (int i = 0; i < game->tiles_len; i++) { 288 tile_t *tile = game->tiles[i]; 289 if (tile_wall(tile) || tile_solid(tile)) { 290 rect_t tile_rect = { tile->pos.x, tile->pos.y, TILE_WIDTH, TILE_HEIGHT }; 291 if (rect_collide(shooting_rect, tile_rect)) { 292 if (player->dir > 0) { 293 // clear target if it stands behind a wall 294 if (player->target_entity == NULL) { 295 player->shooting_distance = tile->pos.x - (player->pos.x + PLAYER_WIDTH); 296 } 297 else { 298 rect_t rect = entity_get_rect(player->target_entity); 299 if (rect.x > tile->pos.x) { 300 player->target_entity = NULL; 301 player->shooting_distance = tile->pos.x - (player->pos.x + PLAYER_WIDTH); 302 } 303 } 304 } 305 else { 306 // clear target if it stands behind a wall 307 if (player->target_entity == NULL) { 308 player->shooting_distance = player->pos.x - (tile->pos.x + TILE_WIDTH); 309 } 310 else { 311 rect_t rect = entity_get_rect(player->target_entity); 312 if (rect.x + rect.width < tile->pos.x + TILE_WIDTH) { 313 player->target_entity = NULL; 314 player->shooting_distance = player->pos.x - (tile->pos.x + TILE_WIDTH); 315 } 316 } 317 } 318 } 319 } 320 } 321 } 322 else { 323 player->target_entity = NULL; 324 player->shooting_distance = PLAYER_SHOOTING_RANGE; 325 } 326 // detect holding 327 if (holding) { 328 bool picked_up = false; 329 for (int i = 0; i < game->entities_len; i++) { 330 if (game->entities[i].type == ENTITY_ENEMY) { 331 enemy_t *enemy = game->entities[i].enemy; 332 if (enemy->frozen) { 333 float tolerance = 0.0; 334 if ( 335 player->pos.x <= enemy->pos.x + ENEMY_WIDTH - tolerance && 336 player->pos.x + PLAYER_WIDTH >= enemy->pos.x + tolerance && 337 player->pos.y <= enemy->pos.y + ENEMY_HEIGHT - tolerance && 338 player->pos.y + PLAYER_HEIGHT >= enemy->pos.y + tolerance 339 ) { 340 if (player->held_enemy == NULL) { 341 player->held_enemy = enemy; 342 picked_up = true; 343 } 344 } 345 } 346 } 347 } 348 if (!picked_up && player->held_enemy != NULL) { 349 player->held_enemy = NULL; 350 } 351 } 352 if (player->held_enemy != NULL) { 353 player->held_enemy->pos = (pos_t) { 354 .x = player->pos.x, 355 .y = player->pos.y - (player->sneaking ? 0.3 : 0.5) * TILE_HEIGHT, 356 }; 357 } 358 // detect door entering 359 if (holding) { 360 rect_t player_rect = { player->pos.x, player->pos.y, PLAYER_WIDTH, PLAYER_HEIGHT }; 361 for (int i = 0; i < game->entities_len; i++) { 362 if (game->entities[i].type == ENTITY_DOOR) { 363 door_t *door = game->entities[i].door; 364 rect_t door_rect = entity_get_rect(&game->entities[i]); 365 if (rect_collide(player_rect, door_rect)) { 366 if (door_unlock(door, game)) { 367 game->door = door; 368 game->victory = true; 369 } 370 else { 371 PlaySound(game->assets.locked); 372 } 373 } 374 } 375 } 376 } 377 // detect damage 378 rect_t player_rect = { 379 .x = player->pos.x, 380 .y = player->pos.y, 381 .width = PLAYER_WIDTH, 382 .height = PLAYER_HEIGHT, 383 }; 384 for (int i = 0; i < game->entities_len; i++) { 385 entity_t *entity = &game->entities[i]; 386 if (entity->is_bad) { 387 // ignore gates 388 if (entity->type == ENTITY_GATE) { 389 continue; 390 } 391 // ignore frozen enemies 392 if (entity->type == ENTITY_ENEMY && entity->enemy->frozen) { 393 continue; 394 } 395 rect_t entity_rect = entity_get_rect(entity); 396 if (rect_collide_threshold(entity_rect, player_rect, PLAYER_WIDTH / 4.0)) { 397 if (player->damage_timer == 0) { 398 if (player->health > 0) { 399 player->health--; 400 player->damage_timer = 80; 401 player->velocity = 8.0; 402 if (player->pos.x + PLAYER_WIDTH / 2.0 > entity_rect.x + entity_rect.width / 2.0) { 403 player->pos.x += 20.0; 404 } 405 else { 406 player->pos.x -= 20.0; 407 } 408 if (player->held_enemy != NULL) { 409 player->held_enemy = NULL; 410 } 411 } 412 } 413 } 414 } 415 } 416 // fall out of map 417 if (player->pos.y > TILE_HEIGHT * game->level->height) { 418 if (player->health > 0) { 419 player->health = 0; 420 } 421 } 422 // update timer 423 if (player->on_ground && player->moving) { 424 fz_timer_update(player->timer_walking); 425 fz_timer_update(player->timer_sneaking); 426 } 427 else { 428 fz_timer_reset(player->timer_walking); 429 fz_timer_reset(player->timer_sneaking); 430 } 431 } 432 433 void player_draw(player_t *player, game_t *game) { 434 pos_t pos = pos_snap(player->pos); 435 int frame = (player->sneaking || player->shooting) 436 ? fz_timer_get(player->timer_sneaking) 437 : fz_timer_get(player->timer_walking); 438 if (player->damage_timer / 4 % 2 == 0) { 439 if (player->sneaking) { 440 if (player->dir > 0) { 441 DrawTextureRec(game->assets.entities, texture_rect(frame, 14, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE); 442 } 443 else { 444 DrawTextureRec(game->assets.entities, texture_rect(frame, 15, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE); 445 } 446 } 447 else { 448 if (player->dir > 0) { 449 DrawTextureRec(game->assets.entities, texture_rect(frame, 12, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE); 450 } 451 else { 452 DrawTextureRec(game->assets.entities, texture_rect(frame, 13, PLAYER_WIDTH, PLAYER_HEIGHT), pos, WHITE); 453 } 454 } 455 if (player->shooting) { 456 float y = player->sneaking ? pos.y + 8 * SCALE : pos.y + 7 * SCALE; 457 if (player->dir > 0) { 458 DrawRectangle(pos.x + PLAYER_WIDTH, y, player->shooting_distance, 1 * SCALE, BLUE); 459 } 460 else { 461 DrawRectangle(pos.x - player->shooting_distance, y, player->shooting_distance, 1 * SCALE, BLUE); 462 } 463 } 464 } 465 } 466 467 void player_free(player_t *player) { 468 fz_timer_free(player->timer_walking); 469 fz_timer_free(player->timer_sneaking); 470 free(player); 471 }