QUOTE: Let your heart guide you always.

freezo

A retro platform game

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
     11player_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
     35void 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
    433void 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
    467void player_free(player_t *player) {
    468  fz_timer_free(player->timer_walking);
    469  fz_timer_free(player->timer_sneaking);
    470  free(player);
    471}