diff --git a/.gitignore b/.gitignore index 7ea0ff6..085829e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ __pycache__/ .vscode level*.json +tilesetnpp.json diff --git a/README.md b/README.md index f4df062..03c5af6 100644 --- a/README.md +++ b/README.md @@ -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] Carte Multilayer (Background, Foreground + accessibilité / Dommages) avec transparence du calque Foreground - [x] Personnage -- [x] Dialogues (sauts de lignes et mots plus grands que l'écran pas supportés) -- [ ] Fontes de caractères -- [ ] Interaction +- [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) +- [x] Fontes de caractères +- [x] Interaction - [ ] NPC - [x] Changement de map durant le jeu +- [ ] Système d'événements + ## Crédits diff --git a/TODO.txt b/TODO.txt index 46d445e..23a926b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -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] - ajouter face pour dialogue pour PNJ [OK] - ajouter indicateur visuel pour action joueur quand on est proche d'un PNJ ou d'un panneau - -- faire des fonts lisibles avec le set `print` disponible intégralement \ No newline at end of file +[OK] - faire des fonts lisibles avec le set `print` disponible intégralement \ No newline at end of file diff --git a/assets/DialogsLvl0.json b/assets/DialogsLvl0.json index d0ca35a..c91155e 100644 --- a/assets/DialogsLvl0.json +++ b/assets/DialogsLvl0.json @@ -66,7 +66,7 @@ }, { "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, "choice":"_", "conclusion1":"_", @@ -80,7 +80,7 @@ "dialog":"Salut Hero, je suis le cremier. Veux tu me delester un peu ?", "isQuestion":1, "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, "conclusion2":"Bon bah casse toi ...", "next2":-1, @@ -110,7 +110,7 @@ }, { "ID":10, - "dialog":"Et sa tombe est en train d'etre creusee ?", + "dialog":"Et une tombe est en train d'etre creusee ?", "isQuestion":0, "choice":"_", "conclusion1":"_", @@ -129,6 +129,17 @@ "conclusion2":"_", "next2":-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 } ] } \ No newline at end of file diff --git a/assets/DialogsLvl1.json b/assets/DialogsLvl1.json index c8e9ff5..0574c1d 100644 --- a/assets/DialogsLvl1.json +++ b/assets/DialogsLvl1.json @@ -13,7 +13,7 @@ "ID":1, "dialog":"Salut, je suis le gardien du Tombeau. As-tu remarque quelque chose d'etrange en venant ici ?", "isQuestion":1, - "choice":"Rien de special$Des pas dans les bois", + "choice":"Non rien$Des empreintes", "conclusion1":"Ok, soit prudent tout de meme", "next1":2, "conclusion2":"Je vais finir mon tour de ronde et verifier", diff --git a/assets/level0.tmx b/assets/level0.tmx index a5d1322..4cf2838 100644 --- a/assets/level0.tmx +++ b/assets/level0.tmx @@ -1,5 +1,5 @@ - + @@ -114,6 +114,15 @@ + + + + + + + + + @@ -147,7 +156,7 @@ - + @@ -156,5 +165,8 @@ + + + diff --git a/src/dialogs.c b/src/dialogs.c index 410fef8..233bf8f 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -7,6 +7,7 @@ #include "config.h" #include "game.h" +#include "npc.h" #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++){ /* Redrawing the entire screen, because maybe there was no dialog displayed before. */ + update_npc(game); draw(game); /* 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. */ for(i=BOX_HEIGHT;i>0;i--){ /* It is the same as the start animation. */ + update_npc(game); draw(game); drect(0, 0, DWIDTH, i*PXSIZE, C_WHITE); 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 ;) */ for(i=DWIDTH/8+1;i>0;i--){ /* I'm drawing the same box as on the start animation */ + update_npc(game); draw(game); showtext_opt(game, _face, _text, NULL, false, false, NULL, 0, false, _i, false); diff --git a/src/game.c b/src/game.c index 5dea9b4..9fa9238 100644 --- a/src/game.c +++ b/src/game.c @@ -14,13 +14,16 @@ extern bopti_image_t SignAction_img; extern Dialog *dialogRPG; - +extern NPC *npcRPG; +extern uint32_t nbNPC; #define MAX_INTERACTION_DISTANCE 12 void game_logic(Game *game) { + update_npc( game ); + /* we check if interactions are possible close to the player */ for( int i=0; imap_level->nbextradata; i++ ) { @@ -31,18 +34,46 @@ void game_logic(Game *game) { < MAX_INTERACTION_DISTANCE*PXSIZE) && (abs((int) game->player.wy - (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 */ 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 = false; return; } } + + for( int i=0; iplayer.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 */ game->player.canDoSomething = false; game->player.whichAction = -1; + game->player.isInteractingWithNPC = false; return; } diff --git a/src/game.h b/src/game.h index b5dbb4b..8d24797 100644 --- a/src/game.h +++ b/src/game.h @@ -38,7 +38,9 @@ typedef struct { /* extradata layer of the map */ int32_t whichAction; /* the player is doing something */ - bool isDoingAction; + bool isDoingAction; + /* the player is interacting with a NPC */ + bool isInteractingWithNPC; } Player; @@ -79,8 +81,8 @@ typedef struct { /* data for NPC's trajectories */ uint32_t hasPath; uint32_t path_length; - uint16_t *xpath; - uint16_t *ypath; + int16_t *xpath; + int16_t *ypath; /* ... this can be extended as per needs ... */ } ExtraData; diff --git a/src/main.c b/src/main.c index 9294a8a..0375212 100644 --- a/src/main.c +++ b/src/main.c @@ -4,7 +4,10 @@ #include #include +#include + #include "config.h" +#include "npc.h" #if USB_FEATURE #include @@ -32,7 +35,7 @@ extern Map *worldRPG[]; /* Game data (defined in "game.h")*/ Game game = { 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 /* debug variables*/ @@ -86,6 +89,9 @@ int update_time(void) { } int main(void) { + + __printf_enable_fp(); + int timer; timer = timer_configure(TIMER_TMU, 1000, GINT_CALL(update_time)); if(timer < 0){ @@ -95,6 +101,7 @@ int main(void) { game.map_level = worldRPG[0]; + reload_npc(&game); #if USB_FEATURE usb_interface_t const *interfaces[] = {&usb_ff_bulk, NULL}; diff --git a/src/npc.c b/src/npc.c index 01fea98..12c5aec 100644 --- a/src/npc.c +++ b/src/npc.c @@ -5,7 +5,10 @@ #include "config.h" #include #include /*debug*/ + #include +#include +#include extern bopti_image_t demo_PNJ_img; @@ -20,7 +23,140 @@ extern bopti_image_t demo_PNJ_img; #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; u0.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; umap_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; umap_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) { + Player *pl = &game->player; + + for (uint32_t u=0; uhasPath==1) /* this NPC has a trajectory */ + { + int NbPoints = Data->path_length+1; + for(int v=0; vx + + 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; for (uint32_t u=0; umap_level->nbextradata; u++) //uint pour enlever un warning diff --git a/src/npc.h b/src/npc.h index 0293824..e7e1e12 100644 --- a/src/npc.h +++ b/src/npc.h @@ -3,16 +3,45 @@ #include +#include #include "game.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 * map! */ void npc_draw(Game *game); +void update_npc(Game *game); + +void reload_npc(Game *game); #endif diff --git a/src/player.c b/src/player.c index 737639b..2274b11 100644 --- a/src/player.c +++ b/src/player.c @@ -3,6 +3,7 @@ #include "game.h" #include "map.h" #include "config.h" +#include "npc.h" #include 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 NPC *npcRPG; +extern uint32_t nbNPC; + + void player_draw(Game *game) { Player *player = &game->player; 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) { - 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 */ game->player.isDoingAction = true; @@ -103,8 +108,8 @@ void player_action(Game *game) { if (strcmp("INFO", currentData->type)==0) face = &INFO_Icon_img; - else if (strcmp("NPC", currentData->type)==0) - face = &NPC_Icon_img; + //else if (strcmp("NPC", currentData->type)==0) + // face = &NPC_Icon_img; else if (strcmp("SGN", currentData->type)==0) face = &SGN_Icon_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 */ 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, @@ -184,6 +215,10 @@ bool player_collision(Game *game, Direction direction, 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; } }