so://phie

freezo

A retro platform game
git clone git://soophie.de/freezo

blob: 5c07aa04e1fb7824d832340041e90573292cfa94 (15054 bytes)[raw]

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}