npc : Implémentation théorique de la machine d'état

This commit is contained in:
attilavs2 2024-08-27 12:41:01 +02:00
parent e37bf141c2
commit 5842aa905e
7 changed files with 234 additions and 167 deletions

View file

@ -334,8 +334,8 @@ def convert_map(input: str, output: str, params: dict, target):
npc_struct += fxconv.u8(0) # TODO: Group npc_struct += fxconv.u8(0) # TODO: Group
npc_struct += fxconv.u8(0) # Hostile to - leave as npc_struct += fxconv.u8(0) # Hostile to - leave as
npc_struct += fxconv.u8(0) # state - leave as npc_struct += fxconv.u8(0) # state - leave as
npc_struct += fxconv.u8(0) # Padding npc_struct += fxconv.u16(0) # target - leave as
npc_struct += fxconv.u16(0) # Padding npc_struct += fxconv.u8(0) # padding
map_struct += fxconv.ptr(npc_struct) map_struct += fxconv.ptr(npc_struct)
# Load signs # Load signs
map_struct += fxconv.u32(len(signs)) map_struct += fxconv.u32(len(signs))

View file

@ -282,13 +282,13 @@ void game_get_inputs(Game *game) {
mynpc->xpath = NULL; mynpc->xpath = NULL;
mynpc->ypath = NULL; mynpc->ypath = NULL;
} }
while(keydown(KEY_F1)){ while(keydown(KEY_F1)) {
clearevents(); clearevents();
} }
} }
if(keydown(KEY_F2)) { if(keydown(KEY_F2)) {
npc_remove_pos(0); npc_remove_pos(0);
while(keydown(KEY_F2)){ while(keydown(KEY_F2)) {
clearevents(); clearevents();
} }
} }

View file

