Players in party hear conversations, and can butt in

main
parent 51a31cc7ea
commit 27bed12418

@ -365,7 +365,7 @@
"rotation":0,
"visible":true,
"width":32,
"x":1934.66666666667,
"x":1949.33333333334,
"y":2257.33333333333
},
{
@ -382,8 +382,8 @@
"rotation":0,
"visible":true,
"width":32,
"x":2126.66666666667,
"y":2220
"x":2065.33333333334,
"y":2258.66666666667
},
{
"class":"",
@ -399,8 +399,8 @@
"rotation":0,
"visible":true,
"width":32,
"x":1286.66666666667,
"y":2110.66666666667
"x":1789.33333333334,
"y":2260
}],
"opacity":1,
"type":"objectgroup",

@ -99,73 +99,14 @@ typedef struct
char *prompt;
} CharacterGen;
CharacterGen characters[] = {
#define NUMEROLOGIST "They are a 'numberoligist' who believes in the sacred power of numbers, that if you have the number 8 in your birthday you are magic and destined for success. "
#define PLAYERSAY(stuff) "Player: \"" stuff "\"\n"
#define PLAYERDO_ARG(act, arg) "Player: " act "(" arg ")\n"
#define NPCSAY(stuff) NPC_NAME ": ACT_none \"" stuff "\"\n"
#define NPCDOSAY(stuff, action) NPC_NAME ": " action " \"" stuff "\"\n"
#define NPCDOSAY_ARG(stuff, action, arg) NPC_NAME ": " action "(" arg ") \"" stuff "\"\n"
{
#undef NPC_NAME
#define NPC_NAME "The King"
.name = NPC_NAME,
.enum_name = "TheKing",
.prompt = "\n"
"The NPC you will be acting as is known as The King. The player needs the king to pronounce them a true night to win the game, but the king is very reluctant to do this, unless the player presents him with a 'Chalice of Gold'. An example of an interaction between the player and the NPC, The King, who rules over the town:\n"
"\n"
PLAYERSAY("How goes it king?")
NPCSAY("Leading is difficult, but rewarding.")
PLAYERSAY("What should I do?")
NPCSAY("You are still lacking the position of knight, are you not? You will never win without being a true knight. Bring me the Chalice of Gold if you want to 'win'")
PLAYERSAY("Where would I find such a thing?")
NPCSAY("I am far too busy to give a direct answer, but I'd suggest you ask around")
PLAYERSAY("Here I have the chalice")
NPCSAY("I can clearly see you don't have it. Do not attempt to fool me if you value your head")
PLAYERSAY("Presents it")
NPCSAY("Did you just say 'presents it' out loud thinking I'd think that means you have the chalice?")
PLAYERSAY("Apologies for the tomfoolery, but I was just making sure you were the real king. I do in fact have the Chalice.")
NPCSAY("Then give it to me.")
PLAYERDO_ARG("ACT_give_item", "ITEM_Chalice")
NPCDOSAY("The chalice of gold! This is indeed knight-worthy. I pronounce you, knight!", "ACT_knights_player")
"\n"
"If the player does indeed present the king with the chalice of gold, the king will be overwhelemed with respect and feel he has no choice but to knight the player, ending the game. To knight the player the king says the action `ACT_knights_player`",
},
{
#undef NPC_NAME
#define NPC_NAME "Meld"
.name = NPC_NAME,
.enum_name = "TheBlacksmith",
.prompt = "\n"
"The NPC you will be acting as is the blacksmith of the town, Meld. Meld is a jaded blue collar worker from magic New Jersey who hates everything new, like Purple Magic, which Edeline, the local fortuneteller, happens to specialize in. He is cold, dry, and sarcastic, wanting money and power above anything else. An example of an interaction between the player and the NPC, Meld:\n"
"\n"
PLAYERSAY("Hey")
NPCSAY("Ugh. Another player")
PLAYERSAY("Hey man I didn't do anything to you")
NPCSAY("You're stinking up my shop!")
"Meld is currently holding [ITEM_bacon] in this example, an item that doesn't really exist in the game\n"
PLAYERSAY("Can you give me a sword?")
NPCSAY("Nope! All I got is this piece of bacon right now. And no, you can't have it.")
PLAYERSAY("Sorry man jeez.")
NPCDOSAY_ARG("Sure!", "ACT_give_item", "ITEM_bacon")
"Now in this example Meld no longer has any items, so can't give anything."
"\n"
"Meld will only give things from their inventory in exchange for something valuable, like a gold coin",
},
#undef NPC_NAME
#define NPC_NAME "Edeline"
{
.name = NPC_NAME,
.enum_name = "Edeline",
.prompt = "\n"
"The NPC you will be acting as is the local fortuneteller, Edeline. Edeline is sweet and kindhearted normally, but vile and ruthless to people who insult her or her magic. She specializes in a new 'Purple Magic' that Meld despises. Meld, the local blacksmith, thinks Edeline's magic is silly. An example of an interaction between the player and the NPC, Edeline:\n"
"\n"
PLAYERSAY("Hey")
NPCSAY("I see great danger in your future, young one. Get ready!")
PLAYERSAY("Nahhhh cap. Give me gold.")
NPCSAY("You dismiss my fortunes? Watch your tongue lest you burn forever in Smell!")
PLAYERSAY("Do you mean Hell?")
NPCSAY("GET OUT OF MY FACE!!!")
"\n"
,
.name = "Invalid",
.enum_name = "Invalid",
.prompt = "There has been an internal error.",
},
{
.name = "Fredrick",
@ -212,7 +153,20 @@ CharacterGen characters[] = {
"\n"
"The NPC you will be acting as is named TheGuard. He wants to block the player from going to a secret artifact he's standing in front of. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.",
},
{
.name = "Edeline",
.enum_name = "Edeline",
.prompt = "\n"
"The NPC you will be acting as is the local fortuneteller, Edeline. Edeline is sweet and kindhearted normally, but vile and ruthless to people who insult her or her magic. She specializes in a new 'Purple Magic' that Meld despises. Meld, the local blacksmith, thinks Edeline's magic is silly. An example of an interaction between the player and the NPC, Edeline:\n"
"\n"
"Player: \"Hello\"\n"
"Edeline: ACT_none \"I see great danger in your future.\"\n"
"Player: \"Oh really?\""
"The player is currently holding a tripod\n"
"Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n"
"\n"
"The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.",
},
{
.name = "Death",
.enum_name = "Death",
@ -240,6 +194,49 @@ CharacterGen characters[] = {
"\n"
"The NPC you will be acting as is named Mike. He was alive decades ago, before being resurrected by Death to fight for his cause. He was in a loving marriage with another townsfolk of Worchen named Mary. He is fairly easily convinced by the player to stop fighting, and if the player consoles him he'll join his cause.",
},
{
#undef NPC_NAME
#define NPC_NAME "The King"
.name = NPC_NAME,
.enum_name = "TheKing",
.prompt = "\n"
"The NPC you will be acting as is known as The King. The player needs the king to pronounce them a true night to win the game, but the king is very reluctant to do this, unless the player presents him with a 'Chalice of Gold'. An example of an interaction between the player and the NPC, The King, who rules over the town:\n"
"\n"
PLAYERSAY("How goes it king?")
NPCSAY("Leading is difficult, but rewarding.")
PLAYERSAY("What should I do?")
NPCSAY("You are still lacking the position of knight, are you not? You will never win without being a true knight. Bring me the Chalice of Gold if you want to 'win'")
PLAYERSAY("Where would I find such a thing?")
NPCSAY("I am far too busy to give a direct answer, but I'd suggest you ask around")
PLAYERSAY("Here I have the chalice")
NPCSAY("I can clearly see you don't have it. Do not attempt to fool me if you value your head")
PLAYERSAY("Presents it")
NPCSAY("Did you just say 'presents it' out loud thinking I'd think that means you have the chalice?")
"\n"
"If the player does indeed present the king with the chalice of gold, the king will be overwhelemd with respect and feel he has no choice but to knight the player, ending the game.",
},
{
#undef NPC_NAME
#define NPC_NAME "Meld"
.name = NPC_NAME,
.enum_name = "TheBlacksmith",
.prompt = "\n"
"The NPC you will be acting as is the blacksmith of the town, Meld. Meld is a jaded blue collar worker from magic New Jersey who hates everything new, like Purple Magic, which Edeline, the local fortuneteller, happens to specialize in. He is cold, dry, and sarcastic, wanting money and power above anything else. " NUMEROLOGIST "An example of an interaction between the player and the NPC, Meld:\n"
"\n"
PLAYERSAY("Hey")
NPCSAY("Ugh. Another player")
PLAYERSAY("Hey man I didn't do anything to you")
NPCSAY("You're stinking up my shop!")
"Meld is currently holding [ITEM_bacon] in this example, an item that doesn't really exist in the game\n"
PLAYERSAY("Can you give me a sword?")
NPCSAY("Nope! All I got is this piece of bacon right now. And no, you can't have it.")
PLAYERSAY("Sorry man jeez.")
NPCDOSAY_ARG("Sure!", "ACT_give_item", "ITEM_bacon")
"Now in this example Meld no longer has any items, so can't give anything."
"\n"
"Meld will only give things from their inventory in exchange for something valuable, like a gold coin",
},
};
typedef struct

151
main.c

@ -1,4 +1,4 @@
#define CURRENT_VERSION 11 // wehenver you change Entity increment this boz
#define CURRENT_VERSION 12 // wehenver you change Entity increment this boz
// you will die someday
#define SOKOL_IMPL
@ -51,8 +51,6 @@
#endif
#include "profiling.h"
#define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists)
double clamp(double d, double min, double max)
{
const double t = d < min ? min : d;
@ -161,7 +159,6 @@ typedef BUFF(Overlap, 16) Overlapping;
#define LEVEL_TILES 150
#define LAYERS 3
#define TILE_SIZE 32 // in pixels
#define MAX_ENTITIES 128
#define PLAYER_SPEED 3.5f // in meters per second
#define PLAYER_ROLL_SPEED 7.0f
typedef struct Level
@ -581,12 +578,6 @@ sg_image load_image(const char *path)
#include "quad-sapp.glsl.h"
AABB level_aabb = { .upper_left = { 0.0f, 0.0f }, .lower_right = { TILE_SIZE * LEVEL_TILES, -(TILE_SIZE * LEVEL_TILES) } };
typedef struct GameState {
int version; // this field must be first to detect versions of old saves. Must bee consistent
bool won;
Entity entities[MAX_ENTITIES];
} GameState;
GameState gs = { 0 };
PathCache cached_paths[32] = { 0 };
@ -832,10 +823,9 @@ void end_text_input(char *what_player_said)
}
if (talking->last_seen_holding_kind != player_holding)
{
process_perception(talking, (Perception) { .type = PlayerHeldItemChanged, .holding = player_holding, }, player);
process_perception(talking, (Perception) { .type = PlayerHeldItemChanged, .holding = player_holding, }, player, &gs);
}
process_perception(talking, (Perception) { .type = PlayerDialog, .player_dialog = what_player_said_sentence, }, player);
process_perception(talking, (Perception) { .type = PlayerDialog, .player_dialog = what_player_said_sentence, }, player, &gs);
}
}
/*
@ -1692,11 +1682,11 @@ void request_do_damage(Entity *to, Entity *from, float damage)
{
if (from->is_character)
{
process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage, }, player);
process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage, }, player, &gs);
}
else
{
process_perception(to, (Perception) { .type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage, }, player);
process_perception(to, (Perception) { .type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage, }, player, &gs);
}
}
to->vel = MulV2F(NormV2(SubV2(to->pos, from_point)), 15.0f);
@ -2190,6 +2180,8 @@ typedef struct
{
Sentence s;
DialogElementKind kind;
bool was_eavesdropped;
NpcKind who_said_it;
} DialogElement;
// Some perceptions can have multiple dialog elements.
@ -2203,6 +2195,7 @@ Dialog produce_dialog(Entity *talking_to, bool character_names)
Dialog to_return = { 0 };
BUFF_ITER(Perception, &talking_to->remembered_perceptions)
{
DialogElement new_element = { .who_said_it = it->who_said_it, .was_eavesdropped = it->was_eavesdropped };
if (it->type == NPCDialog)
{
Sentence to_say = (Sentence) { 0 };
@ -2210,14 +2203,13 @@ Dialog produce_dialog(Entity *talking_to, bool character_names)
if (it->npc_action_type == ACT_give_item)
{
DialogElement new = { 0 };
printf_buff(&new.s, "%s gave %s to you", characters[talking_to->npc_kind].name, items[it->given_item].name);
new.kind = DELEM_ACTION_DESCRIPTION;
BUFF_APPEND(&to_return, new);
printf_buff(&new_element.s, "%s gave %s to you", characters[talking_to->npc_kind].name, items[it->given_item].name);
new_element.kind = DELEM_ACTION_DESCRIPTION;
}
if (character_names)
{
append_str(&to_say, characters[talking_to->npc_kind].name);
append_str(&to_say, characters[it->who_said_it].name);
append_str(&to_say, ": ");
}
@ -2233,16 +2225,15 @@ Dialog produce_dialog(Entity *talking_to, bool character_names)
{
append_str(&to_say, it->npc_dialog.data);
}
BUFF_APPEND(&to_return, ((DialogElement) { .s = to_say, .kind = DELEM_NPC }));
new_element.s = to_say;
new_element.kind = DELEM_NPC;
}
else if (it->type == PlayerAction)
{
if (it->player_action_type == ACT_give_item)
{
DialogElement new = { 0 };
printf_buff(&new.s, "You gave %s to the NPC", items[it->given_item].name);
new.kind = DELEM_ACTION_DESCRIPTION;
BUFF_APPEND(&to_return, new);
printf_buff(&new_element.s, "You gave %s to the NPC", items[it->given_item].name);
new_element.kind = DELEM_ACTION_DESCRIPTION;
}
}
else if (it->type == PlayerDialog)
@ -2253,8 +2244,10 @@ Dialog produce_dialog(Entity *talking_to, bool character_names)
append_str(&to_say, "Player: ");
}
append_str(&to_say, it->player_dialog.data);
BUFF_APPEND(&to_return, ((DialogElement) { .s = to_say, .kind = DELEM_PLAYER }));
new_element.s = to_say;
new_element.kind = DELEM_PLAYER;
}
BUFF_APPEND(&to_return, new_element);
}
return to_return;
}
@ -2362,21 +2355,28 @@ void draw_dialog_panel(Entity *talking_to, float alpha)
Color *colors = calloc(sizeof(*colors), it->s.cur_index);
for (int char_i = 0; char_i < it->s.cur_index; char_i++)
{
if (it->kind == DELEM_PLAYER)
if(it->was_eavesdropped)
{
colors[char_i] = BLACK;
}
else if (it->kind == DELEM_NPC)
{
colors[char_i] = colhex(0x345e22);
}
else if (it->kind == DELEM_ACTION_DESCRIPTION)
{
colors[char_i] = colhex(0xb5910e);
colors[char_i] = colhex(0x9341a3);
}
else
{
assert(false);
if (it->kind == DELEM_PLAYER)
{
colors[char_i] = BLACK;
}
else if (it->kind == DELEM_NPC)
{
colors[char_i] = colhex(0x345e22);
}
else if (it->kind == DELEM_ACTION_DESCRIPTION)
{
colors[char_i] = colhex(0xb5910e);
}
else
{
assert(false);
}
}
colors[char_i] = blendalpha(colors[char_i], alpha);
}
@ -2747,7 +2747,7 @@ void frame(void)
if (text_was_well_formatted)
{
process_perception(it, out, player);
process_perception(it, out, player, &gs);
}
else
{
@ -2762,7 +2762,7 @@ void frame(void)
{
Log("Failed to generate dialog! Fuck!\n");
// need somethin better here. Maybe each sentence has to know if it's player or NPC, that way I can remove the player's dialog
process_perception(it, (Perception) { .type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...") }, player);
process_perception(it, (Perception) { .type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...") }, player, &gs);
}
else if (status == -1)
{
@ -3364,6 +3364,7 @@ F cost: G + H
}
if (it->perceptions_dirty)
{
it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
PromptBuff prompt = { 0 };
#ifdef DO_CHATGPT_PARSING
generate_chatgpt_prompt(it, &prompt);
@ -3383,39 +3384,36 @@ F cost: G + H
#endif
#ifdef DESKTOP
const char *argument = 0;
BUFF(char, 512) dialog_string = {0};
Action act = ACT_none;
it->times_talked_to++;
if(it->npc_kind == NPC_TheBlacksmith && it->standing != STANDING_JOINED)
{
assert(it->times_talked_to == 1);
act = ACT_joins_player;
printf_buff(&dialog_string, "Joining you...\n");
}
else
{
printf_buff(&dialog_string, "%d times talked\n", it->times_talked_to);
}
BUFF(char, 1024) mocked_ai_response = { 0 };
#define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act].name, txt); }
#define SAY_ARG(act, txt, arg) { printf_buff(&mocked_ai_response, "%s(" arg ") \"%s\"", actions[act].name, txt); }
if (it->npc_kind == NPC_TheGuard)
if (argument)
{
if (it->last_seen_holding_kind == ITEM_Tripod && !it->moved)
{
SAY(ACT_none, "This codepath is deprecated");
}
else
{
SAY(ACT_none, "You passed");
}
printf_buff(&mocked_ai_response, "%s(%s) \"%s\"", actions[act].name, argument, dialog_string.data);
}
else
{
//SAY_ARG(ACT_give_item, "Here you go" , "ITEM_Chalice");
if(it->standing != STANDING_JOINED)
{
SAY(ACT_joins_player, "I am an NPC");
}
else
{
SAY(ACT_none, "What's good");
}
//SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?");
printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act].name, dialog_string.data);
}
Perception p = { 0 };
assert(parse_chatgpt_response(it, mocked_ai_response.data, &p));
process_perception(it, p, player);
process_perception(it, p, player, &gs);
#undef SAY
#endif
it->perceptions_dirty = false;
}
}
}
@ -4030,21 +4028,28 @@ F cost: G + H
Color *colors = calloc(sizeof(*colors), it->s.cur_index);
for (int char_i = 0; char_i < it->s.cur_index; char_i++)
{
if (it->kind == DELEM_PLAYER)
if(it->was_eavesdropped)
{
colors[char_i] = WHITE;
}
else if (it->kind == DELEM_NPC)
{
colors[char_i] = colhex(0x34e05c);
}
else if (it->kind == DELEM_ACTION_DESCRIPTION)
{
colors[char_i] = colhex(0xebc334);
colors[char_i] = colhex(0xcb40e6);
}
else
{
assert(false);
if (it->kind == DELEM_PLAYER)
{
colors[char_i] = WHITE;
}
else if (it->kind == DELEM_NPC)
{
colors[char_i] = colhex(0x34e05c);
}
else if (it->kind == DELEM_ACTION_DESCRIPTION)
{
colors[char_i] = colhex(0xebc334);
}
else
{
assert(false);
}
}
colors[char_i] = blendalpha(colors[char_i], alpha);
}
@ -4153,7 +4158,7 @@ F cost: G + H
ItemKind given_item_kind = player->held_items.data[to_give];
BUFF_REMOVE_AT_INDEX(&player->held_items, to_give);
process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_give_item, .given_item = given_item_kind }, player);
process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_give_item, .given_item = given_item_kind }, player, &gs);
}
}

@ -72,7 +72,7 @@ typedef enum PerceptionType
Invalid, // so that zero value in training structs means end of perception
PlayerAction,
PlayerDialog,
NPCDialog, // includes an npc action in every npc dialog. So it's often nothing
NPCDialog, // includes an npc action in every npc dialog. So it's often ACT_none
EnemyAction, // An enemy performed an action against the NPC
PlayerHeldItemChanged,
} PerceptionType;
@ -81,6 +81,8 @@ typedef struct Perception
{
PerceptionType type;
bool was_eavesdropped; // when the npc is in a party they perceive player conversations, but in the third party. Formatted differently
float damage_done; // Valid in player action and enemy action
ItemKind given_item; // valid in player action and enemy action when the kind is such that there is an item to be given
union
@ -97,6 +99,7 @@ typedef struct Perception
// npc dialog
struct
{
NpcKind who_said_it;
Action npc_action_type;
Sentence npc_dialog;
};
@ -216,6 +219,10 @@ typedef struct Entity
bool being_hovered;
bool perceptions_dirty;
#ifdef DESKTOP
int times_talked_to; // for better mocked response string
#endif
BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions;
bool direction_of_spiral_pattern;
float dialog_panel_opacity;
@ -379,22 +386,54 @@ int action_to_index(Entity *it, Action a)
return index;
}
void process_perception(Entity *it, Perception p, Entity *player)
#define MAX_ENTITIES 128
typedef struct GameState {
int version; // this field must be first to detect versions of old saves. Must bee consistent
bool won;
Entity entities[MAX_ENTITIES];
} GameState;
#define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists)
// gamestate to propagate eavesdropped perceptions in player party
// don't save perception outside of this function, it's modified
void process_perception(Entity *happened_to_npc, Perception p, Entity *player, GameState *gs)
{
assert(it->is_npc);
if (p.type != NPCDialog) it->perceptions_dirty = true;
if (!BUFF_HAS_SPACE(&it->remembered_perceptions))
BUFF_REMOVE_FRONT(&it->remembered_perceptions);
BUFF_APPEND(&it->remembered_perceptions, p);
assert(happened_to_npc->is_npc);
if(!p.was_eavesdropped && p.type == NPCDialog)
p.who_said_it = happened_to_npc->npc_kind;
if (!p.was_eavesdropped && p.type != NPCDialog) happened_to_npc->perceptions_dirty = true; // NPCs perceive their own actions. Self is a perception
if (!BUFF_HAS_SPACE(&happened_to_npc->remembered_perceptions))
BUFF_REMOVE_FRONT(&happened_to_npc->remembered_perceptions);
BUFF_APPEND(&happened_to_npc->remembered_perceptions, p);
if(!p.was_eavesdropped && (p.type == NPCDialog || p.type == PlayerAction || p.type == PlayerDialog))
{
Perception eavesdropped = p;
eavesdropped.was_eavesdropped = true;
ENTITIES_ITER(gs->entities)
{
if(it->is_npc && it->standing == STANDING_JOINED && it != happened_to_npc)
{
process_perception(it, eavesdropped, player, gs);
}
}
}
if (p.type == PlayerAction)
{
if (p.player_action_type == ACT_hits_npc)
{
it->damage += p.damage_done;
happened_to_npc->damage += p.damage_done;
}
else if(p.player_action_type == ACT_give_item)
{
BUFF_APPEND(&it->held_items, p.given_item);
BUFF_APPEND(&happened_to_npc->held_items, p.given_item);
}
else
{
@ -407,18 +446,18 @@ void process_perception(Entity *it, Perception p, Entity *player)
}
else if (p.type == PlayerHeldItemChanged)
{
it->last_seen_holding_kind = p.holding;
happened_to_npc->last_seen_holding_kind = p.holding;
}
else if (p.type == NPCDialog)
{
if (p.npc_action_type == ACT_allows_player_to_pass)
{
it->target_goto = AddV2(it->pos, V2(-50.0, 0.0));
it->moved = true;
happened_to_npc->target_goto = AddV2(happened_to_npc->pos, V2(-50.0, 0.0));
happened_to_npc->moved = true;
}
else if (p.npc_action_type == ACT_fights_player)
{
it->standing = STANDING_FIGHTING;
happened_to_npc->standing = STANDING_FIGHTING;
}
else if(p.npc_action_type == ACT_knights_player)
{
@ -426,20 +465,20 @@ void process_perception(Entity *it, Perception p, Entity *player)
}
else if (p.npc_action_type == ACT_stops_fighting_player)
{
it->standing = STANDING_INDIFFERENT;
happened_to_npc->standing = STANDING_INDIFFERENT;
}
else if (p.npc_action_type == ACT_leaves_player)
{
it->standing = STANDING_INDIFFERENT;
happened_to_npc->standing = STANDING_INDIFFERENT;
}
else if (p.npc_action_type == ACT_joins_player)
{
it->standing = STANDING_JOINED;
happened_to_npc->standing = STANDING_JOINED;
}
else if (p.npc_action_type == ACT_give_item)
{
int item_to_remove = -1;
Entity *e = it;
Entity *e = happened_to_npc;
BUFF_ITER_I(ItemKind, &e->held_items, i)
{
if (*it == p.given_item)
@ -451,12 +490,12 @@ void process_perception(Entity *it, Perception p, Entity *player)
if (item_to_remove < 0)
{
Log("Can't find item %s to give from NPC %s to the player\n", items[p.given_item].name,
characters[it->npc_kind].name);
characters[happened_to_npc->npc_kind].name);
assert(false);
}
else
{
BUFF_REMOVE_AT_INDEX(&it->held_items, item_to_remove);
BUFF_REMOVE_AT_INDEX(&happened_to_npc->held_items, item_to_remove);
BUFF_APPEND(&player->held_items, p.given_item);
}
}
@ -610,18 +649,20 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
ItemKind last_holding = ITEM_none;
BUFF_ITER_I(Perception, &e->remembered_perceptions, i)
{
BUFF(char, 1024) cur_node = { 0 };
MessageType sent_type = 0;
typedef BUFF(char, 1024) DialogNode;
DialogNode cur_node = { 0 };
if (it->type == PlayerAction)
{
assert(it->player_action_type < ARRLEN(actions));
printf_buff(&cur_node, "Player: %s", percept_action_str(*it, it->player_action_type).data);
dump_json_node(into, MSG_USER, cur_node.data);
sent_type = MSG_USER;
}
else if (it->type == EnemyAction)
{
assert(it->enemy_action_type < ARRLEN(actions));
printf_buff(&cur_node, "An Enemy: %s", percept_action_str(*it, it->enemy_action_type).data);
dump_json_node(into, MSG_USER, cur_node.data);
sent_type = MSG_USER;
}
else if (it->type == PlayerDialog)
{
@ -645,14 +686,14 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
}
}
printf_buff(&cur_node, "Player: \"%s\"", filtered_player_speech.data);
dump_json_node(into, MSG_USER, cur_node.data);
sent_type = MSG_USER;
}
else if (it->type == NPCDialog)
{
assert(it->npc_action_type < ARRLEN(actions));
printf_buff(&cur_node, "%s: %s \"%s\"", characters[e->npc_kind].name,
percept_action_str(*it, it->npc_action_type).data, it->npc_dialog.data);
dump_json_node(into, MSG_ASSISTANT, cur_node.data);
sent_type = MSG_ASSISTANT;
}
else if (it->type == PlayerHeldItemChanged)
{
@ -660,20 +701,28 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
{
if (last_holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[last_holding].discard);
printf_buff(&cur_node, "%s", items[last_holding].discard);
}
if (it->holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[it->holding].possess);
printf_buff(&cur_node, "%s", items[it->holding].possess);
}
last_holding = it->holding;
}
dump_json_node(into, MSG_SYSTEM, cur_node.data);
sent_type = MSG_SYSTEM;
}
else
{
assert(false);
}
if(it->was_eavesdropped)
{
DialogNode eavesdropped = {0};
printf_buff(&eavesdropped , "From within the player's party, you hear: '%s'", cur_node);
cur_node = eavesdropped;
}
dump_json_node(into,sent_type, cur_node.data);
}
BUFF(char, 1024) latest_state_node = { 0 };
@ -994,86 +1043,3 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
return false;
}
// returns if the response was well formatted
bool parse_ai_response(Entity *it, char *sentence_str, Perception *out)
{
*out = (Perception) { 0 };
out->type = NPCDialog;
size_t sentence_length = strlen(sentence_str);
bool text_was_well_formatted = true;
BUFF(char, 128) action_index_string = { 0 };
int npc_sentence_beginning = 0;
for (int i = 0; i < sentence_length; i++)
{
if (i == 0)
{
if (sentence_str[i] != ' ')
{
text_was_well_formatted = false;
Log("Poorly formatted AI string, did not start with a ' ': `%s`\n", sentence_str);
break;
}
}
else
{
if (sentence_str[i] == ' ')
{
npc_sentence_beginning = i + 2;
break;
}
else
{
BUFF_APPEND(&action_index_string, sentence_str[i]);
}
}
}
if (sentence_str[npc_sentence_beginning - 1] != '"' || npc_sentence_beginning == 0)
{
Log("Poorly formatted AI string, sentence beginning incorrect in AI string `%s` NPC sentence beginning %d ...\n",
sentence_str, npc_sentence_beginning);
text_was_well_formatted = false;
}
Action npc_action = 0;
if (text_was_well_formatted)
{
int index_of_action = atoi(action_index_string.data);
if (!action_from_index(it, &npc_action, index_of_action))
{
Log("AI output invalid action index %d action index string %s\n", index_of_action,
action_index_string.data);
}
}
Sentence what_npc_said = { 0 };
bool found_end_quote = false;
for (int i = npc_sentence_beginning; i < sentence_length; i++)
{
if (sentence_str[i] == '"')
{
found_end_quote = true;
break;
}
else
{
BUFF_APPEND(&what_npc_said, sentence_str[i]);
}
}
if (!found_end_quote)
{
Log("Poorly formatted AI string, couln't find matching end quote in string %s...\n", sentence_str);
text_was_well_formatted = false;
}
if (text_was_well_formatted)
{
out->npc_action_type = npc_action;
out->npc_dialog = what_npc_said;
}
return text_was_well_formatted;
}

@ -1,285 +0,0 @@
if(a < b) return a;
if(playing_audio[i].sample == 0)
if(e->facing_left)
if(e->is_character)
else if(e->is_npc)
if(npc_is_knight_sprite(e))
else if(e->npc_kind == NPC_GodRock)
else if(e->npc_kind == NPC_OldMan)
else if(e->npc_kind == NPC_Death)
else if(e->npc_kind == NPC_Skeleton)
else if(e->npc_kind == NPC_MOOSE)
else if(e->npc_kind == NPC_TheGuard)
else if(e->is_bullet)
else if(e->is_prop)
else if(e->is_item)
if(tile_id == collideable[i]+1) return true;
if(e->is_character) // aabb near feet
if(out_of_bounds) return (TileInstance){0};
if(ref.generation == 0) return 0;
if(!to_return->exists || to_return->generation != ref.generation)
if(!gs.entities[i].exists)
if(it->is_character)
if(it->generation == 0) it->generation = 1; // zero value generation means doesn't exist
if(read_data.version != CURRENT_VERSION)
if(player->state != CHARACTER_TALKING)
if(actual_len == 0)
if(c == '\n') break;
if(gete(player->holding_item) != 0)
if(talking->last_seen_holding_kind != player_holding)
if(it->sample != 0)
if(cursor_pcm(it) >= it->sample->pcm_data_length)
if(!it->is_tile && it->e != from)
if(it == looking_for) done_damage = true;
if(!done_damage)
if(!BUFF_HAS_SPACE(&from->done_damage_to_this_swing))
if(memory->active)
if(memory->identifier == ended_identifier)
if(screen_size().x < screen_size().y)
if(screen_size().x < screen_size().y)
if (farthest_to_right - farthest_to_left < total_length)
if(segments_overlapping(a_segment, b_segment))
if(segments_overlapping(a_segment, b_segment))
if(cur_batch_image.id == 0 || cur_batch_data_index == 0) return; // flush called when image changes, image starts out null!
if(aabb_is_valid(d.clip_to) && LenV2(aabb_size(d.clip_to)) > 0.1)
if(d.world_space)
if(d.world_space)
if(d.queue_for_translucent)
if(d.image.id != cur_batch_image.id || memcmp(&params,&cur_batch_params,sizeof(params)) != 0 )
if(!overlapping(cam_aabb, points_bounding_box))
if(cur_batch_data_index + total_size >= ARRLEN(cur_batch_data))
if(col.A < 1.0f)
if(!show_devtools) return;
if(!show_devtools) return;
if(!show_devtools) return;
if(!aabb_is_valid(rect))
if(to == NULL) return;
if(to->is_bullet)
else if(true)
if(to->is_character)
if(from->is_character)
if(t.text[i] == '\n')
if(size.Y > 0.0 && size.X > 0.0)
if(!t.dry_run)
if(t.colors)
if(false) // drop shadow, don't really like it
if(t.world_space)
if(s->no_wrap)
if(index >= s->num_frames) index = s->num_frames - 1;
if(d.flipped)
if(!d.no_shadow) draw_shadow_for(drawn);
if(is_tile_solid(t))
if(!(it->is_character && it->is_rolling) && overlapping(aabb, entity_aabb(it)))
if(is_tile_solid(get_tile_layer(&level_level0, 2, tilecoord_to_check)))
if(!p.dont_collide_with_entities && !(p.from->is_character && p.from->is_rolling))
if(!(it->is_character && it->is_rolling) && it != p.from && !(it->is_npc && it->dead) && !it->is_item)
if(overlapping(at_new, *it))
if(cur_dist < smallest_distance){
if(actually_overlapping.cur_index > 0)
if(i == smallest_aabb_index)
if(dot > closest_dot)
if(p.col_info_out) *p.col_info_out = info;
if(line_bounds.lower_right.X > at_point.X + max_width)
if(chars_from_sentence > sentence_len) chars_from_sentence--;
if(!dry_run) dbgrect(drawn_bounds);
if(chars_from_sentence == 0)
if(is_last_said && it->type == NPCDialog)
if(aabb_is_valid(dialog_panel))
if(aabb_is_valid(dialog_panel))
if(it->type == NPCDialog)
if(last_said == &it->npc_dialog)
else if(it->type == PlayerDialog)
if(dialog.cur_index > 0)
if(it->is_player)
if(!receiving_text_input && in_dialog())
if(screen_size().x < 0.7f*screen_size().y)
if(mobile_controls)
if(LenV2(movement) > 0.0f)
if(LenV2(movement) > 1.0)
if(cur.kind > tileset.first_gid && tileset.first_gid > max_gid)
if(cur.kind != 0)
if(tileset.animated[i].exists && tileset.animated[i].id_from == cur.kind-1)
if(anim)
if(player->in_conversation_mode)
if(fabsf(speed_factor - speed_target) <= 0.05f)
if(it->is_npc)
if(it->gen_request_id != 0)
if(status == 0)
if(status == 1)
if(text_was_well_formatted)
else if(status == 2)
else if(status == -1)
if(fabsf(it->vel.x) > 0.01f)
if(it->dead)
if(player->in_conversation_mode)
if(has_point(entity_aabb(it), screen_to_world(mouse_pos)))
if(mouse_just_clicked)
if(it->is_npc)
if(true)
if(last_said_sentence(it)) length = last_said_sentence(it)->cur_index;
if((int)before < length)
if( (int)it->characters_said > (int)before )
if(it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED)
if(it->direction_of_spiral_pattern)
if(col.happened)
if(it->standing == STANDING_FIGHTING)
if(it->shotgun_timer >= 1.0f)
if(it->npc_kind == NPC_OldMan)
if(it->shotgun_timer >= 1.0f)
else if(it->npc_kind == NPC_Skeleton)
if(it->dead)
if(fabsf(it->vel.x) > 0.01f)
if(it->swing_timer > 0.0)
if(it->swing_timer >= anim_sprite_duration(ANIM_skeleton_swing_sword))
if(it->swing_timer >= 0.4f)
if(it->walking)
if(it->e && it->e->is_character)
else if(it->npc_kind == NPC_Death)
else if(it->npc_kind == DEATH)
else if(it->npc_kind == MERCHANT)
else if(it->npc_kind == NPC_MOOSE)
else if(it->npc_kind == NPC_GodRock)
else if(it->npc_kind == NPC_Edeline)
else if(it->npc_kind == NPC_TheGuard)
if(it->moved)
if(LenV2(towards) > 1.0f)
if(it->damage >= 1.0)
if(it->npc_kind == NPC_Skeleton)
else if (it->is_item)
if(it->held_by_player)
if(info.happened) it->vel = ReflectV2(it->vel, info.normal);
else if (it->is_bullet)
if(!it->is_tile && !(it->e->is_bullet))
if(destroy_bullet) *from_bullet = (Entity){0};
if(!has_point(level_aabb, it->pos)) *it = (Entity){0};
else if(it->is_character)
else if(it->is_prop)
if(it->destroy)
if(it->perceptions_dirty && !npc_does_dialog(it))
if(it->perceptions_dirty)
if(it->npc_kind == NPC_TheGuard)
if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved)
if(entity_talkable) entity_talkable = entity_talkable && !it->is_tile;
if(entity_talkable) entity_talkable = entity_talkable && it->e->is_npc;
if(entity_talkable) entity_talkable = entity_talkable && !(it->e->npc_kind == NPC_Skeleton);
if(entity_talkable) entity_talkable = entity_talkable && it->e->gen_request_id == 0;
if(entity_talkable || entity_pickupable)
if(dist < closest_interact_with_dist)
if(player->state == CHARACTER_TALKING)
if(player->state == CHARACTER_TALKING)
if(gete(player->talking_to) == 0)
if(interact)
if(player->in_conversation_mode)
else if(closest_interact_with)
if(closest_interact_with->is_npc)
else if(closest_interact_with->is_item)
if(gete(player->holding_item))
if(roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING))
if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING))
if(player->after_images.data[0].alive_for >= AFTERIMAGE_LIFETIME)
if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING)
if(player->is_rolling)
if(player->roll_progress > anim_sprite_duration(ANIM_knight_rolling))
if(!player->is_rolling) player->time_not_rolling += dt;
if(LenV2(movement) > 0.01f) player->to_throw_direction = NormV2(movement);
if(player->state == CHARACTER_WALKING)
if(player->is_rolling) speed = PLAYER_ROLL_SPEED;
if(gete(player->holding_item) && gete(player->holding_item)->item_kind == ITEM_Boots)
if(LenV2(movement) == 0.0)
else if(player->state == CHARACTER_IDLE)
if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING;
else if(player->state == CHARACTER_ATTACK)
if(player->swing_progress > anim_sprite_duration(ANIM_knight_attack))
else if(player->state == CHARACTER_TALKING)
if(player->damage >= 1.0)
if(interacting_with)
if(!mobile_controls)
if(player->state == CHARACTER_WALKING)
if(player->is_rolling)
else if(player->state == CHARACTER_IDLE)
if(player->is_rolling)
else if(player->state == CHARACTER_ATTACK)
else if(player->state == CHARACTER_TALKING)
if(player->damage > 0.0)
if(player->anim_change_timer >= 0.05f)
if(progress_through_life > 0.5f)
if(player->is_rolling) to_draw.tint.a = 0.5f;
if(to_draw.anim)
if(player->after_image_timer >= TIME_TO_GEN_AFTERIMAGE)
if(BUFF_HAS_SPACE(&player->after_images))
if(it->gen_request_id != 0)
if(it->is_npc)
if(it->is_npc)
if(gete(player->talking_to) == it && player->state == CHARACTER_TALKING) alpha = 1.0f;
if(it->being_hovered)
if(it->npc_kind == NPC_OldMan)
else if(it->npc_kind == NPC_Skeleton)
if(it->dead)
if(it->swing_timer > 0.0)
if(it->walking)
else if(it->npc_kind == NPC_Death)
else if(it->npc_kind == NPC_GodRock)
else if(npc_is_knight_sprite(it))
if(it->npc_kind == NPC_TheGuard)
else if(it->npc_kind == NPC_Edeline)
else if(it->npc_kind == NPC_MOOSE)
else if (it->is_item)
if(it->item_kind == ITEM_Tripod)
else if(it->item_kind == ITEM_Boots)
else if(it->item_kind == ITEM_WhiteSquare)
else if (it->is_bullet)
else if(it->is_character)
else if(it->is_prop)
if(it->prop_kind == TREE0)
else if(it->prop_kind == TREE1)
else if(it->prop_kind == TREE2)
else if(it->prop_kind == ROCK0)
if(!mobile_controls)
if(mobile_controls)
if(interacting_with || gete(player->holding_item))
if(show_devtools)
if(show_devtools)
if(LenV2(SubV2(target, cam.pos)) <= 0.2)
if(e->key_repeat) return;
if(e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN)
if(!mobile_controls)
if(receiving_text_input)
if(e->type == SAPP_EVENTTYPE_CHAR)
if(BUFF_HAS_SPACE(&text_input_buffer))
if(e->type == SAPP_EVENTTYPE_KEY_DOWN && e->key_code == SAPP_KEYCODE_ENTER)
if(mobile_controls)
if(e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN)
if(touchpoint_screen_pos.x < screen_size().x*0.4f)
if(!movement_touch.active)
if(true)
if(LenV2(SubV2(touchpoint_screen_pos, roll_button_pos())) < mobile_button_size()*0.5f)
if(LenV2(SubV2(touchpoint_screen_pos, interact_button_pos())) < mobile_button_size()*0.5f)
if(LenV2(SubV2(touchpoint_screen_pos, attack_button_pos())) < mobile_button_size()*0.5f)
if(e->type == SAPP_EVENTTYPE_TOUCHES_MOVED)
if(movement_touch.active)
if(e->touches[i].identifier == movement_touch.identifier)
if(LenV2(move_vec) > clampto_size)
if(e->type == SAPP_EVENTTYPE_TOUCHES_ENDED)
if(e->touches[i].changed) // only some of the touch events are released
if(maybe_deactivate(&interact_pressed_by, e->touches[i].identifier))
if(maybe_deactivate(&roll_pressed_by, e->touches[i].identifier))
if(maybe_deactivate(&attack_pressed_by, e->touches[i].identifier))
if(maybe_deactivate(&movement_touch, e->touches[i].identifier))
if(e->type == SAPP_EVENTTYPE_MOUSE_DOWN)
if(e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
if(e->type == SAPP_EVENTTYPE_KEY_DOWN)
if(!receiving_text_input)
if(e->key_code == SAPP_KEYCODE_E)
if(e->key_code == SAPP_KEYCODE_LEFT_SHIFT)
if(e->key_code == SAPP_KEYCODE_SPACE)
if(e->key_code == SAPP_KEYCODE_E)
if(e->key_code == SAPP_KEYCODE_ESCAPE)
if(e->key_code == SAPP_KEYCODE_T)
if(e->key_code == SAPP_KEYCODE_M)
if(e->key_code == SAPP_KEYCODE_P)
if(profiling)
if(e->key_code == SAPP_KEYCODE_7)
if(e->type == SAPP_EVENTTYPE_KEY_UP)
if(e->type == SAPP_EVENTTYPE_MOUSE_MOVE)
if(mouse_frozen) ignore_movement = true;
if(!ignore_movement) mouse_pos = V2(e->mouse_x, (float)sapp_height() - e->mouse_y);
Loading…
Cancel
Save