2023-07-06 22:02:37 +02:00
|
|
|
#include "player.h"
|
2024-07-29 21:04:23 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
#include "config.h"
|
2023-08-16 19:25:24 +02:00
|
|
|
#include "dialogs.h"
|
2023-08-18 20:56:44 +02:00
|
|
|
#include "game.h"
|
2023-07-06 22:02:37 +02:00
|
|
|
#include "map.h"
|
2023-08-26 17:13:05 +02:00
|
|
|
#include "npc.h"
|
2024-07-29 21:04:23 +02:00
|
|
|
|
2023-07-07 14:20:13 +02:00
|
|
|
#include <gint/display.h>
|
2023-07-06 22:02:37 +02:00
|
|
|
|
2024-07-27 17:38:58 +02:00
|
|
|
extern bopti_image_t player_male_img;
|
|
|
|
extern bopti_image_t player_female_img;
|
2024-07-26 17:01:51 +02:00
|
|
|
extern bopti_image_t npc_male;
|
|
|
|
extern bopti_image_t npc_female;
|
|
|
|
extern bopti_image_t npc_milkman;
|
|
|
|
extern bopti_image_t npc_police;
|
|
|
|
extern bopti_image_t SGN_Icon_img;
|
|
|
|
extern bopti_image_t INFO_Icon_img;
|
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
const Face faces[FACES] = {{"MALE", &npc_male},
|
|
|
|
{"FEMALE", &npc_female},
|
|
|
|
{"MILKMAN", &npc_milkman},
|
|
|
|
{"POLICE", &npc_police}};
|
2024-07-26 17:01:51 +02:00
|
|
|
|
2023-07-08 15:55:06 +02:00
|
|
|
const char one_px_mov[8] = {
|
2024-07-29 11:36:11 +02:00
|
|
|
0, -1, /* Up */
|
|
|
|
0, 1, /* Down */
|
|
|
|
-1, 0, /* Left */
|
|
|
|
1, 0 /* Right */
|
2023-07-08 15:55:06 +02:00
|
|
|
};
|
2023-07-07 19:26:14 +02:00
|
|
|
|
2023-07-08 15:55:06 +02:00
|
|
|
/* TODO: Search for all hard tiles in the tileset. hard_tiles is a list of their
|
|
|
|
* IDs */
|
2023-07-09 22:02:59 +02:00
|
|
|
/* The speed of the player on the diffrent tiles in the walkable layer. */
|
|
|
|
#define WALKABLE_TILE_MAX 4
|
2024-07-29 11:36:11 +02:00
|
|
|
const short int walkable_speed[WALKABLE_TILE_MAX] = {SPEED, 0, PXSIZE, PXSIZE};
|
2023-07-09 22:02:59 +02:00
|
|
|
|
|
|
|
/* How much damage the player takes on the diffrent tiles in the walkable
|
|
|
|
* layer. */
|
2024-07-29 11:36:11 +02:00
|
|
|
const char damage_taken_walkable[WALKABLE_TILE_MAX] = {0, 0, 5, 0};
|
2023-07-06 22:02:37 +02:00
|
|
|
|
2023-07-08 15:55:06 +02:00
|
|
|
extern bopti_image_t demo_player_img;
|
2023-08-21 21:35:49 +02:00
|
|
|
|
2023-08-26 17:13:05 +02:00
|
|
|
extern NPC *npcRPG;
|
|
|
|
extern uint32_t nbNPC;
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
void player_draw(Game *game) {
|
|
|
|
Player *player = &game->player;
|
2024-07-29 11:36:11 +02:00
|
|
|
dimage(player->px - P_WIDTH / 2, player->py - P_HEIGHT / 2,
|
2024-07-27 17:38:58 +02:00
|
|
|
player->is_male ? &player_male_img : &player_female_img);
|
2023-07-07 14:20:13 +02:00
|
|
|
}
|
2023-07-06 22:02:37 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
void player_move(Game *game, Direction direction) {
|
2023-08-11 08:54:04 +02:00
|
|
|
Player *player = &game->player;
|
|
|
|
|
2023-07-08 15:55:06 +02:00
|
|
|
/* How this player movement will modify the player x and y. */
|
2023-07-09 22:02:59 +02:00
|
|
|
char dx, dy;
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-09 15:45:46 +02:00
|
|
|
/* If the player will collide with a hard tile or if the will go outside of
|
|
|
|
* the map. */
|
2024-07-29 11:36:11 +02:00
|
|
|
|
|
|
|
if(player_collision(game, direction, P_CENTER)) {
|
|
|
|
|
2023-07-09 13:37:49 +02:00
|
|
|
/* If the will collide with the center of the player. */
|
2024-07-29 11:36:11 +02:00
|
|
|
dx = one_px_mov[direction * 2] * player->speed;
|
|
|
|
dy = one_px_mov[direction * 2 + 1] * player->speed;
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
player_fix_position(game, dx, dy);
|
2024-07-29 11:36:11 +02:00
|
|
|
} else {
|
2024-07-29 21:08:55 +02:00
|
|
|
if(player_collision(game, direction, P_RIGHTDOWN) ||
|
|
|
|
player_collision(game, direction, P_LEFTUP)) {
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-09 13:37:49 +02:00
|
|
|
/* If the will collide with the edges of the player. */
|
2023-07-09 13:22:20 +02:00
|
|
|
/* I fix his position so he won't be partially in the tile. */
|
2023-07-09 13:37:49 +02:00
|
|
|
/* I invert dx and dy to fix the axis where he is not moving on. */
|
|
|
|
/* Do not replace dx==0 with !dx or dy==0 with !dy, it won't work!
|
|
|
|
*/
|
2024-07-29 11:36:11 +02:00
|
|
|
dx = one_px_mov[direction * 2] * player->speed;
|
|
|
|
dy = one_px_mov[direction * 2 + 1] * player->speed;
|
|
|
|
|
|
|
|
player_fix_position(game, dx == 0, dy == 0);
|
2023-07-09 13:22:20 +02:00
|
|
|
}
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-09 13:37:49 +02:00
|
|
|
/* If he won't collide with the center, so I just move him normally */
|
2024-07-29 11:36:11 +02:00
|
|
|
dx = one_px_mov[direction * 2] * player->speed;
|
|
|
|
dy = one_px_mov[direction * 2 + 1] * player->speed;
|
|
|
|
|
2023-07-08 15:55:06 +02:00
|
|
|
player->x += dx;
|
|
|
|
player->y += dy;
|
2023-07-07 14:50:30 +02:00
|
|
|
}
|
2023-08-14 18:40:36 +02:00
|
|
|
|
|
|
|
player->wx = game->map_level->xmin * PXSIZE + player->x;
|
|
|
|
player->wy = game->map_level->ymin * PXSIZE + player->y;
|
2023-07-06 22:02:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
void player_action(Game *game) {
|
2024-07-26 17:01:51 +02:00
|
|
|
register size_t i;
|
2024-07-25 13:07:19 +02:00
|
|
|
/* already doing something (action IS NOT with an NPC) */
|
2024-07-29 11:36:11 +02:00
|
|
|
if(game->player.isDoingAction)
|
|
|
|
return;
|
2023-08-16 19:25:24 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
if(game->player.canDoSomething && !game->player.isInteractingWithNPC) {
|
2024-07-25 13:07:19 +02:00
|
|
|
/* we can do something */
|
2023-08-16 23:12:16 +02:00
|
|
|
/* we indicate that the player is occupied */
|
2023-08-16 19:25:24 +02:00
|
|
|
game->player.isDoingAction = true;
|
|
|
|
|
2024-07-29 21:08:55 +02:00
|
|
|
ExtraData *currentData =
|
|
|
|
&game->map_level->extradata[game->player.whichAction];
|
2023-08-18 20:56:44 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
/* we use the correct image as per the class of the item */
|
2023-08-22 10:39:37 +02:00
|
|
|
|
2023-08-16 19:25:24 +02:00
|
|
|
bopti_image_t *face;
|
2024-07-29 11:36:11 +02:00
|
|
|
/* we use the correct image as per the class of the item */
|
2023-08-21 22:53:33 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
if(strcmp("INFO", currentData->type) == 0) {
|
2023-08-16 19:25:24 +02:00
|
|
|
face = &INFO_Icon_img;
|
2024-07-29 11:36:11 +02:00
|
|
|
} else if(strcmp("SGN", currentData->type) == 0) {
|
2023-08-16 19:36:31 +02:00
|
|
|
face = &SGN_Icon_img;
|
2024-07-29 11:36:11 +02:00
|
|
|
} else {
|
2024-07-26 17:01:51 +02:00
|
|
|
/* It's a NPC */
|
|
|
|
/* (Mibi88) TODO: Use string hash + strcmp if the hashes match for
|
|
|
|
* fast string comparison. */
|
|
|
|
face = NULL;
|
2024-07-29 11:36:11 +02:00
|
|
|
for(i = 0; i < FACES; i++) {
|
2024-07-27 17:20:06 +02:00
|
|
|
Face current_face = faces[i];
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!strcmp(current_face.name, currentData->face)) {
|
2024-07-26 17:01:51 +02:00
|
|
|
face = current_face.face;
|
|
|
|
}
|
|
|
|
}
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!face)
|
|
|
|
face = &npc_male;
|
2024-07-26 17:01:51 +02:00
|
|
|
}
|
2023-08-16 19:25:24 +02:00
|
|
|
|
2023-08-22 10:39:37 +02:00
|
|
|
uint32_t dialogStart = currentData->dialogID;
|
2023-08-18 20:56:44 +02:00
|
|
|
|
2024-07-25 13:07:19 +02:00
|
|
|
dialogs_initiate_sequence(game, face, dialogStart);
|
2023-08-18 20:56:44 +02:00
|
|
|
|
2023-08-16 23:12:16 +02:00
|
|
|
/* when done we release the occupied status of the player */
|
2023-08-16 19:36:31 +02:00
|
|
|
game->player.isDoingAction = false;
|
2024-07-29 21:08:55 +02:00
|
|
|
} else if(game->player.canDoSomething &&
|
|
|
|
game->player.isInteractingWithNPC) {
|
2024-07-25 13:07:19 +02:00
|
|
|
/* we can do something (action IS with an NPC) */
|
2023-08-26 17:13:05 +02:00
|
|
|
/* we indicate that the player is occupied */
|
|
|
|
game->player.isDoingAction = true;
|
|
|
|
|
|
|
|
NPC *currentNPC = &npcRPG[game->player.whichAction];
|
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
/* we use the correct image as per the class of the item */
|
2023-08-26 17:13:05 +02:00
|
|
|
|
2024-07-29 21:08:55 +02:00
|
|
|
ExtraData *currentData =
|
|
|
|
&game->map_level->extradata[game->player.whichAction];
|
2024-07-26 17:01:51 +02:00
|
|
|
bopti_image_t *face = &npc_male;
|
|
|
|
/* It's a NPC */
|
|
|
|
/* (Mibi88) TODO: Use string hash + strcmp if the hashes match for
|
2024-07-29 11:36:11 +02:00
|
|
|
* fast string comparison. */
|
2024-07-26 17:01:51 +02:00
|
|
|
face = NULL;
|
2024-07-29 11:36:11 +02:00
|
|
|
for(i = 0; i < FACES; i++) {
|
2024-07-27 17:20:06 +02:00
|
|
|
Face current_face = faces[i];
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!strcmp(current_face.name, currentNPC->face)) {
|
2024-07-26 17:01:51 +02:00
|
|
|
face = current_face.face;
|
|
|
|
}
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!face)
|
|
|
|
face = &npc_male;
|
2024-07-26 17:01:51 +02:00
|
|
|
}
|
|
|
|
dtext(2, 64, C_BLACK, currentData->type);
|
2023-08-26 17:13:05 +02:00
|
|
|
uint32_t dialogStart = currentNPC->dialogID;
|
|
|
|
|
2024-07-25 13:07:19 +02:00
|
|
|
/* we set this NPC to paused to avoid changing its position while
|
|
|
|
* talking (the rest of the NPCs pursue their action) */
|
2023-08-26 17:13:05 +02:00
|
|
|
currentNPC->paused = true;
|
|
|
|
|
2024-07-25 13:07:19 +02:00
|
|
|
dialogs_initiate_sequence(game, face, dialogStart);
|
2023-08-26 17:13:05 +02:00
|
|
|
|
|
|
|
/* when done we release the occupied status of the player */
|
|
|
|
game->player.isDoingAction = false;
|
|
|
|
|
|
|
|
currentNPC->paused = false;
|
|
|
|
}
|
2023-07-06 22:02:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
bool player_collision(Game *game, Direction direction,
|
2024-07-29 11:36:11 +02:00
|
|
|
Checkpos nomov_axis_check) {
|
2023-08-11 08:54:04 +02:00
|
|
|
|
|
|
|
Player *player = &game->player;
|
|
|
|
|
2023-07-08 16:57:04 +02:00
|
|
|
/* Where is the tile where he will go to from his position. */
|
2024-07-29 11:36:11 +02:00
|
|
|
char dx = one_px_mov[direction * 2];
|
|
|
|
char dy = one_px_mov[direction * 2 + 1];
|
2023-08-14 18:40:36 +02:00
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!dx) {
|
2023-07-09 13:22:20 +02:00
|
|
|
dx += nomov_axis_check;
|
2024-07-29 11:36:11 +02:00
|
|
|
} else if(!dy) {
|
2023-07-09 13:22:20 +02:00
|
|
|
dy += nomov_axis_check;
|
|
|
|
}
|
2024-07-29 11:36:11 +02:00
|
|
|
|
|
|
|
dx = dx * (P_WIDTH / 2 + 1);
|
|
|
|
dy = dy * (P_HEIGHT / 2 + 1);
|
|
|
|
|
2023-07-08 16:57:04 +02:00
|
|
|
/* The tile he will go to. */
|
2024-07-29 11:36:11 +02:00
|
|
|
int player_tile_x = player->x + dx;
|
|
|
|
int player_tile_y = player->y + dy;
|
|
|
|
|
2023-08-14 20:46:51 +02:00
|
|
|
/* check where the player is expected to go on the next move */
|
|
|
|
/* if outside the map, we check if there is a map on the other */
|
|
|
|
/* side of the current map*/
|
2024-07-29 11:36:11 +02:00
|
|
|
if(map_get_walkable(game, player_tile_x, player_tile_y) == MAP_OUTSIDE) {
|
2023-08-14 20:46:51 +02:00
|
|
|
// we compute the expected world coordinates accordingly
|
|
|
|
// while taking care of the scaling between fx and cg models (PXSIZE)
|
2024-07-29 11:36:11 +02:00
|
|
|
int worldX = (player->wx + dx) / PXSIZE;
|
|
|
|
int worldY = (player->wy + dy) / PXSIZE;
|
2024-07-25 13:07:19 +02:00
|
|
|
Map *map = map_get_for_coordinates(game, worldX, worldY);
|
2024-07-29 11:36:11 +02:00
|
|
|
if(map != NULL && map != game->map_level) {
|
2023-08-14 21:15:38 +02:00
|
|
|
Map *backupmap = game->map_level;
|
|
|
|
int backupx = player->x;
|
|
|
|
int backupy = player->y;
|
|
|
|
int backupwx = player->wx;
|
|
|
|
int backupwy = player->wy;
|
|
|
|
|
2023-08-14 20:46:51 +02:00
|
|
|
game->map_level = map;
|
|
|
|
|
|
|
|
player->wx = worldX * PXSIZE;
|
|
|
|
player->wy = worldY * PXSIZE;
|
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
player->x = (worldX - map->xmin) * PXSIZE;
|
|
|
|
player->y = (worldY - map->ymin) * PXSIZE;
|
|
|
|
|
|
|
|
int on_walkable = map_get_walkable(game, player->x / T_WIDTH,
|
|
|
|
player->y / T_HEIGHT);
|
|
|
|
|
|
|
|
int speed = (on_walkable >= 0 && on_walkable < WALKABLE_TILE_MAX)
|
|
|
|
? walkable_speed[on_walkable]
|
|
|
|
: 0;
|
2023-08-14 20:46:51 +02:00
|
|
|
|
2023-08-14 21:15:38 +02:00
|
|
|
/* if he's on a hard tile and we need to revert the changes as */
|
|
|
|
/* tile on the next side of the border is not walkable */
|
|
|
|
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!speed) {
|
2023-08-14 21:15:38 +02:00
|
|
|
game->map_level = backupmap;
|
|
|
|
player->x = backupx;
|
|
|
|
player->y = backupy;
|
|
|
|
player->wx = backupwx;
|
|
|
|
player->wy = backupwy;
|
|
|
|
return true; /* He will collide with it. */
|
|
|
|
}
|
|
|
|
|
2023-08-26 17:13:05 +02:00
|
|
|
/* we update the list of NPCs in the current map */
|
|
|
|
/* to follow the trajectories */
|
2024-07-25 13:07:19 +02:00
|
|
|
reload_npc(game);
|
2023-08-26 17:13:05 +02:00
|
|
|
|
2023-08-14 20:46:51 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle a negative position differently than a positive one. */
|
2024-07-29 11:36:11 +02:00
|
|
|
if(player_tile_x < 0)
|
|
|
|
player_tile_x = player_tile_x / T_WIDTH - 1;
|
|
|
|
else
|
|
|
|
player_tile_x = player_tile_x / T_WIDTH;
|
|
|
|
|
|
|
|
if(player_tile_y < 0)
|
|
|
|
player_tile_y = player_tile_y / T_HEIGHT - 1;
|
|
|
|
else
|
|
|
|
player_tile_y = player_tile_y / T_HEIGHT;
|
|
|
|
|
2024-07-25 13:07:19 +02:00
|
|
|
int on_walkable = map_get_walkable(game, player_tile_x, player_tile_y);
|
2024-07-29 11:36:11 +02:00
|
|
|
|
|
|
|
int speed = (on_walkable >= 0 && on_walkable < WALKABLE_TILE_MAX)
|
|
|
|
? walkable_speed[on_walkable]
|
|
|
|
: 0;
|
|
|
|
|
2023-07-09 22:02:59 +02:00
|
|
|
/* if he's on a hard tile */
|
2024-07-29 11:36:11 +02:00
|
|
|
if(!speed) {
|
2023-07-09 22:02:59 +02:00
|
|
|
return true; /* He will collide with it. */
|
2023-07-07 14:50:30 +02:00
|
|
|
}
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-09 22:02:59 +02:00
|
|
|
player->speed = speed;
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-08 16:57:04 +02:00
|
|
|
return false; /* He won't collide with a hard tile. */
|
2023-07-06 22:02:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
void player_fix_position(Game *game, bool fix_x, bool fix_y) {
|
|
|
|
|
|
|
|
Player *player = &game->player;
|
2024-07-29 11:36:11 +02:00
|
|
|
|
2023-07-08 16:57:04 +02:00
|
|
|
/* I fix his poition on x or/and on y if y need to, so that he won't be over
|
|
|
|
* the hard tile that he collided with. */
|
2024-07-29 11:36:11 +02:00
|
|
|
if(fix_x)
|
|
|
|
player->x = player->x / T_WIDTH * T_WIDTH + P_WIDTH / 2;
|
|
|
|
|
|
|
|
if(fix_y)
|
|
|
|
player->y = player->y / T_HEIGHT * T_HEIGHT + P_HEIGHT / 2;
|
2023-07-06 22:02:37 +02:00
|
|
|
}
|
|
|
|
|
2023-08-11 08:54:04 +02:00
|
|
|
void player_damage(Game *game, int amount) {
|
|
|
|
|
|
|
|
Player *player = &game->player;
|
2024-07-29 11:36:11 +02:00
|
|
|
|
|
|
|
player->life -= amount;
|
2023-07-09 22:02:59 +02:00
|
|
|
/* TODO: Let the player dye if life < 1. */
|
|
|
|
};
|