QUOTE: Be the change, make a difference.

player.c - freezo - A retro platform game

freezo

A retro platform game
git clone git@soophie.de:/srv/git/freezo
log | files | refs | readme

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 }