Merge pull request 'master' (#1) from Slyvtt/Collab_RPG:master into master

Reviewed-on: https://gitea.planet-casio.com/Fcalva/Collab_RPG_Fcalva/pulls/1
This commit is contained in:
Fcalva 2023-09-17 13:40:46 +02:00
commit baf40d040e
13 changed files with 290 additions and 21 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@ __pycache__/
.vscode .vscode
level*.json level*.json
tilesetnpp.json

View file

@ -13,11 +13,13 @@ A ce stade, on a déjà implémenté :
- [x] Multiple cartes avec importation automatique des fichiers `world` issus de Tiled - [x] Multiple cartes avec importation automatique des fichiers `world` issus de Tiled
- [x] Carte Multilayer (Background, Foreground + accessibilité / Dommages) avec transparence du calque Foreground - [x] Carte Multilayer (Background, Foreground + accessibilité / Dommages) avec transparence du calque Foreground
- [x] Personnage - [x] Personnage
- [x] Dialogues (sauts de lignes et mots plus grands que l'écran pas supportés) - [x] Dialogues avec fichiers externes `json` et séquenceage possible de ceux-ci via un arbre d'histoire (sauts de lignes et mots plus grands que l'écran pas supportés)
- [ ] Fontes de caractères - [x] Fontes de caractères
- [ ] Interaction - [x] Interaction
- [ ] NPC - [ ] NPC
- [x] Changement de map durant le jeu - [x] Changement de map durant le jeu
- [ ] Système d'événements
## Crédits ## Crédits

View file

@ -2,5 +2,4 @@
[OK] - corriger les tuiles 1bit pour les rivières (tiles 1 2 3 et 4) avec niveaux de diphering pour tiles 1 et 3 (gris 33% et 66%) [OK] - corriger les tuiles 1bit pour les rivières (tiles 1 2 3 et 4) avec niveaux de diphering pour tiles 1 et 3 (gris 33% et 66%)
[OK] - ajouter face pour dialogue pour PNJ [OK] - ajouter face pour dialogue pour PNJ
[OK] - ajouter indicateur visuel pour action joueur quand on est proche d'un PNJ ou d'un panneau [OK] - ajouter indicateur visuel pour action joueur quand on est proche d'un PNJ ou d'un panneau
[OK] - faire des fonts lisibles avec le set `print` disponible intégralement
- faire des fonts lisibles avec le set `print` disponible intégralement

View file

@ -66,7 +66,7 @@
}, },
{ {
"ID":6, "ID":6,
"dialog":"On dit qu'il se passe des choses étranges par ici depuis quelques temps. Fais bien attention a Toi.", "dialog":"On dit qu'il se passe des choses etranges par ici depuis quelques temps. Fais bien attention a Toi.",
"isQuestion":0, "isQuestion":0,
"choice":"_", "choice":"_",
"conclusion1":"_", "conclusion1":"_",
@ -80,7 +80,7 @@
"dialog":"Salut Hero, je suis le cremier. Veux tu me delester un peu ?", "dialog":"Salut Hero, je suis le cremier. Veux tu me delester un peu ?",
"isQuestion":1, "isQuestion":1,
"choice":"Oui$Non", "choice":"Oui$Non",
"conclusion1":"Voici donc pour toi`$life+5``$mana+5``$power+2`", "conclusion1":"Voici donc pour toi.`$life+5``$mana+5``$power+2`",
"next1":-1, "next1":-1,
"conclusion2":"Bon bah casse toi ...", "conclusion2":"Bon bah casse toi ...",
"next2":-1, "next2":-1,
@ -110,7 +110,7 @@
}, },
{ {
"ID":10, "ID":10,
"dialog":"Et sa tombe est en train d'etre creusee ?", "dialog":"Et une tombe est en train d'etre creusee ?",
"isQuestion":0, "isQuestion":0,
"choice":"_", "choice":"_",
"conclusion1":"_", "conclusion1":"_",
@ -129,6 +129,17 @@
"conclusion2":"_", "conclusion2":"_",
"next2":-1, "next2":-1,
"nextOther":-1 "nextOther":-1
},
{
"ID":12,
"dialog":"J'attends mon fils pour dejeuner a la taverne ... Il est toujours en retard !!",
"isQuestion":0,
"choice":"_",
"conclusion1":"_",
"next1":-1,
"conclusion2":"_",
"next2":-1,
"nextOther":-1
} }
] ]
} }

