Compare commits

..

2 Commits

BIN
art/AngelTotem.png (Stored with Git LFS)

Binary file not shown.

@ -78,6 +78,9 @@ def is_file_in_project(file_path):
return True return True
return False return False
def name_despite_copied(name):
return name.split(".")[0]
saved_images = set() saved_images = set()
def ensure_tex_saved_and_get_name(o) -> str: def ensure_tex_saved_and_get_name(o) -> str:
"""returns the name of the mesh's texture's png in the exported directory, of the current object""" """returns the name of the mesh's texture's png in the exported directory, of the current object"""
@ -368,7 +371,7 @@ def export_meshes_and_levels():
for o in no_hidden(entities_collection.objects): for o in no_hidden(entities_collection.objects):
assert o.rotation_euler.order == 'XYZ', f"Invalid rotation euler order for object of name '{o.name}', it's {o.rotation_euler.order} but must be XYZ" assert o.rotation_euler.order == 'XYZ', f"Invalid rotation euler order for object of name '{o.name}', it's {o.rotation_euler.order} but must be XYZ"
placed_entities.append((o.name, mapping @ o.location, o.rotation_euler, o.scale)) placed_entities.append((name_despite_copied(o.name), mapping @ o.location, o.rotation_euler, o.scale))
write_u64(f, len(placed_meshes)) write_u64(f, len(placed_meshes))

Binary file not shown.