@ -3,10 +3,34 @@
#include "animation.h" #include "animation.h"
#include "events.h" #include "events.h"
// #include "npc.h"
#include <gint/display.h> #include <gint/display.h>
#include <stdint.h> #include <stdint.h>
enum {
NPC_Static = 0, /*~= none, disqualifies from all AI*/
NPC_Guard = 1,
NPC_Bandit = 2,
NPC_Monster = 3,
NPC_Type_Count
};
enum {
NPC_T_NONE = 0,
NPC_T_FRIENDLY = 1, /* The player's team */
NPC_T_HOSTILE = 2, /* to the player */
NPC_T_ALL = 3,
NPC_T_Count
};
enum {
NPC_S_IDLE = 0,
NPC_S_ATTACK = 1,
NPC_S_FLEE = 2,
NPC_S_WANDER = 3
};
/* The direction where the player is going to. */ /* The direction where the player is going to. */
typedef enum { typedef enum {
D_UP, D_UP,
@ -53,9 +77,9 @@ typedef struct {
weapon. */ weapon. */
Slot equipped[3]; Slot equipped[3];
/* 1 if the inventory is open. */ /* 1 if the inventory is open. */
char open : 1; int8_t open : 1;
char selected; int8_t selected;
char selection; int8_t selection;
} Inventory; } Inventory;
typedef struct { typedef struct {
@ -157,12 +181,20 @@ typedef struct {
uint8_t hostile_to_group; uint8_t hostile_to_group;
/*state should be one of NPC_S*/ /*state should be one of NPC_S*/
uint8_t state; uint8_t state;
uint8_t __temp; /*Should be one of NPC_ - 0 if none*/
uint16_t target;
/* uint16_t to keep the struct aligned */ /* to keep the struct aligned */
uint16_t __padding; uint8_t __padding;
} NPC; } NPC;
/*Map wide NPC info*/
typedef struct {
uint16_t team_strength[NPC_T_Count];
} NPC_AI_Dat;
typedef struct { typedef struct {
Collider collider; Collider collider;
/* The destination portal */ /* The destination portal */
@ -231,6 +263,8 @@ typedef struct {
Animation npc_animation; Animation npc_animation;
Inventory inventory; Inventory inventory;
NPC_AI_Dat npc_ai_dat;
int mana; /* Only for testing events TODO: Remove this! */ int mana; /* Only for testing events TODO: Remove this! */
} Game; } Game;

View file

@ -22,7 +22,7 @@ void inventory_init(Inventory *inventory) {
} }
void inventory_draw(Inventory *inventory, Player *player) { void inventory_draw(Inventory *inventory, Player *player) {
size_t i; int i;
if(inventory->open) { if(inventory->open) {
dimage(0, 0, &inventory_img); dimage(0, 0, &inventory_img);
for(i = 0; i < SLOT_NUM; i++) { for(i = 0; i < SLOT_NUM; i++) {
@ -76,23 +76,23 @@ void inventory_move_from_selected(Inventory *inventory) {
inventory->slots[inventory->selected] = current; inventory->slots[inventory->selected] = current;
} }
void inventory_use(Inventory *inventory, Player *player) { void inventory_use(Inventory *inventory, GUNUSED Player *player) {
Item item = inventory->slots[inventory->selection].i; Item item = inventory->slots[inventory->selection].i;
switch(item_types[item]) { switch(item_types[item]) {
case IT_TALISMAN: case IT_TALISMAN:
inventory->equipped[0] = inventory->slots[inventory->selection]; inventory->equipped[0] = inventory->slots[inventory->selection];
break; break;
case IT_ARMOR: case IT_ARMOR:
inventory->equipped[1] = inventory->slots[inventory->selection]; inventory->equipped[1] = inventory->slots[inventory->selection];
break; break;
case IT_WEAPON: case IT_WEAPON:
inventory->equipped[2] = inventory->slots[inventory->selection]; inventory->equipped[2] = inventory->slots[inventory->selection];
break; break;
case IT_FOOD: case IT_FOOD:
/* TODO */ /* TODO */
break; break;
default: default:
break; break;
} }
inventory->slots[inventory->selection].i = I_NONE; inventory->slots[inventory->selection].i = I_NONE;
inventory->slots[inventory->selection].durability = 255; inventory->slots[inventory->selection].durability = 255;
@ -102,50 +102,50 @@ void inventory_unequip(Inventory *inventory, ItemType type) {
if(inventory->slots[inventory->selection].i) if(inventory->slots[inventory->selection].i)
return; return;
switch(type) { switch(type) {
case IT_TALISMAN: case IT_TALISMAN:
inventory->slots[inventory->selection] = inventory->equipped[0]; inventory->slots[inventory->selection] = inventory->equipped[0];
inventory->equipped[0].i = I_NONE; inventory->equipped[0].i = I_NONE;
inventory->equipped[0].durability = 255; inventory->equipped[0].durability = 255;
break; break;
case IT_ARMOR: case IT_ARMOR:
inventory->slots[inventory->selection] = inventory->equipped[1]; inventory->slots[inventory->selection] = inventory->equipped[1];
inventory->equipped[1].i = I_NONE; inventory->equipped[1].i = I_NONE;
inventory->equipped[1].durability = 255; inventory->equipped[1].durability = 255;
break; break;
case IT_WEAPON: case IT_WEAPON:
inventory->slots[inventory->selection] = inventory->equipped[2]; inventory->slots[inventory->selection] = inventory->equipped[2];
inventory->equipped[2].i = I_NONE; inventory->equipped[2].i = I_NONE;
inventory->equipped[2].durability = 255; inventory->equipped[2].durability = 255;
break; break;
default: default:
break; break;
} }
} }
void inventory_move_selection(Inventory *inventory, Direction direction) { void inventory_move_selection(Inventory *inventory, Direction direction) {
switch(direction) { switch(direction) {
case D_UP: case D_UP:
inventory->selection -= SLOT_COLUMNS; inventory->selection -= SLOT_COLUMNS;
if(inventory->selection < 0) if(inventory->selection < 0)
inventory->selection = 0; inventory->selection = 0;
break; break;
case D_DOWN: case D_DOWN:
inventory->selection += SLOT_COLUMNS; inventory->selection += SLOT_COLUMNS;
if(inventory->selection >= SLOT_NUM) { if(inventory->selection >= SLOT_NUM) {
inventory->selection = SLOT_NUM - 1; inventory->selection = SLOT_NUM - 1;
} }
break; break;
case D_LEFT: case D_LEFT:
if(inventory->selection > 0) { if(inventory->selection > 0) {
inventory->selection--; inventory->selection--;
} }
break; break;
case D_RIGHT: case D_RIGHT:
if(inventory->selection < SLOT_NUM - 1) { if(inventory->selection < SLOT_NUM - 1) {
inventory->selection++; inventory->selection++;
} }
break; break;
default: default:
break; break;
} }
} }

View file

@ -33,32 +33,33 @@ extern Map *worldRPG[];
/* Game data (defined in "game.h")*/ /* Game data (defined in "game.h")*/
Game game = { Game game = {
NULL, .map_level = NULL,
{12 * PXSIZE, .player = {.x = 12 * PXSIZE,
36 * PXSIZE, .y = 36 * PXSIZE,
0, .px = 0,
0, .py = 0,
100, .life = 100,
SPEED, .speed = SPEED,
false, .canDoSomething = false,
0, .whichAction = 0,
false, .isDoingAction = false,
false, .isInteractingWithNPC = false,
true, .is_male = true,
{}}, .animation = {}},
{{}, {}, 0}, .handler = {{}, {}, 0},
false, .exittoOS = false,
false, .screenshot = false,
false, .record = false,
0, .frame_duration = 0,
/* debug variables*/ /* debug variables*/
false, .debug_map = false,
false, .debug_player = false,
false, .debug_extra = false,
{}, .npc_animation = {},
{}, .inventory = {},
100, .npc_ai_dat = {.team_strength = {0, 0, 0, 0}},
.mana = 100,
}; };
/* screen capture management code. TODO: Clean this up! */ /* screen capture management code. TODO: Clean this up! */

154
src/npc.c
View file

@ -20,29 +20,25 @@ extern bopti_image_t tiny_npc_police;
NPC_TypeData npc_typedat[NPC_Type_Count] = { NPC_TypeData npc_typedat[NPC_Type_Count] = {
/*NPC_Static*/ /*NPC_Static*/
{0, 0, 0, 0}, {0, 0, 0, 0, 0},
/*NPC_Guard*/ /*NPC_Guard*/
{ {.presence = 8,
.agressivity = 75, .agressivity = 75,
.cowardice = 85, .cowardice = 85,
.lazyness = 50, .lazyness = 50,
.wanderlust = 150 .wanderlust = 150},
},
/*NPC_Bandit*/ /*NPC_Bandit*/
{ {.presence = 6,
.agressivity = 110, .agressivity = 110,
.cowardice = 85, .cowardice = 85,
.lazyness = 40, .lazyness = 40,
.wanderlust = 60 .wanderlust = 60},
},
/*NPC_Monster*/ /*NPC_Monster*/
{ {.presence = 10,
.agressivity = 170, .agressivity = 170,
.cowardice = 50, .cowardice = 50,
.lazyness = 40, .lazyness = 40,
.wanderlust = 80 .wanderlust = 80}};
}
};
NPC npc_stack[NPC_STACK_SIZE]; NPC npc_stack[NPC_STACK_SIZE];
uint32_t npc_count; uint32_t npc_count;
@ -84,10 +80,10 @@ void npc_remove_pos(uint32_t pos) { npc_remove(&npc_stack[pos]); }
/*Takes input in curx/cury*/ /*Takes input in curx/cury*/
/*Incredibely jank*/ /*Incredibely jank*/
bool npc_collision(Game *game, NPC *npc, int32_t dx, int32_t dy) { bool npc_collision(Game *game, int32_t dx, int32_t dy) {
int on_walkable = map_get_walkable(game, (dx>>PRECISION) / T_WIDTH, int on_walkable = map_get_walkable(game, (dx >> PRECISION) / T_WIDTH,
(dy>>PRECISION) / T_HEIGHT); (dy >> PRECISION) / T_HEIGHT);
int speed = (on_walkable >= 0 && on_walkable < WALKABLE_TILE_MAX) int speed = (on_walkable >= 0 && on_walkable < WALKABLE_TILE_MAX)
? walkable_speed[on_walkable] ? walkable_speed[on_walkable]
@ -140,11 +136,11 @@ void as_clean(uint8_t *visited, uint8_t *gscore, uint8_t *fscore) {
/*Takes input as pixel position*/ /*Takes input as pixel position*/
int npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) { int npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) {
uint8_t *map = full_map->walkable; // uint8_t *map = full_map->walkable;
uint32_t w = full_map->w; uint32_t w = full_map->w;
uint32_t h = full_map->h; uint32_t h = full_map->h;
uint32_t sx = npc_from_curxy(npc->curx); // uint32_t sx = npc_from_curxy(npc->curx);
uint32_t sy = npc_from_curxy(npc->cury); // uint32_t sy = npc_from_curxy(npc->cury);
dest_x /= PXSIZE; dest_x /= PXSIZE;
dest_y /= PXSIZE; dest_y /= PXSIZE;
@ -167,8 +163,8 @@ int npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) {
return 0; return 0;
} }
int as_reconstruct_path(int16_t *came_from, int w, int h, int16_t start, int as_reconstruct_path(int16_t *came_from, int w, int16_t start, int16_t dest,
int16_t dest, bool is_alloc, NPC *npc) { bool is_alloc, NPC *npc) {
if(npc_clear_path(npc)) if(npc_clear_path(npc))
goto as_recons_fail; goto as_recons_fail;
@ -208,8 +204,8 @@ int as_reconstruct_path(int16_t *came_from, int w, int h, int16_t start,
npc->ypath[npc->path_length - i - 1] = ty; npc->ypath[npc->path_length - i - 1] = ty;
}*/ }*/
/*if(is_alloc) if(is_alloc)
free(came_from);*/ free(came_from);
npc->hasPath = true; npc->hasPath = true;
@ -320,7 +316,7 @@ int __npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) {
if(bx == dest_x && by == dest_y) { if(bx == dest_x && by == dest_y) {
if(is_alloc) if(is_alloc)
as_clean(visited, gscore, fscore); as_clean(visited, gscore, fscore);
return 0; /*as_reconstruct_path(came_from, w, h, spos, return 0; /*as_reconstruct_path(came_from, w, spos,
dest_y * w + dest_x, is_alloc npc)*/ dest_y * w + dest_x, is_alloc npc)*/
} }
@ -361,6 +357,13 @@ int __npc_pathfind(int32_t dest_x, int32_t dest_y, Map *full_map, NPC *npc) {
// Refactoring to make adding complexity cleaner // Refactoring to make adding complexity cleaner
void update_npcs(Game *game) { void update_npcs(Game *game) {
uint32_t i; uint32_t i;
for(i = 0; i < NPC_T_Count; i++)
game->npc_ai_dat.team_strength[i] = 0;
for(i = 0; i < npc_count; i++) {
uint16_t type = npc_stack[i].type;
game->npc_ai_dat.team_strength[type] += npc_typedat[type].presence;
}
for(i = 0; i < game->map_level->nbNPC; i++) { for(i = 0; i < game->map_level->nbNPC; i++) {
update_npc(&game->map_level->npcs[i], game); update_npc(&game->map_level->npcs[i], game);
} }
@ -369,35 +372,83 @@ void update_npcs(Game *game) {
} }
} }
void npc_ai(NPC *npc, Game *game){ /*Returns NULL if none are spotted*/
NPC *enemy_spotted(NPC *npc, Game *game) { return NULL; }
#define AI_CHANCE_MAX (0xFFFF * 0xFF)
#define AI_RAND() (rand() & 0xFFFF)
void npc_ai(NPC *npc, Game *game) {
NPC_TypeData *tdat = &npc_typedat[npc->type]; NPC_TypeData *tdat = &npc_typedat[npc->type];
NPC_AI_Dat *aidat = &game->npc_ai_dat;
/*Graph of the logic availiable in ticket #39*/
uint32_t idle_chance = 0; uint32_t idle_chance = 0;
uint32_t wander_chance = 0; uint32_t wander_chance = 0;
uint32_t flee_chance = 0; uint32_t flee_chance = 0;
uint32_t attack_chance = 0; uint32_t attack_chance = 0;
uint64_t temp;
switch(npc->state){ /* Fixed point ratio*/
case NPC_S_IDLE : { uint32_t relative_strength =
/*TODO : Expand conditions to switch states*/ (aidat->team_strength[npc->current_group] << PRECISION) /
aidat->team_strength[npc->hostile_to_group];
break; NPC *spotted = enemy_spotted(npc, game);
}
case NPC_S_WANDER : { switch(npc->state) {
/*TODO : Choose a random close point to pathfind to */ default: {
break;
}
case NPC_S_FLEE : {
/*TODO : Pathfind to a point away from the player*/
break;
}
case NPC_S_ATTACK : {
/*TODO : Attack !*/
break;
}
default : {
/*Get real*/ /*Get real*/
npc->state = NPC_S_IDLE; npc->state = NPC_S_IDLE;
__attribute__((fallthrough));
}
case NPC_S_IDLE: {
if(spotted) {
npc->target = spotted->type;
npc->state = NPC_S_ATTACK;
break;
}
wander_chance = AI_RAND() * tdat->wanderlust;
idle_chance = AI_CHANCE_MAX - wander_chance;
break;
}
case NPC_S_WANDER: {
/*TODO : Choose a random close point to pathfind to */
if(spotted) {
npc->target = spotted->type;
npc->state = NPC_S_ATTACK;
break;
}
idle_chance = AI_RAND() * tdat->lazyness;
wander_chance = AI_CHANCE_MAX - idle_chance;
break;
}
case NPC_S_FLEE: {
/*TODO : Pathfind to a point away from the player*/
/*Avoid overflow*/
temp = (AI_RAND() * tdat->agressivity) * relative_strength;
attack_chance = temp >> PRECISION;
if(!spotted)
wander_chance = AI_RAND() * tdat->cowardice;
flee_chance = AI_CHANCE_MAX - attack_chance - wander_chance;
break;
}
case NPC_S_ATTACK: {
/*TODO : Attack !*/
/*Decide what to do next*/
temp = (AI_RAND() * tdat->cowardice) *
((1 << PRECISION) / relative_strength);
flee_chance = temp >> PRECISION;
if(!spotted)
flee_chance += (tdat->cowardice / 4) * 0xFFFF;
attack_chance = AI_CHANCE_MAX - flee_chance;
break; break;
} }
} }
@ -406,6 +457,7 @@ void npc_ai(NPC *npc, Game *game){
extern const short int walkable_speed[WALKABLE_TILE_MAX]; extern const short int walkable_speed[WALKABLE_TILE_MAX];
void update_npc(NPC *npc, Game *game) { void update_npc(NPC *npc, Game *game) {
/*If he has a brain, poke it with a stick*/
if(npc->type != NPC_Static) if(npc->type != NPC_Static)
npc_ai(npc, game); npc_ai(npc, game);
@ -446,7 +498,7 @@ void update_npc(NPC *npc, Game *game) {
if(vecY > 0) if(vecY > 0)
mpos_y += 1 << PRECISION; mpos_y += 1 << PRECISION;
if(!npc_collision(game, npc, mpos_x, mpos_x)) { if(!npc_collision(game, mpos_x, mpos_x)) {
npc->curx = new_x; npc->curx = new_x;
npc->cury = new_y; npc->cury = new_y;
} }

View file

@ -18,38 +18,18 @@
typedef struct { typedef struct {
/*TODO : Stats*/ /*TODO : Moar stats*/
uint8_t presence; /*How much the NPC adds to team strength*/
/*AI weights - Values on 255*/ /*AI weights - Values on 255*/
uint8_t agressivity; /*Attack*/ uint8_t agressivity; /*Attack*/
uint8_t cowardice; /*Flee*/ uint8_t cowardice; /*Flee*/
uint8_t lazyness; /*Idle*/ uint8_t lazyness; /*Idle*/
uint8_t wanderlust; /*Wandering*/ uint8_t wanderlust; /*Wandering*/
} NPC_TypeData; } NPC_TypeData;
enum {
NPC_Static = 0, /*~= none, disqualifies from all AI*/
NPC_Guard = 1,
NPC_Bandit = 2,
NPC_Monster = 3,
NPC_Type_Count
};
enum {
NPC_T_NONE = 0,
NPC_T_FRIENDLY = 1, /* The player's team */
NPC_T_HOSTILE = 2, /* to the player */
NPC_T_ALL = 3
};
enum {
NPC_S_IDLE = 0,
NPC_S_ATTACK = 1,
NPC_S_FLEE = 2,
NPC_S_WANDER = 3
};
/* /!\ Warning /!\ /* /!\ Warning /!\
* Do not keep hard references to non-static NPCs, as they will likely move * Do not keep hard references to non-static NPCs, as they will likely move
* in the stack */ * in the stack */