View file

@ -13,7 +13,7 @@
"ID":1, "ID":1,
"dialog":"Salut, je suis le gardien du Tombeau. As-tu remarque quelque chose d'etrange en venant ici ?", "dialog":"Salut, je suis le gardien du Tombeau. As-tu remarque quelque chose d'etrange en venant ici ?",
"isQuestion":1, "isQuestion":1,
"choice":"Rien de special$Des pas dans les bois", "choice":"Non rien$Des empreintes",
"conclusion1":"Ok, soit prudent tout de meme", "conclusion1":"Ok, soit prudent tout de meme",
"next1":2, "next1":2,
"conclusion2":"Je vais finir mon tour de ronde et verifier", "conclusion2":"Je vais finir mon tour de ronde et verifier",

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="48" height="24" tilewidth="8" tileheight="8" infinite="0" nextlayerid="8" nextobjectid="12"> <map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="48" height="24" tilewidth="8" tileheight="8" infinite="0" nextlayerid="8" nextobjectid="14">
<editorsettings> <editorsettings>
<export target="level0.json" format="json"/> <export target="level0.json" format="json"/>
</editorsettings> </editorsettings>
@ -114,6 +114,15 @@
</properties> </properties>
<point/> <point/>
</object> </object>
<object id="12" name="PNJ3" type="NPC" x="267.25" y="125.75">
<properties>
<property name="dialogID" type="int" value="12"/>
<property name="hasPath" type="int" value="1"/>
<property name="needAction" type="int" value="1"/>
<property name="path" type="object" value="13"/>
</properties>
<point/>
</object>
<object id="2" name="PNJ2" type="NPC" x="164" y="132"> <object id="2" name="PNJ2" type="NPC" x="164" y="132">
<properties> <properties>
<property name="dialogID" type="int" value="5"/> <property name="dialogID" type="int" value="5"/>
@ -147,7 +156,7 @@
<point/> <point/>
</object> </object>
<object id="9" name="Chemin Crémier" type="TRJ" x="251.967" y="164.12"> <object id="9" name="Chemin Crémier" type="TRJ" x="251.967" y="164.12">
<polyline points="0,0 -72.25,-18.5 -171.75,-19 -172.5,-99.25 -206.25,-122.75 -140.75,-114.75 -175.25,-97.5 -174.5,-33 -148.25,-20.5 -73.25,-20.25 39,-30.25 81.25,-45 79.25,-24.5"/> <polyline points="0,0 -72.25,-18.5 -171.75,-19 -172.5,-99.25 -206.25,-122.75 -140.75,-114.75 -175.25,-97.5 -174.5,-33 -148.25,-20.5 -73.25,-20.25 39,-30.25 81.25,-45 34.25,-25.5"/>
</object> </object>
<object id="11" name="DébutHistoire" type="INFO" x="18.6666" y="42.6667"> <object id="11" name="DébutHistoire" type="INFO" x="18.6666" y="42.6667">
<properties> <properties>
@ -156,5 +165,8 @@
</properties> </properties>
<point/> <point/>
</object> </object>
<object id="13" name="Chemin 100pas Client Auberge" type="TRJ" x="267.25" y="126.25">
<polyline points="0,0 -30.5,16.75 -182.5,15.75 -195.25,-26 -183.25,16.5 -29.25,18.5"/>
</object>
</objectgroup> </objectgroup>
</map> </map>

View file

