A retro platform game
git clone git://soophie.de/freezo
blob: 5c07aa04e1fb7824d832340041e90573292cfa94 (15054 bytes)[raw]
1
#include <stdlib.h>2
#include <math.h>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"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
}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 over45
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 ground81
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_WIDTH90
) {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 damage102
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_WIDTH126
) {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 ceiling144
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_WIDTH151
) {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 wall165
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_HEIGHT172
) {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 move194
if (player->sneaking || player->shooting) {195
player->pos.x += 0.5 * move;196
}197
else {198
player->pos.x += move;199
}200
// handle map border201
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 jump208
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 shooting230
if (player->shooting) {231
// determine target232
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 enemies245
if (entity->type == ENTITY_ENEMY && player->held_enemy == entity->enemy) {246
continue;247
}248
// ignore frozen gates249
if (entity->type == ENTITY_GATE && entity->gate->frozen) {250
continue;251
}252
// ignore non-bad entities253
if (!entity->is_bad) {254
continue;255
}256
// ignore bosses257
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 that267
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 distance278
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/solid287
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 wall294
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 wall307
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 holding327
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 + tolerance339
) {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 entering359
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 damage378
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 gates388
if (entity->type == ENTITY_GATE) {389
continue;390
}391
// ignore frozen enemies392
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 map417
if (player->pos.y > TILE_HEIGHT * game->level->height) {418
if (player->health > 0) {419
player->health = 0;420
}421
}422
// update timer423
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
}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
}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
}