added NPCs movement along paths + adjusted interraction system to work with moving positions

This commit is contained in:
SlyVTT 2023-08-26 17:13:05 +02:00
parent 126bd87d6e
commit 53663f61ec
10 changed files with 280 additions and 13 deletions

View file

@ -14,7 +14,7 @@ find_package(LibProf 2.4 REQUIRED)
#set the color mode either to 1b or 2b #set the color mode either to 1b or 2b
set(COLORMODE_fx 2b) set(COLORMODE_fx 2b)
#set the color mode either to 1b, 2b or EGA64 #set the color mode either to 1b, 2b or EGA64
set(COLORMODE_cg 2b) set(COLORMODE_cg EGA64)
fxconv_declare_converters(assets/converters.py) fxconv_declare_converters(assets/converters.py)

View file

@ -129,6 +129,17 @@
"conclusion2":"_", "conclusion2":"_",
"next2":-1, "next2":-1,
"nextOther":-1 "nextOther":-1
},
{
"ID":12,
"dialog":"J'attends mon fils pour dejeuner à la taverne ... Il est toujours en retard !!",
"isQuestion":0,
"choice":"_",
"conclusion1":"_",
"next1":-1,
"conclusion2":"_",
"next2":-1,
"nextOther":-1
} }
] ]
} }

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;
} }
} }