@ -7,6 +7,7 @@
#include "config.h" #include "config.h"
#include "game.h" #include "game.h"
#include "npc.h"
#define BOX_HEIGHT (F_HEIGHT/PXSIZE+8) #define BOX_HEIGHT (F_HEIGHT/PXSIZE+8)
@ -65,6 +66,7 @@ int showtext_opt(Game *game, bopti_image_t *face, char *text,
for(i=0;i<=BOX_HEIGHT;i++){ for(i=0;i<=BOX_HEIGHT;i++){
/* Redrawing the entire screen, because maybe there was no dialog /* Redrawing the entire screen, because maybe there was no dialog
displayed before. */ displayed before. */
update_npc(game);
draw(game); draw(game);
/* Fill the dialog box with white */ /* Fill the dialog box with white */
@ -177,6 +179,7 @@ int showtext_opt(Game *game, bopti_image_t *face, char *text,
/* Run another little fancy animation if we should. */ /* Run another little fancy animation if we should. */
for(i=BOX_HEIGHT;i>0;i--){ for(i=BOX_HEIGHT;i>0;i--){
/* It is the same as the start animation. */ /* It is the same as the start animation. */
update_npc(game);
draw(game); draw(game);
drect(0, 0, DWIDTH, i*PXSIZE, C_WHITE); drect(0, 0, DWIDTH, i*PXSIZE, C_WHITE);
drect(0, i*PXSIZE, DWIDTH, (i+1)*PXSIZE, C_BLACK); drect(0, i*PXSIZE, DWIDTH, (i+1)*PXSIZE, C_BLACK);
@ -297,6 +300,7 @@ int _choice_call_before_end(Game *game, unsigned int org_i) {
/* Make a little animation because we looove little animations ;) */ /* Make a little animation because we looove little animations ;) */
for(i=DWIDTH/8+1;i>0;i--){ for(i=DWIDTH/8+1;i>0;i--){
/* I'm drawing the same box as on the start animation */ /* I'm drawing the same box as on the start animation */
update_npc(game);
draw(game); draw(game);
showtext_opt(game, _face, _text, NULL, false, false, NULL, 0, false, showtext_opt(game, _face, _text, NULL, false, false, NULL, 0, false,
_i, false); _i, false);

View file

@ -14,13 +14,16 @@
extern bopti_image_t SignAction_img; extern bopti_image_t SignAction_img;
extern Dialog *dialogRPG; extern Dialog *dialogRPG;
extern NPC *npcRPG;
extern uint32_t nbNPC;
#define MAX_INTERACTION_DISTANCE 12 #define MAX_INTERACTION_DISTANCE 12
void game_logic(Game *game) { void game_logic(Game *game) {
update_npc( game );
/* we check if interactions are possible close to the player */ /* we check if interactions are possible close to the player */
for( int i=0; i<game->map_level->nbextradata; i++ ) for( int i=0; i<game->map_level->nbextradata; i++ )
{ {
@ -31,18 +34,46 @@ void game_logic(Game *game) {
< MAX_INTERACTION_DISTANCE*PXSIZE) < MAX_INTERACTION_DISTANCE*PXSIZE)
&& (abs((int) game->player.wy - && (abs((int) game->player.wy -
(int) game->map_level->extradata[i].y*PXSIZE ) (int) game->map_level->extradata[i].y*PXSIZE )
< MAX_INTERACTION_DISTANCE*PXSIZE) ) < MAX_INTERACTION_DISTANCE*PXSIZE)
&& strcmp( game->map_level->extradata[i].type, "NPC") !=0 )
{ {
/* the player can do something */ /* the player can do something */
game->player.canDoSomething = true; game->player.canDoSomething = true;
/* we mark the action for futur treatment in player_action() */ /* we mark the action for futur treatment in player_action() */
game->player.whichAction = i; game->player.whichAction = i;
/* this is not an interraction with a NPC */
game->player.isInteractingWithNPC = false;
return; return;
} }
} }
for( int i=0; i<nbNPC; i++ )
{
/* simple distance check along X and Y axis */
/* Be careful to use world coordinates, not local (i.e.map) ones */
if ( (abs((int) game->player.wx -
(int) npcRPG[i].curx*PXSIZE )
< MAX_INTERACTION_DISTANCE*PXSIZE)
&& (abs((int) game->player.wy -
(int) npcRPG[i].cury*PXSIZE )
< MAX_INTERACTION_DISTANCE*PXSIZE)
&& strcmp( game->map_level->extradata[i].type, "NPC") !=0 )
{
/* the player can do something */
game->player.canDoSomething = true;
/* we mark the action for futur treatment in player_action() */
game->player.whichAction = i;
/* this is not an interraction with a NPC */
game->player.isInteractingWithNPC = true;
return;
}
}
/* else nothing to be done here */ /* else nothing to be done here */
game->player.canDoSomething = false; game->player.canDoSomething = false;
game->player.whichAction = -1; game->player.whichAction = -1;
game->player.isInteractingWithNPC = false;
return; return;
} }

View file

@ -39,6 +39,8 @@ typedef struct {
int32_t whichAction; int32_t whichAction;
/* the player is doing something */ /* the player is doing something */
bool isDoingAction; bool isDoingAction;
/* the player is interacting with a NPC */
bool isInteractingWithNPC;
} Player; } Player;
@ -79,8 +81,8 @@ typedef struct {
/* data for NPC's trajectories */ /* data for NPC's trajectories */
uint32_t hasPath; uint32_t hasPath;
uint32_t path_length; uint32_t path_length;
uint16_t *xpath; int16_t *xpath;
uint16_t *ypath; int16_t *ypath;
/* ... this can be extended as per needs ... */ /* ... this can be extended as per needs ... */
} ExtraData; } ExtraData;

View file

@ -4,7 +4,10 @@
#include <gint/timer.h> #include <gint/timer.h>
#include <gint/cpu.h> #include <gint/cpu.h>
#include <fxlibc/printf.h>
#include "config.h" #include "config.h"
#include "npc.h"
#if USB_FEATURE #if USB_FEATURE
#include <gint/usb-ff-bulk.h> #include <gint/usb-ff-bulk.h>
@ -32,7 +35,7 @@ extern Map *worldRPG[];
/* Game data (defined in "game.h")*/ /* Game data (defined in "game.h")*/
Game game = { Game game = {
NULL, NULL,
{12*PXSIZE, 36*PXSIZE, 0, 0, 12*PXSIZE, 36*PXSIZE, 100, SPEED, false, 0, false}, {12*PXSIZE, 36*PXSIZE, 0, 0, 12*PXSIZE, 36*PXSIZE, 100, SPEED, false, 0, false, false},
false, false, false, 0 false, false, false, 0
/* debug variables*/ /* debug variables*/
@ -86,6 +89,9 @@ int update_time(void) {
} }
int main(void) { int main(void) {
__printf_enable_fp();
int timer; int timer;
timer = timer_configure(TIMER_TMU, 1000, GINT_CALL(update_time)); timer = timer_configure(TIMER_TMU, 1000, GINT_CALL(update_time));
if(timer < 0){ if(timer < 0){
@ -95,6 +101,7 @@ int main(void) {
game.map_level = worldRPG[0]; game.map_level = worldRPG[0];
reload_npc(&game);
#if USB_FEATURE #if USB_FEATURE
usb_interface_t const *interfaces[] = {&usb_ff_bulk, NULL}; usb_interface_t const *interfaces[] = {&usb_ff_bulk, NULL};

136
src/npc.c
View file

@ -5,7 +5,10 @@
#include "config.h" #include "config.h"
#include <gint/display.h> #include <gint/display.h>
#include <gint/keyboard.h> /*debug*/ #include <gint/keyboard.h> /*debug*/
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <math.h>
extern bopti_image_t demo_PNJ_img; extern bopti_image_t demo_PNJ_img;
@ -20,7 +23,140 @@ extern bopti_image_t demo_PNJ_img;
#endif #endif
NPC *npcRPG;
uint32_t nbNPC = 0;
float length( float x, float y )
{
return sqrtf( x*x+y*y );
}
void update_npc(Game *game)
{
for( uint32_t u=0; u<nbNPC; u++ )
{
/* if the NPC has a path to follow AND is not currently in pause */
/* (talking with the player) */
if (npcRPG[u].hasPath==1 && npcRPG[u].paused==false)
{
float vecX = (float) (npcRPG[u].xpath[ npcRPG[u].currentPoint ] +
npcRPG[u].x) - npcRPG[u].curx;
float vecY = (float) (npcRPG[u].ypath[ npcRPG[u].currentPoint ] +
npcRPG[u].y) - npcRPG[u].cury;
float vecN = length(vecX, vecY);
if (vecN>0.5f)
{
vecX /= vecN*2.0;
vecY /= vecN*2.0;
}
else
{
npcRPG[u].currentPoint++;
npcRPG[u].currentPoint = npcRPG[u].currentPoint % npcRPG[u].path_length;
}
npcRPG[u].curx += vecX;
npcRPG[u].cury += vecY;
}
}
}
void reload_npc(Game *game)
{
if (npcRPG!=NULL)
{
free(npcRPG);
npcRPG = NULL;
}
nbNPC = 0;
for (uint32_t u=0; u<game->map_level->nbextradata; u++) //uint pour enlever un warning
{
ExtraData *Data = &game->map_level->extradata[u];
if (strcmp(Data->type, "NPC")==0) /* the current data is a NPC */
{
nbNPC++;
}
}
npcRPG = (NPC*) malloc( nbNPC * sizeof(NPC) );
int currentNPC=0;
for (uint32_t u=0; u<game->map_level->nbextradata; u++) //uint pour enlever un warning
{
ExtraData *Data = &game->map_level->extradata[u];
if (strcmp(Data->type, "NPC")==0) /* the current data is a NPC */
{
npcRPG[currentNPC].curx = (float) Data->x;
npcRPG[currentNPC].cury = (float) Data->y;
npcRPG[currentNPC].x = Data->x;
npcRPG[currentNPC].y = Data->y;
npcRPG[currentNPC].dialogID = Data->dialogID;
npcRPG[currentNPC].currentPoint = 1;
npcRPG[currentNPC].hasPath = Data->hasPath;
npcRPG[currentNPC].path_length = Data->path_length;
npcRPG[currentNPC].xpath = Data->xpath;
npcRPG[currentNPC].ypath = Data->ypath;
npcRPG[currentNPC].paused = false;
currentNPC++;
}
}
}
void npc_draw(Game *game) { void npc_draw(Game *game) {
Player *pl = &game->player;
for (uint32_t u=0; u<nbNPC; u++) //uint pour enlever un warning
{
NPC *Data = &npcRPG[u];
/* TODO : This is for debugging purpose, JUste to render the path */
/* to be followed by the NPC when this will be implemented */
#if DEBUGMODE
if (Data->hasPath==1) /* this NPC has a trajectory */
{
int NbPoints = Data->path_length+1;
for(int v=0; v<NbPoints; v++)
{
int16_t deltaX1=((int16_t) (Data->x +
Data->xpath[v % NbPoints]) * PXSIZE)
-(int16_t) pl->wx;
int16_t deltaY1=((int16_t) (Data->y +
Data->ypath[v % NbPoints]) * PXSIZE)
-(int16_t) pl->wy;
int16_t deltaX2=((int16_t) (Data->x +
Data->xpath[(v+1) % NbPoints]) * PXSIZE)
-(int16_t) pl->wx;
int16_t deltaY2=((int16_t) (Data->y +
Data->ypath[(v+1) % NbPoints]) * PXSIZE)
-(int16_t) pl->wy;
dline( pl->px + deltaX1, pl->py + deltaY1,
pl->px + deltaX2, pl->py + deltaY2,
PATH_COLOR);
}
}
#endif // DEBUGMODE
int16_t delX=((int16_t) (Data->curx * PXSIZE))-(int16_t) pl->wx;
int16_t delY=((int16_t) (Data->cury * PXSIZE))-(int16_t) pl->wy;
dimage( pl->px-P_WIDTH/2+delX, pl->py-P_HEIGHT/2+delY, &demo_PNJ_img);
}
}
void OLD_npc_draw(Game *game) {
Player *player = &game->player; Player *player = &game->player;
for (uint32_t u=0; u<game->map_level->nbextradata; u++) //uint pour enlever un warning for (uint32_t u=0; u<game->map_level->nbextradata; u++) //uint pour enlever un warning

View file

@ -3,16 +3,45 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include "game.h" #include "game.h"
#include "memory.h" #include "memory.h"
typedef struct
{
/* current coordinates of the NPC */
float curx, cury;
/* initial coordinates of the NPC (needed to get absolute coordinates of path) */
uint32_t x;
uint32_t y;
/* the ID of the first element of the dialog */
/* (to be aligned with "dialogs.json" IDs)*/
uint32_t dialogID;
/* the number of the target point of the path */
/* Note: it must keep the value 0 if NPC has no path assigned */
uint32_t currentPoint;
/* data of the path */
uint32_t hasPath;
uint32_t path_length;
int16_t *xpath;
int16_t *ypath;
/* is the current NPC in pause (during dialog) */
bool paused;
} NPC;
/* Draws the player player. This function should be called after drawing the /* Draws the player player. This function should be called after drawing the
* map! */ * map! */
void npc_draw(Game *game); void npc_draw(Game *game);
void update_npc(Game *game);
void reload_npc(Game *game);
#endif #endif

View file

@ -3,6 +3,7 @@
#include "game.h" #include "game.h"
#include "map.h" #include "map.h"
#include "config.h" #include "config.h"
#include "npc.h"
#include <gint/display.h> #include <gint/display.h>
const char one_px_mov[8] = { const char one_px_mov[8] = {
@ -28,6 +29,10 @@ const char damage_taken_walkable[WALKABLE_TILE_MAX] = {
extern bopti_image_t demo_player_img; extern bopti_image_t demo_player_img;
extern NPC *npcRPG;
extern uint32_t nbNPC;
void player_draw(Game *game) { void player_draw(Game *game) {
Player *player = &game->player; Player *player = &game->player;
dimage(player->px-P_WIDTH/2, player->py-P_HEIGHT/2, &demo_player_img); dimage(player->px-P_WIDTH/2, player->py-P_HEIGHT/2, &demo_player_img);
@ -86,9 +91,9 @@ extern bopti_image_t INFO_Icon_img;
void player_action(Game *game) { void player_action(Game *game) {
if( game->player.isDoingAction ) return; /* alreday doing something */ if( game->player.isDoingAction ) return; /* alreday doing something (action IS NOT with an NPC ) */
if( game->player.canDoSomething ) /* we can do something */ if( game->player.canDoSomething && !game->player.isInteractingWithNPC ) /* we can do something */
{ {
/* we indicate that the player is occupied */ /* we indicate that the player is occupied */
game->player.isDoingAction = true; game->player.isDoingAction = true;
@ -103,8 +108,8 @@ void player_action(Game *game) {
if (strcmp("INFO", currentData->type)==0) if (strcmp("INFO", currentData->type)==0)
face = &INFO_Icon_img; face = &INFO_Icon_img;
else if (strcmp("NPC", currentData->type)==0) //else if (strcmp("NPC", currentData->type)==0)
face = &NPC_Icon_img; // face = &NPC_Icon_img;
else if (strcmp("SGN", currentData->type)==0) else if (strcmp("SGN", currentData->type)==0)
face = &SGN_Icon_img; face = &SGN_Icon_img;
else face = &demo_player_img; else face = &demo_player_img;
@ -116,6 +121,32 @@ void player_action(Game *game) {
/* when done we release the occupied status of the player */ /* when done we release the occupied status of the player */
game->player.isDoingAction = false; game->player.isDoingAction = false;
} }
else if( game->player.canDoSomething && game->player.isInteractingWithNPC ) /* we can do something (action IS with an NPC ) */
{
/* we indicate that the player is occupied */
game->player.isDoingAction = true;
NPC *currentNPC = &npcRPG[game->player.whichAction];
/* we use the correct image as per the class of the item */
bopti_image_t *face = &NPC_Icon_img;
uint32_t dialogStart = currentNPC->dialogID;
/* we setr this NPC to paused to avoid changing its position while talking (the rest of the NPCs pursue their action)*/
currentNPC->paused = true;
initiate_dialog_sequence( game, face, dialogStart );
/* when done we release the occupied status of the player */
game->player.isDoingAction = false;
currentNPC->paused = false;
}
} }
bool player_collision(Game *game, Direction direction, bool player_collision(Game *game, Direction direction,
@ -184,6 +215,10 @@ bool player_collision(Game *game, Direction direction,
return true; /* He will collide with it. */ return true; /* He will collide with it. */
} }
/* we update the list of NPCs in the current map */
/* to follow the trajectories */
reload_npc( game );
return false; return false;
} }
} }