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