diff --git a/src/dialogs.c b/src/dialogs.c index ce26a57..dff74ad 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -59,7 +59,7 @@ int dialogs_text_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); + update_npcs(game); game_draw(game); /* Fill the dialog box with white */ @@ -182,7 +182,7 @@ int dialogs_text_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); + update_npcs(game); game_draw(game); drect(0, 0, DWIDTH, i*PXSIZE, C_WHITE); drect(0, i*PXSIZE, DWIDTH, (i+1)*PXSIZE, C_BLACK); @@ -303,7 +303,7 @@ int _choice_call_before_end(Game *game, [[maybe_unused]] 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); + update_npcs(game); game_draw(game); dialogs_text_opt(game, _face, _text, NULL, false, false, NULL, 0, false, _i, false); @@ -376,4 +376,4 @@ void dialogs_initiate_sequence(Game *game, bopti_image_t *face, dialogs_text(game, face, text, true, true); if (nextOther!=-1) dialogs_initiate_sequence(game, face, nextOther); } -} \ No newline at end of file +} diff --git a/src/game.c b/src/game.c index 32b4104..2f7cb7a 100644 --- a/src/game.c +++ b/src/game.c @@ -24,7 +24,7 @@ extern uint32_t nbNPC; void game_logic(Game *game) { - update_npc( game ); + update_npcs( game ); /* we check if interactions are possible close to the player */ for( uint32_t i=0; imap_level->nbextradata; i++ ){ diff --git a/src/npc.c b/src/npc.c index 3f87086..f7c776c 100644 --- a/src/npc.c +++ b/src/npc.c @@ -22,36 +22,239 @@ float length( float x, float y ) return sqrtf( x*x+y*y ); } -void update_npc( [[maybe_unused]] Game *game) +int npc_clear_path(NPC *npc) +{ + npc->currentPoint = 0; + npc->hasPath = 0; + npc->path_length = 0; + free(npc->xpath); + free(npc->ypath); + npc->xpath = malloc(4); + npc->ypath = malloc(4); + if(npc->xpath == NULL || npc->ypath == NULL) return 1; + return 0; +} + +int npc_append_path(uint16_t x, uint16_t y, NPC *npc) +{ + npc->xpath = realloc(npc->xpath, npc->path_length*2+2); + npc->ypath = realloc(npc->ypath, npc->path_length*2+2); + if(npc->xpath == NULL || npc->ypath == NULL) return 1; + npc->path_length++; + npc->xpath[npc->path_length-1] = x - npc->x; + npc->ypath[npc->path_length-1] = y - npc->y; + return 0; +} + +void as_clean(uint8_t *visited, uint8_t *gscore, uint8_t *fscore) +{ + free(visited); + free(gscore); + free(fscore); +} + +int as_reconstruct_path(int16_t *came_from, int w, int h, int16_t spos, + int16_t dest, NPC *npc) +{ + if(npc_clear_path(npc)) goto as_recons_fail; + + int16_t next = came_from[dest]; + + unsigned int i; + + for(i = 0; i < 64; i++) + { + if(npc_append_path((next%w)*T_WIDTH,(next/h)*T_HEIGHT, npc)) + { + goto as_recons_fail; + } + + next = came_from[next]; + if(next == spos){ + if(npc_append_path((spos%w)*T_WIDTH,(spos/h)*T_HEIGHT, npc)) + goto as_recons_fail; + break; + } + } + + uint16_t tx, ty; + + //Flip the path because it started from the end + + for(i = 0; i < npc->path_length/2; i++) + { + tx = npc->xpath[i]; + ty = npc->ypath[i]; + npc->xpath[i] = npc->xpath[npc->path_length-i-1]; + npc->ypath[i] = npc->ypath[npc->path_length-i-1]; + npc->ypath[npc->path_length-i-1] = tx; + npc->ypath[npc->path_length-i-1] = ty; + } + + free(came_from); + + npc->hasPath = true; + + return 0; + + as_recons_fail: + + free(came_from); + + return 1; +} + +//Returns non zero error code on failure +//Custom a* implemetation +//Unoptimized, may become an issue +int npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) +{ + int32_t i, j; + + int32_t w = full_map->w; + int32_t h = full_map->h; + int32_t x = floor(npc->curx)/T_WIDTH; + int32_t y = floor(npc->cury)/T_HEIGHT; + dest_x /= T_WIDTH; + dest_y /= T_HEIGHT; + int32_t spos = y*w+x; + + uint8_t *map = full_map->walkable; + + if(dest_x < 0 || dest_x > w || dest_y < 0 || dest_x > h) return 2; + if(map[spos]) return 2; + if(map[dest_y*w+dest_x]) return 2; + + npc_clear_path(npc); + + uint8_t *visited = malloc(w*h); + for(i=0; i bscore) continue; + bx = i%w; + by = i/w; + bscore = fscore[i]; + } + if(bx == dest_x && by == dest_y) + { + as_clean(visited, gscore, fscore); + return as_reconstruct_path(came_from, w, h, spos, + dest_y*w+dest_x, npc); + } + + visited[by*w+bx] = 1; + + int att_score; + + for(i = bx-1; i < bx+2; i++) + { + if(i > w) break; + for(j = by-1; j < by+2; j++) + { + if(j > h) break; + if(map[j*w+i] == 1) continue; + if(i == bx && j == by) continue; + att_score = gscore[by*w+bx] + round(length(bx-i,by-j)); + if(att_score < gscore[j*w+i]) + { + came_from[j*w+i] = by*w+bx; + gscore[j*w+i] = att_score; + fscore[j*w+i] = att_score + round( + length(dest_x-i, dest_y-j)); + if(visited[j*w+i]) visited[j*w+i] = 0; + } + } + } + } + + as_clean(visited, gscore, fscore); + + free(came_from); + return 3; +} + +NPC *npc_create() +{ + //Use temp pointer to avoid breaking the whole npcRPG on failure + void *temp = realloc(npcRPG, (nbNPC+1)*sizeof(NPC)); + if(temp == NULL) return NULL; + npcRPG = temp; + nbNPC++; + NPC *npc = &npcRPG[nbNPC-1]; + npc->xpath = malloc(2); + npc->ypath = malloc(2); + return npc; +} + +void npc_remove(NPC *npc) +{ + uint32_t pos = ((uint32_t)npc - (uint32_t)npcRPG)/sizeof(NPC); + if(pos > nbNPC-1) return; + if(pos == nbNPC-1) + { + nbNPC--; + return; + } + memmove(npc, &npc[1], (nbNPC-pos-1)*sizeof(NPC)); +} + +//Refactoring to make adding complexity cleaner +void update_npcs([[maybe_unused]] 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; - - } + update_npc(&npcRPG[u]); } +} + +void update_npc(NPC *npc) +{ + /* if the NPC has no path or is paused, skip it */ + if (!npc->hasPath || npc->paused==true) return; + + float vecX = (float) (npc->xpath[ npc->currentPoint ] + + npc->x) - npc->curx; + float vecY = (float) (npc->ypath[ npc->currentPoint ] + + npc->y) - npc->cury; + float vecN = length(vecX, vecY); + + if (vecN>0.5f) + { + vecX /= vecN*2.0; + vecY /= vecN*2.0; + } + else + { + npc->currentPoint++; + npc->currentPoint = npc->currentPoint % npc->path_length; + } + + npc->curx += vecX; + npc->cury += vecY; } @@ -67,7 +270,7 @@ void reload_npc(Game *game) nbNPC = 0; - for (uint32_t u=0; umap_level->nbextradata; u++) //uint pour enlever un warning + for (uint32_t u=0; umap_level->nbextradata; u++) { ExtraData *Data = &game->map_level->extradata[u]; @@ -78,9 +281,10 @@ void reload_npc(Game *game) } npcRPG = (NPC*) malloc( nbNPC * sizeof(NPC) ); + if(npcRPG == NULL) return; int currentNPC=0; - for (uint32_t u=0; umap_level->nbextradata; u++) //uint pour enlever un warning + for (uint32_t u=0; umap_level->nbextradata; u++) { ExtraData *Data = &game->map_level->extradata[u]; @@ -106,12 +310,11 @@ void reload_npc(Game *game) void npc_draw(Game *game) { Player *pl = &game->player; - for (uint32_t u=0; uhasPath==1) /* this NPC has a trajectory */ { diff --git a/src/npc.h b/src/npc.h index a7dd906..d6c17cb 100644 --- a/src/npc.h +++ b/src/npc.h @@ -8,6 +8,15 @@ #include "game.h" #include "memory.h" +enum +{ + + NPC_NONE = 0, + NPC_FRIENDLY = 1, //The player's team + NPC_HOSTILE = 2, //to the player + NPC_ALL = 3 + +}; typedef struct { @@ -29,19 +38,44 @@ typedef struct int16_t *xpath; int16_t *ypath; + int type; + + int8_t current_group; + int8_t hostile_to_group; + /* is the current NPC in pause (during dialog) */ bool paused; char *face; } NPC; +//Frees then malloc()s a new path to npc +//Useful if you want to safely edit a path +int npc_clear_path(NPC *npc); +//Adds point x,y to the path of npc +//Won't work on static NPCs, use npc_clear_path before or make them on the heap +int npc_append_path(uint16_t x, uint16_t y, NPC *npc); + +//Clears the NPCs path and creates a new one going to dest, +//avoiding non-walkable tiles +//Returns non-zero on failure +int npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc); + +//realloc()s npcRPG to adequate size and returns a pointer to the new element +//Returns NULL on failure +NPC *npc_create(); + +//Pops the NPC from npcRPG +void npc_remove(NPC *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 update_npcs(Game *game); + +void update_npc(NPC *npc); void reload_npc(Game *game);