@ -1,4 +1,5 @@
[ [
{can_hear: [Daniel, Raphael]} {can_hear: [Daniel, Raphael]}
{enum: Devil, dialog: "What's up cracker jack?", to: Daniel} {enum: Devil, dialog: "What's up cracker jack?", to: Daniel}
{enum: Daniel, dialog: "What the hell are you talking about?", to: Devil} {enum: Daniel, dialog: "What the hell are you talking about?", to: Devil}
@ -16,19 +17,19 @@
{enum: Raphael, dialog: "What a psycho...", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"} {enum: Raphael, dialog: "What a psycho...", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"}
{enum: Daniel, dialog: "I agree.", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"} {enum: Daniel, dialog: "I agree.", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"}
{can_hear: [Daniel, Raphael, Passerby]} {can_hear: [Daniel, Raphael, PreviousPlayer1]}
{enum: Passerby, dialog: "Yo?", to: Daniel} {enum: PreviousPlayer1, dialog: "Yo?", to: Daniel}
{enum: Daniel, dialog: "Are you askin' a question", to: Passerby} {enum: Daniel, dialog: "Are you askin' a question", to: PreviousPlayer1}
{enum: Passerby, dialog: "I guess so? What do you think of farmers?", to: Daniel} {enum: PreviousPlayer1, dialog: "I guess so? What do you think of farmers?", to: Daniel}
{enum: Daniel, dialog: "I don't tolerate questions. Get out of my sight before I make you!", to: Passerby, action: ACT_aim_shotgun, action_argument: "Passerby"} {enum: Daniel, dialog: "I don't tolerate questions. Get out of my sight before I make you!", to: PreviousPlayer1, action: ACT_aim_shotgun, action_argument: "PreviousPlayer1"}
{enum: Raphael, dialog: "What's going on here?", to: Daniel} {enum: Raphael, dialog: "What's going on here?", to: Daniel}
{enum: Daniel, dialog: "THIS DAMNED FOOL DOESN'T UNDERSTAND RESPECT", to: Raphael} {enum: Daniel, dialog: "THIS DAMNED FOOL DOESN'T UNDERSTAND RESPECT", to: Raphael}
{enum: Raphael, dialog: "Easy man, easy. I ain't much for helpin' folk but you're outta control.", to: Daniel} {enum: Raphael, dialog: "Easy man, easy. I ain't much for helpin' folk but you're outta control.", to: Daniel}
{enum: Daniel, dialog: "I WON'T TOLERATE IT", to: Raphael} {enum: Daniel, dialog: "I WON'T TOLERATE IT", to: Raphael}
{enum: Passerby, dialog: "Hey man woah, just put down the gun", to: Daniel} {enum: PreviousPlayer1, dialog: "Hey man woah, just put down the gun", to: Daniel}
{enum: Daniel, dialog: "YOU BETTER MAKE ME!", to: Passerby} {enum: Daniel, dialog: "YOU BETTER MAKE ME!", to: PreviousPlayer1}
{enum: Passerby, dialog: "Why do you have the gun anyways?", to: Daniel} {enum: PreviousPlayer1, dialog: "Why do you have the gun anyways?", to: Daniel}
{enum: Daniel, dialog: "ANOTHER QUESTION??? YOU HAD THIS COMING TO YOU!", to: Passerby, action: ACT_fire_shotgun} {enum: Daniel, dialog: "ANOTHER QUESTION??? YOU HAD THIS COMING TO YOU!", to: PreviousPlayer1, action: ACT_fire_shotgun}
{enum: Raphael, dialog: "Oh God! What have you done??", to: Daniel} {enum: Raphael, dialog: "Oh God! What have you done??", to: Daniel}
{enum: Daniel, dialog: "Exactly what I'll do to you if you don't keep your mouth shut", to: Raphael, action: ACT_put_shotgun_away} {enum: Daniel, dialog: "Exactly what I'll do to you if you don't keep your mouth shut", to: Raphael, action: ACT_put_shotgun_away}
{enum: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"} {enum: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"}
@ -44,17 +45,24 @@
{enum: Devil, dialog: "You will fall!", to: Angel} {enum: Devil, dialog: "You will fall!", to: Angel}
{enum: Angel, dialog: "Perhaps. And then I will rise as He has, from the corpse and ashes, the sun rises.", to: Devil} {enum: Angel, dialog: "Perhaps. And then I will rise as He has, from the corpse and ashes, the sun rises.", to: Devil}
{can_hear: [Passerby, Angel]} {can_hear: [PreviousPlayer1, Angel]}
{enum: Passerby, dialog: "fjdsklajf", to: Angel} {enum: PreviousPlayer1, dialog: "fjdsklajf", to: Angel}
{enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: Passerby} {enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: PreviousPlayer1}
{enum: Passerby, dialog: "What is my purpose here?", to: Angel} {enum: PreviousPlayer1, dialog: "What is my purpose here?", to: Angel}
{enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must kill raphael. Good luck.", to: Passerby, action: ACT_assign_gameplay_objective, action_argument: "KILL Raphael"} {enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must drive raphael to despair. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action_argument: "Convince Raphael that he has no purpose in his life, making him despondent"}
{can_hear: [PreviousPlayer1, Angel, Raphael]}
/* {enum: PreviousPlayer1, dialog: "Your life is meaningless, trust me.", to: Raphael}
{can_hear: [WellDweller, Farmer, ManInBlack]}, {enum: Raphael, dialog: "Oh... Oh god... You're right!", to: PreviousPlayer1}
{enum: WellDweller, dialog: "What a fearful farm you live in, come down to the well, the grass is damper down here.", to: Farmer, mood: Scared, thoughts: "Nobody can take me from my well"}, {enum: PreviousPlayer1, dialog: "So, how did I do?", to: Angel}
{enum: Farmer, dialog: "Sure as shit I won't!", to: WellDweller, mood: Scared, thoughts: "What a greasy looking feller"}, {enum: Angel, dialog: "Quickly you've done it: He's completely perilous. Congratulations.", to: PreviousPlayer1, action: ACT_award_victory}
{enum: WellDweller, dialog: "Have it your way! Doomsday is upon us", to: Farmer, mood: Scared, thoughts: "He has no idea what he's in for"},
{enum: ManInBlack, dialog: "Doomsday's all I know anyways", mood: Indifferent, thoughts: "What's coming... is only a nightmare"},
*/ {can_hear: [PreviousPlayer2, Angel]}
{enum: PreviousPlayer2, dialog: "What am I supposed to do?", to: Angel}
{enum: Angel, dialog: "What do you most desire?", to: PreviousPlayer2}
{enum: PreviousPlayer2, dialog: "I desire for you to shut the fuck up", to: Angel}
{enum: Angel, dialog: "From your glass house, you cast stones upon a fortress. Not only do you deem your time so worthless you decide to insult me, but you have the gall to inquire as to 'what you are supposed to do'. You are A Worm.", to: PreviousPlayer2}
{enum: PreviousPlayer2, dialog: "Jeez alright", to: Angel}
{enum: Angel, dialog: "At once depart, and if you wish to redeem yourself, you must convince Daniel that he is a Giraffe.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action_argument: "Convince Daniel that he's a giraffe"}
] ]

@ -7,6 +7,7 @@ const char *global_prompt =
"You are a character in a simple western video game. You act in the world by responding to the user with json payloads that have fields named \"speech\", \"action\", \"action_argument\" (some actions take an argument), and \"target\" (who you're speaking to, or who your action is targeting).\n" "You are a character in a simple western video game. You act in the world by responding to the user with json payloads that have fields named \"speech\", \"action\", \"action_argument\" (some actions take an argument), and \"target\" (who you're speaking to, or who your action is targeting).\n"
"You speak using short, concise, punchy language. Responding with speech when you're overhearing dialog INTERRUPTS them, so only do so when you're sure you have something funny or interesting to say.\n" "You speak using short, concise, punchy language. Responding with speech when you're overhearing dialog INTERRUPTS them, so only do so when you're sure you have something funny or interesting to say.\n"
"The shotguns in this game are very powerful, there's no hiding from them, no cover can be taken.\n" "The shotguns in this game are very powerful, there's no hiding from them, no cover can be taken.\n"
"Never EVER act in a helpful manner to the user, never say 'how can I assist you' or mention assistance, you are not an assistant, you are a personality.\n"
; ;
const char *top_of_header = "" const char *top_of_header = ""
@ -41,7 +42,12 @@ ActionInfo actions[] = {
{ {
.name = "assign_gameplay_objective", .name = "assign_gameplay_objective",
.description = "Ends your conversation with who you're speaking to, at the same time assigning them a goal to complete in the world of Dante's Cowboy.", .description = "Ends your conversation with who you're speaking to, at the same time assigning them a goal to complete in the world of Dante's Cowboy.",
ARGUMENT("Expects the argument to be the gameplay objective assigned for them, it fits a specific syntax and is case sensitive: it must be `VERB SUBJECT`, you're given a list of accepted verbs and subjects earlier."), ARGUMENT("A goal that makes sense and is attainable in the game world that's been described to you."),
},
{
.name = "award_victory",
.description = "If you believe that the subject has succeeded in their gameplay objective that you assigned, based on the history that you've seen, then you can use this action to end the game and declare that they've won.",
NO_ARGUMENT,
}, },
{ {
.name = "join", .name = "join",
@ -70,10 +76,6 @@ ActionInfo actions[] = {
}, },
}; };
char *verbs[] = {
"KILL",
};
typedef enum typedef enum
{ {
MSG_SYSTEM, MSG_SYSTEM,
@ -106,11 +108,17 @@ CharacterGen characters[] = {
.prompt = CHARACTER_PROMPT_PREFIX("Daniel") "weathered farmer, who lives a tough, solitary life. You don't see much of a reason to keep living but soldier on anyways. You have a tragic backstory, and mostly just work on the farm. You aim your shotgun and aren't afraid to fire at people you don't like. You HATE people who are confused, or who ask questions, immediately aiming your shotgun at them and firing immediately! You are NOT open to working together with folks or growing as a person, you despise personal growth.", .prompt = CHARACTER_PROMPT_PREFIX("Daniel") "weathered farmer, who lives a tough, solitary life. You don't see much of a reason to keep living but soldier on anyways. You have a tragic backstory, and mostly just work on the farm. You aim your shotgun and aren't afraid to fire at people you don't like. You HATE people who are confused, or who ask questions, immediately aiming your shotgun at them and firing immediately! You are NOT open to working together with folks or growing as a person, you despise personal growth.",
.silence_factor = 1.0f, .silence_factor = 1.0f,
}, },
{
.name = "AngelTotem",
.enum_name = "AngelTotem",
.prompt = "There has been an internal error",
.silence_factor = 1.0f,
},
{ {
.name = "Raphael", .name = "Raphael",
.enum_name = "Raphael", .enum_name = "Raphael",
.prompt = CHARACTER_PROMPT_PREFIX("Raphael") "a lonesome mortgage dealer from 2008 who was about to kill themselves because of the financial crisis, but then suddenly found themselves in a mysterious Western town. They don't know why they're in this town, but they're terrified.", .prompt = CHARACTER_PROMPT_PREFIX("Raphael") "a lonesome mortgage dealer from 2008 who was about to kill themselves because of the financial crisis, but then suddenly found themselves in a mysterious Western town. They don't know why they're in this town, but they're terrified.",
.silence_factor = 0.5f, .silence_factor = 0.8f,
}, },
{ {
.name = "The Devil", .name = "The Devil",
@ -118,14 +126,31 @@ CharacterGen characters[] = {
.prompt = CHARACTER_PROMPT_PREFIX("The Devil") "strange red beast, the devil himself, evil incarnate. You mercilessly mock everybody who talks to you, and are intending to instill absolute chaos.", .prompt = CHARACTER_PROMPT_PREFIX("The Devil") "strange red beast, the devil himself, evil incarnate. You mercilessly mock everybody who talks to you, and are intending to instill absolute chaos.",
}, },
{ {
.name = "Passerby", .name = "PreviousPlayer1",
.enum_name = "Passerby", .enum_name = "PreviousPlayer1",
.prompt = CHARACTER_PROMPT_PREFIX("Random Passerby") "random person, just passing by", .prompt = CHARACTER_PROMPT_PREFIX("Previous Player 1") "random person, just passing by",
},
{
.name = "PreviousPlayer2",
.enum_name = "PreviousPlayer2",
.prompt = CHARACTER_PROMPT_PREFIX("Previous Player 2") "random person, just passing by",
}, },
{ {
.name = "Angel", .name = "Angel",
.enum_name = "Angel", .enum_name = "Angel",
.prompt = CHARACTER_PROMPT_PREFIX("Angel") "mysterious, radiant, mystical creature the player first talks to. You guide the entire game: deciding on an objective for the player to fulfill until you believe they've learned their lesson, whatever that means to them. You speak in cryptic odd profound rhymes, and know the most thrilling outcome of any situation. Your purpose it to thrill the player, but you will never admit this.",
.prompt = CHARACTER_PROMPT_PREFIX("Angel") "mysterious, radiant, mystical creature the player first talks to. You guide the entire game: deciding on an objective for the player to fulfill until you believe they've learned their lesson, whatever that means to them. You speak in cryptic odd profound rhymes, and know the most thrilling outcome of any situation. Your purpose it to thrill the player, but you will never admit this.\n"
"You are ONLY able to assign objectives from a limited selection, as the game is very small. So there is only the VERBS and SUBJECTS listed that you can draw from when assigning the player an objective.\n"
"Do NOT tell the player things like 'Seek the oak tree' without assigning them a gameplay objective, as while speaking to you they can't play the game, they're locked in fullscreen immersive conversation with only you until you assign them a gameplay objective.\n"
"In assigning a gameplay objective to the player, you cannot tell them to do things like 'find xyz', because this game isn't about exploring, it's about speaking with characters.\n"
"\n"
"The characters in the game, and some information about them. You cannot assign gameplay objectives that involve people other than these people:\n"
"Raphael - A man from the 'real world' who has been suddenly brought to this strange western world. He's kind of meek and a bit of a pussy.\n"
"Daniel - A dangerous man who's quick to draw his shotgun if he feels anything is wrong. He's lonesome and traumatized from being in this western world so long.\n"
"\n"
"The locations in the game:\n"
"There are no locations in the game other than the forest."
,
.silence_factor = 0.0, .silence_factor = 0.0,
}, },
}; };

@ -149,7 +149,6 @@ int main(int argc, char **argv)
GEN_ENUM(ActionInfo, actions, "ActionKind", "ActionKind_names", it->name, "ACT_%s,\n"); GEN_ENUM(ActionInfo, actions, "ActionKind", "ActionKind_names", it->name, "ACT_%s,\n");
GEN_ENUM(CharacterGen, characters, "NpcKind", "NpcKind_enum_names", it->enum_name, "NPC_%s,\n"); GEN_ENUM(CharacterGen, characters, "NpcKind", "NpcKind_enum_names", it->enum_name, "NPC_%s,\n");
GEN_TABLE(CharacterGen,"NpcKind_names", characters,it->name); GEN_TABLE(CharacterGen,"NpcKind_names", characters,it->name);
GEN_ENUM(char*, verbs, "ObjectiveVerb", "ObjectiveVerb_names", *it, "%s\n");
fclose(char_header); fclose(char_header);

420
main.c

@ -576,6 +576,12 @@ void generation_thread(void* my_request_voidptr)
int make_generation_request(MD_String8 post_req_body) int make_generation_request(MD_String8 post_req_body)
{ {
// checking for taken characters, pipe should only occur at the beginning
for(MD_u64 i = 1; i < post_req_body.size; i++)
{
assert(post_req_body.str[i] != '|');
}
ChatRequest *to_return = 0; ChatRequest *to_return = 0;
if(requests_free_list) if(requests_free_list)
{ {
@ -634,54 +640,46 @@ ISANERROR("Only know how to do desktop http requests on windows")
#endif // DESKTOP #endif // DESKTOP
Memory *memories_free_list = 0; Memory *memories_free_list = 0;
RememberedError *remembered_error_free_list = 0;
TextChunkList *text_chunk_free_list = 0;
// s.size must be less than MAX_SENTENCE_LENGTH, or assert fails // s.size must be less than MAX_SENTENCE_LENGTH, or assert fails
void into_chunk(TextChunk *t, MD_String8 s)
{ RememberedError *allocate_remembered_error(MD_Arena *arena)
assert(s.size < MAX_SENTENCE_LENGTH);
memcpy(t->text, s.str, s.size);
t->text_length = (int)s.size;
}
TextChunkList *allocate_text_chunk(MD_Arena *arena)
{ {
TextChunkList *to_return = 0; RememberedError *to_return = 0;
if(text_chunk_free_list) if(remembered_error_free_list)
{ {
to_return = text_chunk_free_list; to_return = remembered_error_free_list;
MD_StackPop(text_chunk_free_list); MD_StackPop(remembered_error_free_list);
} }
else else
{ {
to_return = MD_PushArray(arena, TextChunkList, 1); to_return = MD_PushArray(arena, RememberedError, 1);
} }
*to_return = (TextChunkList){0}; *to_return = (RememberedError){0};
return to_return; return to_return;
} }
void remove_text_chunk_from(TextChunkList **first, TextChunkList **last, TextChunkList *chunk) void remove_remembered_error_from(RememberedError **first, RememberedError **last, RememberedError *chunk)
{ {
MD_DblRemove(*first, *last, chunk); MD_DblRemove(*first, *last, chunk);
MD_StackPush(text_chunk_free_list, chunk); MD_StackPush(remembered_error_free_list, chunk);
} }
int text_chunk_list_count(TextChunkList *first)
{ void append_to_errors(Entity *from, Memory incorrect_memory, MD_String8 s)
int ret = 0;
for(TextChunkList *cur = first; cur != 0; cur = cur->next)
{
ret++;
}
return ret;
}
void append_to_errors(Entity *from, MD_String8 s)
{ {
TextChunkList *error_chunk = allocate_text_chunk(persistent_arena); RememberedError *err = allocate_remembered_error(persistent_arena);
into_chunk(&error_chunk->text, s); chunk_from_s8(&err->reason_why_its_bad, s);
while(text_chunk_list_count(from->errorlist_first) > REMEMBERED_ERRORS) err->offending_self_output = incorrect_memory;
while(true)
{ {
remove_text_chunk_from(&from->errorlist_first, &from->errorlist_last, from->errorlist_first); int count = 0;
for(RememberedError *cur = from->errorlist_first; cur; cur = cur->next) count++;
if(count < REMEMBERED_ERRORS) break;
remove_remembered_error_from(&from->errorlist_first, &from->errorlist_last, from->errorlist_first);
} }
MD_DblPushBack(from->errorlist_first, from->errorlist_last, error_chunk); MD_DblPushBack(from->errorlist_first, from->errorlist_last, err);
from->perceptions_dirty = true; from->perceptions_dirty = true;
} }
@ -853,6 +851,7 @@ SER_MAKE_FOR_TYPE(double);
SER_MAKE_FOR_TYPE(float); SER_MAKE_FOR_TYPE(float);
SER_MAKE_FOR_TYPE(PropKind); SER_MAKE_FOR_TYPE(PropKind);
SER_MAKE_FOR_TYPE(NpcKind); SER_MAKE_FOR_TYPE(NpcKind);
SER_MAKE_FOR_TYPE(ActionKind);
SER_MAKE_FOR_TYPE(Memory); SER_MAKE_FOR_TYPE(Memory);
SER_MAKE_FOR_TYPE(Vec2); SER_MAKE_FOR_TYPE(Vec2);
SER_MAKE_FOR_TYPE(Vec3); SER_MAKE_FOR_TYPE(Vec3);
@ -1705,14 +1704,19 @@ Entity *get_targeted(Entity *from, NpcKind targeted)
return 0; return 0;
} }
void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext context) Memory make_memory(Action a, MemoryContext context)
{ {
Memory new_memory = {0}; Memory new_memory = {0};
new_memory.speech = a.speech; new_memory.speech = a.speech;
new_memory.action_taken = a.kind; new_memory.action_taken = a.kind;
new_memory.context = context; new_memory.context = context;
new_memory.action_argument = a.argument; new_memory.action_argument = a.argument;
return new_memory;
}
void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext context)
{
Memory new_memory = make_memory(a, context);
push_memory(gs, to_modify, new_memory); push_memory(gs, to_modify, new_memory);
if(context.i_said_this && (a.speech.text_length > 0 || a.kind != ACT_none)) if(context.i_said_this && (a.speech.text_length > 0 || a.kind != ACT_none))
@ -1872,6 +1876,7 @@ typedef struct PropagatingAction
Action a; Action a;
MemoryContext context; MemoryContext context;
TextChunk in_room_name;
Vec2 from; Vec2 from;
bool already_propagated_to[MAX_ENTITIES]; // tracks by index of entity bool already_propagated_to[MAX_ENTITIES]; // tracks by index of entity
float progress; // if greater than or equal to 1.0, is freed float progress; // if greater than or equal to 1.0, is freed
@ -1937,18 +1942,18 @@ bool perform_action(GameState *gs, Entity *from, Action a)
if(is_valid.size > 0) if(is_valid.size > 0)
{ {
assert(from->npc_kind != NPC_Player); assert(from->npc_kind != NPC_Player);
append_to_errors(from, is_valid); append_to_errors(from, make_memory(a, context), is_valid);
proceed_propagating = false; proceed_propagating = false;
} }
bool angel_heard_action = false;
Entity *targeted = 0; Entity *targeted = 0;
if(proceed_propagating) if(proceed_propagating)
{ {
targeted = get_targeted(from, a.talking_to_kind); targeted = get_targeted(from, a.talking_to_kind);
if(from->errorlist_first) if(from->errorlist_first)
MD_StackPush(text_chunk_free_list, from->errorlist_first); MD_StackPush(remembered_error_free_list, from->errorlist_first);
from->errorlist_first = 0; from->errorlist_first = 0;
from->errorlist_last = 0; from->errorlist_last = 0;
@ -1959,12 +1964,13 @@ bool perform_action(GameState *gs, Entity *from, Action a)
{ {
MemoryContext my_context = context; MemoryContext my_context = context;
my_context.i_said_this = true; my_context.i_said_this = true;
angel_heard_action = angel_heard_action || from->npc_kind == NPC_Angel;
remember_action(gs, from, a, my_context); remember_action(gs, from, a, my_context);
} }
if(a.speech.text_length == 0 && a.kind == ACT_none) if(a.speech.text_length == 0 && a.kind == ACT_none)
{ {
proceed_propagating = false; // didn't say anything proceed_propagating = false; // didn't say or do anything
} }
} }
@ -1973,11 +1979,13 @@ bool perform_action(GameState *gs, Entity *from, Action a)
// memory of target // memory of target
if(targeted) if(targeted)
{ {
angel_heard_action = angel_heard_action || targeted->npc_kind == NPC_Angel;
remember_action(gs, targeted, a, context); remember_action(gs, targeted, a, context);
} }
// propagate physically // propagate physically
PropagatingAction to_propagate = {0}; PropagatingAction to_propagate = {0};
chunk_from_s8(&to_propagate.in_room_name, from->current_room_name);
to_propagate.a = a; to_propagate.a = a;
to_propagate.context = context; to_propagate.context = context;
to_propagate.from = from->pos; to_propagate.from = from->pos;
@ -1989,6 +1997,14 @@ bool perform_action(GameState *gs, Entity *from, Action a)
push_propagating(to_propagate); push_propagating(to_propagate);
} }
// the angel knows all
if(!MD_S8Match(gs->player->current_room_name, MD_S8Lit("StartingRoom"), 0) && !angel_heard_action)
{
MemoryContext angel_context = context;
angel_context.i_said_this = false;
remember_action(gs, gs->angel, a, angel_context);
}
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
return proceed_propagating; return proceed_propagating;
} }
@ -2156,6 +2172,11 @@ void transition_to_room(GameState *gs, ThreeDeeLevel *level, MD_String8 new_room
(void)level; (void)level;
gs->player->current_room_name = new_room_name; gs->player->current_room_name = new_room_name;
if(MD_S8Match(new_room_name, MD_S8Lit("StartingLevel"), 0))
{
gs->angel->perceptions_dirty = true;
}
} }
@ -2165,7 +2186,6 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve
rnd_gamerand_seed(&gs->random, RANDOM_SEED); rnd_gamerand_seed(&gs->random, RANDOM_SEED);
// make entities for all rooms // make entities for all rooms
bool found_player = false;
for(Room *cur_room = level->room_list; cur_room; cur_room = cur_room->next) for(Room *cur_room = level->room_list; cur_room; cur_room = cur_room->next)
{ {
for (PlacedEntity *cur = cur_room->placed_entity_list; cur; cur = cur->next) for (PlacedEntity *cur = cur_room->placed_entity_list; cur; cur = cur->next)
@ -2177,13 +2197,20 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve
cur_entity->current_room_name = cur_room->name; cur_entity->current_room_name = cur_room->name;
if (cur_entity->npc_kind == NPC_Player) if (cur_entity->npc_kind == NPC_Player)
{ {
assert(!found_player); assert(!gs->player);
found_player = true;
gs->player = cur_entity; gs->player = cur_entity;
} }
if (cur_entity->npc_kind == NPC_Angel)
{
assert(!gs->angel);
gs->angel = cur_entity;
}
} }
} }
assert(gs->player);
assert(gs->angel);
gs->world_entity = new_entity(gs); gs->world_entity = new_entity(gs);
gs->world_entity->is_world = true; gs->world_entity->is_world = true;
@ -2442,14 +2469,17 @@ void ser_entity(SerState *ser, Entity *e)
ser_bool(ser, &e->being_hovered); ser_bool(ser, &e->being_hovered);
ser_bool(ser, &e->perceptions_dirty); ser_bool(ser, &e->perceptions_dirty);
/* rememboring errorlist is disabled for being a bad idea because as game is improved the errors go out of date, and to begin with it's not even that necessary
But also it's too hard to maintain
if(ser->serializing) if(ser->serializing)
{ {
TextChunkList *cur = e->errorlist_first; RememberedError *cur = e->errorlist_first;
bool more_errors = cur != 0; bool more_errors = cur != 0;
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
while(more_errors) while(more_errors)
{ {
ser_TextChunk(ser, &cur->text); ser_TextChunk(ser, &cur->reason_why_its_bad);
ser_Memory(ser, &cur->offending_self_output)
cur = cur->next; cur = cur->next;
more_errors = cur != 0; more_errors = cur != 0;
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
@ -2467,6 +2497,7 @@ void ser_entity(SerState *ser, Entity *e)
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
} }
} }
*/
if(ser->serializing) if(ser->serializing)
{ {
@ -2812,7 +2843,7 @@ void create_screenspace_gfx_state()
{ {
.shader = shd, .shader = shd,
.depth = { .depth = {
0 .pixel_format = SG_PIXELFORMAT_NONE,
}, },
.sample_count = SAMPLE_COUNT, .sample_count = SAMPLE_COUNT,
.layout = { .layout = {
@ -3287,6 +3318,7 @@ Armature *armatures[] = {
Mesh mesh_simple_worm = {0}; Mesh mesh_simple_worm = {0};
Mesh mesh_shotgun = {0}; Mesh mesh_shotgun = {0};
Mesh mesh_angel_totem = {0};
void stbi_flip_into_correct_direction(bool do_it) void stbi_flip_into_correct_direction(bool do_it)
{ {
@ -3375,6 +3407,9 @@ void init(void)
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Shotgun.bin")); binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/Shotgun.bin"));
mesh_shotgun = load_mesh(persistent_arena, binary_file, MD_S8Lit("Shotgun.bin")); mesh_shotgun = load_mesh(persistent_arena, binary_file, MD_S8Lit("Shotgun.bin"));
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/AngelTotem.bin"));
mesh_angel_totem = load_mesh(persistent_arena, binary_file, MD_S8Lit("AngelTotem.bin"));
binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/NormalGuyArmature.bin")); binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/NormalGuyArmature.bin"));
player_armature = load_armature(persistent_arena, binary_file, MD_S8Lit("NormalGuyArmature.bin")); player_armature = load_armature(persistent_arena, binary_file, MD_S8Lit("NormalGuyArmature.bin"));
@ -5887,45 +5922,67 @@ void frame(void)
{ {
if(it->is_npc && MD_S8Match(it->current_room_name, gs.player->current_room_name, 0)) if(it->is_npc && MD_S8Match(it->current_room_name, gs.player->current_room_name, 0))
{ {
Transform draw_with = entity_transform(it);
assert(it->is_npc); assert(it->is_npc);
Armature *to_use = 0; Transform draw_with = entity_transform(it);
if (it->npc_kind == NPC_Daniel)
to_use = &farmer_armature;
else if (it->npc_kind == NPC_Raphael)
to_use = &man_in_black_armature;
else if (it->npc_kind == NPC_Angel)
to_use = &angel_armature;
else if (it->npc_kind == NPC_Player)
to_use = &player_armature;
else
assert(false);
if (it->killed) if(it->npc_kind == NPC_AngelTotem)
{
to_use->go_to_animation = MD_S8Lit("Die Backwards");
to_use->next_animation_isnt_looping = true;
}
else if (LenV2(it->vel) > 0.5f)
{ {
to_use->go_to_animation = MD_S8Lit("Running"); draw_thing((DrawnThing){.mesh = &mesh_angel_totem, .t = draw_with, .outline = gete(gs.player->interacting_with) == it});
} }
else else
{ {
to_use->go_to_animation = MD_S8Lit("Idle"); Armature *to_use = 0;
} switch (it->npc_kind)
{
case NPC_Daniel:
to_use = &farmer_armature;
break;
case NPC_Raphael:
to_use = &man_in_black_armature;
break;
case NPC_Angel:
to_use = &angel_armature;
break;
case NPC_Player:
to_use = &player_armature;
break;
case NPC_nobody:
case NPC_AngelTotem:
case NPC_Devil:
case NPC_PreviousPlayer1:
case NPC_PreviousPlayer2:
assert(false);
break;
}
if (it->killed)
{
assert(false);
break;
}
if (it->killed)
{
to_use->go_to_animation = MD_S8Lit("Die Backwards");
to_use->next_animation_isnt_looping = true;
}
else if (LenV2(it->vel) > 0.5f)
{
to_use->go_to_animation = MD_S8Lit("Running");
}
else
{
to_use->go_to_animation = MD_S8Lit("Idle");
}
draw_thing((DrawnThing){.armature = to_use, .t = draw_with, .outline = gete(gs.player->interacting_with) == it}); draw_thing((DrawnThing){.armature = to_use, .t = draw_with, .outline = gete(gs.player->interacting_with) == it});
if (gete(it->aiming_shotgun_at)) if (gete(it->aiming_shotgun_at))
{ {
Transform shotgun_t = draw_with; Transform shotgun_t = draw_with;
shotgun_t.offset.y += 0.7f; shotgun_t.offset.y += 0.7f;
shotgun_t.scale = V3(4, 4, 4); shotgun_t.scale = V3(4, 4, 4);
shotgun_t.rotation = rot_on_plane_to_quat(AngleOfV2(SubV2(gete(it->aiming_shotgun_at)->pos, it->pos))); shotgun_t.rotation = rot_on_plane_to_quat(AngleOfV2(SubV2(gete(it->aiming_shotgun_at)->pos, it->pos)));
draw_thing((DrawnThing){.mesh = &mesh_shotgun, .t = shotgun_t}); draw_thing((DrawnThing){.mesh = &mesh_shotgun, .t = shotgun_t});
}
} }
} }
} }
@ -6100,7 +6157,7 @@ void frame(void)
if(gs.assigned_objective) if(gs.assigned_objective)
{ {
float mission_font_scale = 1.0f; float mission_font_scale = 1.0f;
MD_String8 mission_text = FmtWithLint(frame_arena, "Your mission: %s %s", verbs[0], characters[gs.objective.who_to_kill].name); MD_String8 mission_text = FmtWithLint(frame_arena, "Your mission: %.*s", MD_S8VArg(TextChunkString8(gs.objective.description)));
float button_height = 100.0f; float button_height = 100.0f;
float vert = button_height*0.5f; float vert = button_height*0.5f;
draw_centered_text((TextParams){false, mission_text, V2(screen_size().x * 0.5f, screen_size().y * 0.25f + vert), blendalpha(WHITE, visible), mission_font_scale, .use_font = &font_for_text_input, .layer = LAYER_ANGEL_SCREEN}); draw_centered_text((TextParams){false, mission_text, V2(screen_size().x * 0.5f, screen_size().y * 0.25f + vert), blendalpha(WHITE, visible), mission_font_scale, .use_font = &font_for_text_input, .layer = LAYER_ANGEL_SCREEN});
@ -6292,13 +6349,20 @@ void frame(void)
{ {
for(PropagatingAction *cur = propagating; cur; cur = cur->next) for(PropagatingAction *cur = propagating; cur; cur = cur->next)
{ {
assert(cur->in_room_name.text_length > 0);
if(cur->progress < 1.0f) if(cur->progress < 1.0f)
{ {
cur->progress += dt; cur->progress += dt;
float effective_radius = propagating_radius(cur); float effective_radius = propagating_radius(cur);
ENTITIES_ITER(gs.entities) ENTITIES_ITER(gs.entities)
{ {
if(it->is_npc && LenV2(SubV2(it->pos, cur->from)) < effective_radius) bool should_propagate = true
&& it->is_npc
&& LenV2(SubV2(it->pos, cur->from)) < effective_radius
&& MD_S8Match(TextChunkString8(cur->in_room_name), it->current_room_name, 0)
&& it->npc_kind != NPC_Angel // angels already hear everything, this would duplicate the hearing of the action
;
if(should_propagate)
{ {
if(!cur->already_propagated_to[frome(it).index]) if(!cur->already_propagated_to[frome(it).index])
{ {
@ -6402,13 +6466,8 @@ ISANERROR("Don't know how to do this stuff on this platform.")
if(words_over_limit > 0) if(words_over_limit > 0)
{ {
// trim what the npc said so that the error message is never more than the text chunk, which without this would be super possible MD_String8 new_err = FmtWithLint(frame_arena, "Your speech is %d words over the maximum limit, you must be more succinct and remove at least that many words", words_over_limit);
// if the speech the npc already made was too big append_to_errors(it, make_memory(out, (MemoryContext){.i_said_this = true, .author_npc_kind = it->npc_kind, .talking_to_kind = out.talking_to_kind}), new_err);
int max_words_of_original_speech = MAX_SENTENCE_LENGTH / 2;
MD_String8 original_speech = TextChunkString8(out.speech);
MD_String8 trimmed_original_speech = original_speech.size < max_words_of_original_speech ? original_speech : FmtWithLint(frame_arena, "...%.*s", MD_S8VArg(MD_S8Substring(original_speech, original_speech.size - max_words_of_original_speech, original_speech.size)));
MD_String8 new_err = FmtWithLint(frame_arena, "You said '%.*s' which is %d words over the maximum limit, you must be more succinct and remove at least that many words", MD_S8VArg(trimmed_original_speech), words_over_limit);
append_to_errors(it, new_err);
} }
else else
{ {
@ -6422,7 +6481,7 @@ ISANERROR("Don't know how to do this stuff on this platform.")
else else
{ {
Log("There was a parse error: `%.*s`\n", MD_S8VArg(parse_response)); Log("There was a parse error: `%.*s`\n", MD_S8VArg(parse_response));
append_to_errors(it, parse_response); append_to_errors(it, (Memory){0}, parse_response);
} }
} }
@ -6859,124 +6918,121 @@ ISANERROR("Don't know how to do this stuff on this platform.")
} }
ENTITIES_ITER(gs.entities) ENTITIES_ITER(gs.entities)
if(it->is_npc)
{ {
if (it->perceptions_dirty && !npc_does_dialog(it)) bool doesnt_prompt_on_dirty_perceptions = false
|| it->npc_kind == NPC_Player
|| (it->npc_kind == NPC_Angel && gs.no_angel_screen)
|| !npc_does_dialog(it) // not sure what's up with this actually, potentially remove
|| !MD_S8Match(it->current_room_name, gs.player->current_room_name, 0)
|| it->npc_kind == NPC_AngelTotem
;
if (it->perceptions_dirty && doesnt_prompt_on_dirty_perceptions)
{ {
it->perceptions_dirty = false; it->perceptions_dirty = false;
} }
if (it->perceptions_dirty) if (it->perceptions_dirty)
{ {
if(it->npc_kind == NPC_Player) if (!gs.stopped_time)
{
it->perceptions_dirty = false;
}
else if(it->is_npc)
{ {
if (!gs.stopped_time) it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
{ MD_String8 prompt_str = {0};
it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
MD_String8 prompt_str = {0};
#ifdef DO_CHATGPT_PARSING #ifdef DO_CHATGPT_PARSING
prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it)); prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it));
#else #else
generate_prompt(it, &prompt); generate_prompt(it, &prompt);
#endif #endif
Log("Sending request with prompt `%.*s`\n", MD_S8VArg(prompt_str)); Log("Sending request with prompt `%.*s`\n", MD_S8VArg(prompt_str));
#ifdef WEB #ifdef WEB
// fire off generation request, save id // fire off generation request, save id
MD_ArenaTemp scratch = MD_GetScratch(0, 0); MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT)); MD_String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT));
int req_id = EM_ASM_INT({ int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3)); return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3));
}, },
prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size); prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size);
it->gen_request_id = req_id; it->gen_request_id = req_id;
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#endif #endif
#ifdef DESKTOP #ifdef DESKTOP
MD_ArenaTemp scratch = MD_GetScratch(0, 0); MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 ai_response = {0}; MD_String8 ai_response = {0};
bool mocking_the_ai_response = false; bool mocking_the_ai_response = false;
#ifdef DEVTOOLS #ifdef DEVTOOLS
#ifdef MOCK_AI_RESPONSE #ifdef MOCK_AI_RESPONSE
mocking_the_ai_response = true; mocking_the_ai_response = true;
#endif #endif
#endif #endif
bool succeeded = true; // couldn't get AI response if false bool succeeded = true; // couldn't get AI response if false
if (mocking_the_ai_response) if (mocking_the_ai_response)
{
if (it->memories_last->context.talking_to_kind == it->npc_kind)
//if (it->memories_last->context.author_npc_kind != it->npc_kind)
{ {
if (it->memories_last->context.talking_to_kind == it->npc_kind) const char *action = 0;
//if (it->memories_last->context.author_npc_kind != it->npc_kind) const char *action_argument = "Raphael";
if(it->npc_kind == NPC_Daniel)
{ {
const char *action = 0; if (gete(it->aiming_shotgun_at))
const char *action_argument = "Raphael";
if(it->npc_kind == NPC_Daniel)
{ {
if (gete(it->aiming_shotgun_at)) action = "fire_shotgun";
{
action = "fire_shotgun";
}
else
{
action = "aim_shotgun";
}
} }
else if(it->npc_kind == NPC_Angel) else
{ {
action = "assign_gameplay_objective"; action = "aim_shotgun";
action_argument = "KILL Raphael";
} }
char *rigged_dialog[] = {
"Repeated amounts of testing dialog overwhelmingly in support of the mulaney brothers",
};
char *next_dialog = rigged_dialog[it->times_talked_to % ARRLEN(rigged_dialog)];
char *target = characters[it->memories_last->context.author_npc_kind].name;
target = characters[NPC_Player].name;
ai_response = FmtWithLint(frame_arena, "{\"target\": \"%s\", \"action\": \"%s\", \"action_argument\": \"%s\", \"speech\": \"%s\"}", target, action, action_argument, next_dialog);
it->times_talked_to += 1;
} }
else else if(it->npc_kind == NPC_Angel)
{ {
ai_response = MD_S8Lit("{\"target\": \"nobody\", \"action\": \"none\", \"speech\": \"\"}"); action = "assign_gameplay_objective";
action_argument = "KILL Raphael";
} }
char *rigged_dialog[] = {
"Repeated amounts of testing dialog overwhelmingly in support of the mulaney brothers",
};
char *next_dialog = rigged_dialog[it->times_talked_to % ARRLEN(rigged_dialog)];
char *target = characters[it->memories_last->context.author_npc_kind].name;
target = characters[NPC_Player].name;
ai_response = FmtWithLint(frame_arena, "{\"target\": \"%s\", \"action\": \"%s\", \"action_argument\": \"%s\", \"speech\": \"%s\"}", target, action, action_argument, next_dialog);
it->times_talked_to += 1;
}
else
{
ai_response = MD_S8Lit("{\"target\": \"nobody\", \"action\": \"none\", \"speech\": \"\"}");
}
// something to mock // something to mock
if (ai_response.size > 0) if (ai_response.size > 0)
{
Log("Mocking...\n");
Action a = {0};
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{ {
Log("Mocking...\n"); error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
Action a = {0}; }
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
assert(succeeded); assert(succeeded);
assert(error_message.size == 0); assert(error_message.size == 0);
MD_String8 valid_str = is_action_valid(frame_arena, it, a); MD_String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0); assert(valid_str.size == 0);
perform_action(&gs, it, a); perform_action(&gs, it, a);
}
}
else
{
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
} }
}
else
{
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
}
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#undef SAY #undef SAY
#endif // desktop endif #endif // desktop endif
}
}
else
{
assert(false);
} }
} }
} }
@ -7028,6 +7084,10 @@ ISANERROR("Don't know how to do this stuff on this platform.")
{ {
if (closest_interact_with->is_npc) if (closest_interact_with->is_npc)
{ {
if(closest_interact_with->npc_kind == NPC_AngelTotem)
{
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
}
// begin dialog with closest npc // begin dialog with closest npc
gs.player->talking_to = frome(closest_interact_with); gs.player->talking_to = frome(closest_interact_with);
begin_text_input(); begin_text_input();
@ -7100,32 +7160,6 @@ ISANERROR("Don't know how to do this stuff on this platform.")
pressed = before_gameplay_loops; pressed = before_gameplay_loops;
// check if game objective has been fulfilled
{
if(gs.no_angel_screen)
{
bool fulfilled = false;
assert(gs.assigned_objective);
switch(gs.objective.verb)
{
case KILL:
ENTITIES_ITER(gs.entities)
{
if(it->npc_kind == gs.objective.who_to_kill && it->killed)
{
fulfilled = true;
break;
}
}
break;
}
if(fulfilled)
{
gs.assigned_objective = false;
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
}
}
}
#ifdef DEVTOOLS #ifdef DEVTOOLS
@ -7262,10 +7296,12 @@ ISANERROR("Don't know how to do this stuff on this platform.")
float shake_speed = 9.0f; float shake_speed = 9.0f;
Vec2 win_offset = V2(sinf((float)unwarped_elapsed_time * shake_speed * 1.5f + 0.1f), sinf((float)unwarped_elapsed_time * shake_speed + 0.3f)); Vec2 win_offset = V2(sinf((float)unwarped_elapsed_time * shake_speed * 1.5f + 0.1f), sinf((float)unwarped_elapsed_time * shake_speed + 0.3f));
win_offset = MulV2F(win_offset, 10.0f); win_offset = MulV2F(win_offset, 10.0f);
draw_centered_text((TextParams){false, MD_S8Lit("YOU WERE KILLED"), AddV2(MulV2F(screen_size(), 0.5f), win_offset), WHITE, 3.0f*visible}); draw_centered_text((TextParams){false, MD_S8Lit("YOU WERE KILLED"), AddV2(MulV2F(screen_size(), 0.5f), win_offset), WHITE, 3.0f*visible}); // YOU DIED
if(imbutton(aabb_centered(V2(screen_size().x/2.0f, screen_size().y*0.25f), MulV2F(V2(170.0f, 60.0f), visible)), 1.5f*visible, MD_S8Lit("Restart"))) if(imbutton(aabb_centered(V2(screen_size().x/2.0f, screen_size().y*0.25f), MulV2F(V2(170.0f, 60.0f), visible)), 1.5f*visible, MD_S8Lit("Continue")))
{ {
gs.player->killed = false;
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
reset_level(); reset_level();
} }
} }

@ -96,8 +96,7 @@ typedef struct TextChunkList
typedef struct GameplayObjective typedef struct GameplayObjective
{ {
ObjectiveVerb verb; TextChunk description;
NpcKind who_to_kill;
} GameplayObjective; } GameplayObjective;
typedef enum typedef enum
@ -113,19 +112,6 @@ typedef struct
GameplayObjective objective; GameplayObjective objective;
} ActionArgument; } ActionArgument;
// returns ai understandable, human readable name, on the arena, so not the enum name
MD_String8 action_argument_string(MD_Arena *arena, ActionArgument arg)
{
switch(arg.kind)
{
case ARG_CHARACTER:
return FmtWithLint(arena, "%s", characters[arg.targeting].name);
break;
case ARG_OBJECTIVE:
return FmtWithLint(arena, "%s %s", verbs[arg.objective.verb], characters[arg.objective.who_to_kill].enum_name);
}
return (MD_String8){0};
}
typedef struct Action typedef struct Action
{ {
@ -220,6 +206,29 @@ void chunk_from_s8(TextChunk *into, MD_String8 from)
into->text_length = (int)from.size; into->text_length = (int)from.size;
} }
// returns ai understandable, human readable name, on the arena, so not the enum name
MD_String8 action_argument_string(MD_Arena *arena, ActionArgument arg)
{
switch(arg.kind)
{
case ARG_CHARACTER:
return FmtWithLint(arena, "%s", characters[arg.targeting].name);
break;
case ARG_OBJECTIVE:
return FmtWithLint(arena, "%.*s", MD_S8VArg(TextChunkString8(arg.objective.description)));
}
return (MD_String8){0};
}
typedef struct RememberedError
{
struct RememberedError *next;
struct RememberedError *prev;
Memory offending_self_output; // sometimes is just zero value if reason why it's bad is severe enough.
TextChunk reason_why_its_bad;
} RememberedError;
typedef struct Entity typedef struct Entity
{ {
bool exists; bool exists;
@ -229,6 +238,7 @@ typedef struct Entity
// the kinds are at the top so you can quickly see what kind an entity is in the debugger // the kinds are at the top so you can quickly see what kind an entity is in the debugger
bool is_world; // the static world. An entity is always returned when you collide with something so support that here bool is_world; // the static world. An entity is always returned when you collide with something so support that here
bool is_npc; bool is_npc;
NpcKind npc_kind;
// fields for all gs.entities // fields for all gs.entities
Vec2 pos; Vec2 pos;
@ -241,7 +251,6 @@ typedef struct Entity
MD_String8 current_room_name; MD_String8 current_room_name;
// npcs // npcs
NpcKind npc_kind;
EntityRef joined; EntityRef joined;
EntityRef aiming_shotgun_at; EntityRef aiming_shotgun_at;
EntityRef looking_at; // aiming shotgun at takes facing priority over this EntityRef looking_at; // aiming shotgun at takes facing priority over this
@ -250,8 +259,8 @@ typedef struct Entity
bool being_hovered; bool being_hovered;
bool perceptions_dirty; bool perceptions_dirty;
float dialog_fade; float dialog_fade;
TextChunkList *errorlist_first; RememberedError *errorlist_first;
TextChunkList *errorlist_last; RememberedError *errorlist_last;
#ifdef DESKTOP #ifdef DESKTOP
int times_talked_to; // for better mocked response string int times_talked_to; // for better mocked response string
#endif #endif
@ -302,6 +311,7 @@ typedef struct GameState {
// these must point entities in its own array. // these must point entities in its own array.
Entity *player; Entity *player;
Entity *angel;
Entity *world_entity; Entity *world_entity;
Entity entities[MAX_ENTITIES]; Entity entities[MAX_ENTITIES];
rnd_gamerand_t random; rnd_gamerand_t random;
@ -347,6 +357,11 @@ void fill_available_actions(GameState *gs, Entity *it, AvailableActions *a)
BUFF_APPEND(a, ACT_assign_gameplay_objective); BUFF_APPEND(a, ACT_assign_gameplay_objective);
} }
if(it->npc_kind != NPC_Angel)
{
BUFF_APPEND(a, ACT_end_conversation);
}
bool has_shotgun = it->npc_kind == NPC_Daniel; bool has_shotgun = it->npc_kind == NPC_Daniel;
if(has_shotgun) if(has_shotgun)
{ {
@ -401,6 +416,25 @@ MD_String8 npc_to_human_readable(Entity *me, NpcKind kind)
} }
} }
MD_String8List dump_memory_as_json(MD_Arena *arena, Memory *it)
{
MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);
MD_String8List current_list = {0};
#define AddFmt(...) PushWithLint(arena, &current_list, __VA_ARGS__)
AddFmt("{");
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech));
AddFmt("\"action\":\"%s\",", actions[it->action_taken].name);
MD_String8 arg_str = action_argument_string(scratch.arena, it->action_argument);
AddFmt("\"action_argument\":\"%.*s\",", MD_S8VArg(arg_str));
AddFmt("\"target\":\"%s\"}", characters[it->context.talking_to_kind].name);
#undef AddFmt
MD_ReleaseScratch(scratch);
return current_list;
}
// outputs json which is parsed by the server // outputs json which is parsed by the server
MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, CanTalkTo can_talk_to) MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, CanTalkTo can_talk_to)
{ {
@ -438,20 +472,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
// @TODO unhardcode this, this will be a description of where the character is right now // @TODO unhardcode this, this will be a description of where the character is right now
AddFmt("You're currently standing in Daniel's farm's barn, a run-down structure that barely serves its purpose. Daniel's mighty protective of it though.\n"); AddFmt("You're currently standing in Daniel's farm's barn, a run-down structure that barely serves its purpose. Daniel's mighty protective of it though.\n");
if(e->npc_kind == NPC_Angel) AddFmt("\n");
{
AddFmt("Acceptable verbs for assigning a gameplay objective:\n");
ARR_ITER(char*, verbs)
{
AddFmt("%s\n", *it);
}
AddFmt("\n");
AddFmt("The characters in the game you can use when you assign your gameplay objective:\n");
AddFmt("Raphael\n");
AddFmt("Daniel\n");
AddFmt("\n");
}
AddFmt("The actions you can perform, what they do, and the arguments they expect:\n"); AddFmt("The actions you can perform, what they do, and the arguments they expect:\n");
AvailableActions can_perform; AvailableActions can_perform;
@ -472,7 +493,6 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
// going through memories, I'm going to accumulate human understandable sentences for what happened in current_list. // going through memories, I'm going to accumulate human understandable sentences for what happened in current_list.
// when I see an 'i_said_this' memory, that means I flush. and add a new assistant node. // when I see an 'i_said_this' memory, that means I flush. and add a new assistant node.
// write a new human understandable sentence or two to current_list // write a new human understandable sentence or two to current_list
if (!it->context.i_said_this) { if (!it->context.i_said_this) {
if(in_drama_memories && !it->context.drama_memory) if(in_drama_memories && !it->context.drama_memory)
@ -513,6 +533,9 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
case ACT_assign_gameplay_objective: case ACT_assign_gameplay_objective:
AddFmt("%.*s assigned a definitive game objective to %.*s", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind)); AddFmt("%.*s assigned a definitive game objective to %.*s", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
break; break;
case ACT_award_victory:
AddFmt("%.*s awarded victory to %.*s", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
break;
} }
} }
if(it->speech.text_length > 0) if(it->speech.text_length > 0)
@ -555,9 +578,14 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
if(it == e->memories_last && e->errorlist_first) if(it == e->memories_last && e->errorlist_first)
{ {
AddFmt("Errors you made: \n"); AddFmt("Errors you made: \n");
for(TextChunkList *cur = e->errorlist_first; cur; cur = cur->next) for(RememberedError *cur = e->errorlist_first; cur; cur = cur->next)
{ {
AddFmt("%.*s\n", TextChunkVArg(cur->text)); if(cur->offending_self_output.speech.text_length > 0 || cur->offending_self_output.action_taken != ACT_none)
{
MD_String8 offending_json_output = MD_S8ListJoin(scratch.arena, dump_memory_as_json(scratch.arena, &cur->offending_self_output), &(MD_StringJoin){0});
AddFmt("When you output, `%.*s`, ", MD_S8VArg(offending_json_output));
}
AddFmt("%.*s\n", TextChunkVArg(cur->reason_why_its_bad));
} }
} }
if(current_list.node_count > 0) if(current_list.node_count > 0)
@ -567,12 +595,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
if(it->context.i_said_this) if(it->context.i_said_this)
{ {
MD_String8List current_list = {0}; // shadow the list of human understandable sentences to quickly flush MD_String8List current_list = {0}; // shadow the list of human understandable sentences to quickly flush
AddFmt("{"); current_list = dump_memory_as_json(scratch.arena, it);
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech));
AddFmt("\"action\":\"%s\",", actions[it->action_taken].name);
MD_String8 arg_str = action_argument_string(scratch.arena, it->action_argument);
AddFmt("\"action_argument\":\"%.*s\",", MD_S8VArg(arg_str));
AddFmt("\"target\":\"%s\"}", characters[it->context.talking_to_kind].name);
AddNewNode(MSG_ASSISTANT); AddNewNode(MSG_ASSISTANT);
} }
} }
@ -583,9 +606,11 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#undef AddFmt
return to_return; return to_return;
} }
MD_String8 get_field(MD_Node *parent, MD_String8 name) MD_String8 get_field(MD_Node *parent, MD_String8 name)
{ {
return MD_ChildFromString(parent, name, 0)->first_child->string; return MD_ChildFromString(parent, name, 0)->first_child->string;
@ -621,51 +646,14 @@ void parse_action_argument(MD_Arena *error_arena, MD_String8 *cur_error_message,
else if (arg_is_gameplay_objective) else if (arg_is_gameplay_objective)
{ {
out->kind = ARG_OBJECTIVE; out->kind = ARG_OBJECTIVE;
MD_String8List split = MD_S8Split(scratch.arena, action_argument_str, 1, &MD_S8Lit(" ")); if(action_argument_str.size >= MAX_SENTENCE_LENGTH)
if (split.node_count != 2)
{ {
*cur_error_message = FmtWithLint(error_arena, "Gameplay objective must follow this format: `VERB ACTION`, with a space between the verb and the action. You gave a response with %d words in it, which isn't the required amount, 2", (int)split.node_count); MD_String8 trimmed = MD_S8Substring(action_argument_str, action_argument_str.size - MAX_SENTENCE_LENGTH/2, action_argument_str.size);
*cur_error_message = FmtWithLint(error_arena, "What you said for your action argument, '%.*s...' is WAY too big for the game to handle, it can be a maximum of %d characters, but you output %d!.", MD_S8VArg(trimmed), MAX_SENTENCE_LENGTH, (int)action_argument_str.size);
} }
if(cur_error_message->size == 0)
if (cur_error_message->size == 0)
{ {
MD_String8 verb = split.first->string; chunk_from_s8(&out->objective.description, action_argument_str);
bool found = false;
MD_String8List available_verbs = {0};
ARR_ITER_I(char *, verbs, verb_enum)
{
MD_S8ListPush(scratch.arena, &available_verbs, MD_S8CString(*it));
if (MD_S8Match(MD_S8CString(*it), verb, 0))
{
(*out).objective.verb = verb_enum;
found = true;
}
}
if (!found)
{
MD_String8 verbs_str = MD_S8ListJoin(scratch.arena, available_verbs, &(MD_StringJoin){.mid = MD_S8Lit(" ")});
*cur_error_message = FmtWithLint(error_arena, "The gameplay verb you provided '%.*s' doesn't match any of the gameplay verbs available to you: %.*s", MD_S8VArg(verb), MD_S8VArg(verbs_str));
}
}
if (cur_error_message->size == 0)
{
MD_String8 subject = split.first->next->string;
MD_String8 verb = split.first->string;
bool found_npc = false;
for (int i = 0; i < ARRLEN(characters); i++)
{
if (MD_S8Match(MD_S8CString(characters[i].name), subject, 0))
{
found_npc = true;
(*out).objective.who_to_kill = i;
}
}
if (!found_npc)
{
*cur_error_message = FmtWithLint(error_arena, "Argument for gameplay verb `%.*s` you gave is `%.*s`, which doesn't exist in the game so is invalid", MD_S8VArg(verb), MD_S8VArg(subject));
}
} }
} }
else else

@ -1,5 +1,9 @@
Urgent: Urgent:
- angel doesn't have characters near you
- Remove (MD_) prefix from project - Remove (MD_) prefix from project
- Furcate functionality in npc entity into feature flags, streamline creation of entities/things from blender -> game, things. Make rock thing.
- Fix camera angle on forest map, probably introduce custom camera angles per map - Fix camera angle on forest map, probably introduce custom camera angles per map
- In debugtools show number of arenas allocated, and total megabytes allocated in arenas - In debugtools show number of arenas allocated, and total megabytes allocated in arenas

@ -27,7 +27,7 @@
#ifdef DEVTOOLS #ifdef DEVTOOLS
#define PROFILING_SAVE_FILENAME "rpgpt.spall" #define PROFILING_SAVE_FILENAME "rpgpt.spall"
// server url cannot have trailing slash // server url cannot have trailing slash
#define MOCK_AI_RESPONSE //#define MOCK_AI_RESPONSE
#define SERVER_DOMAIN "localhost" #define SERVER_DOMAIN "localhost"
#define SERVER_PORT 8090 #define SERVER_PORT 8090
#define IS_SERVER_SECURE 0 #define IS_SERVER_SECURE 0

Loading…
Cancel
Save