{enum: NPC_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: NPC_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: NPC_Farmer, dialog: "Sure as shit I won't!", to: WellDweller, mood: Scared, thoughts: "What a greasy looking feller"},
{enum: NPC_Farmer, dialog: "Sure as shit I won't!", to: WellDweller, mood: Scared, thoughts: "What a greasy looking feller"},
{enum: NPC_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: NPC_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: NPC_ManInBlack, dialog: "Doomsday's all I know anyways", mood: Indifferent, thoughts: "What's coming... is only a nightmare"},
{enum: NPC_ManInBlack, dialog: "Doomsday's all I know anyways", mood: Indifferent, thoughts: "What's coming... is only a nightmare"},
// @TODO allow AI to prefix out of character statemetns with [ooc], this is a well khnown thing on role playing forums so gpt would pick up on it.
// @TODO allow AI to prefix out of character statemetns with [ooc], this is a well khnown thing on role playing forums so gpt would pick up on it.
constchar*global_prompt="You are acting as a character in a Western video game, who remembers important memories from the conversation history and stays in character.\n"
constchar*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).";
"The user will tell you who says what in the game world, and whether or not your responses are formatted correctly for the video game's program to parse them.\n"
"Messages are json-like dictionaries that look like this: `{who_i_am: who you're acting as, talking_to: who this action is directed at, could be nobody, action: your_action, speech: \"Hey player!\", thoughts: \"Your thoughts\"}`. The required fields are `action`, `who_i_am`, and `talking_to` \n"
"Some actions take an argument, which you can provide with the field `action_arg`, e.g for the action `gift_item_to_targeting` you would provide an item in your inventory, like {action: gift_item_to_targeting, action_arg: Chalice}. The item must come from your inventory which is listed below\n"
"`talking_to` provides the name of who the action is directed towards. Use 'nobody' if you just want to speak to the air, but if you're speaking to somebody fill out the field like `talking_to: \"Character's Name\"`. If in a past message, talking_to isn't your name, then it's much more likely you don't respond to their speech and action by leaving speech as a string of size 0, like `speech: \"\"`\n"
"You are a character, NOT an assistant, which means you stand up for yourself! Do NOT give away an item until the player gives you something you think is of equal value. Get angry, act human, be interesting. Never say the words 'How can I assist you?'\n"
"Keep your responses SHORT! The shorter the better. Give the player the least amount of text to read. For example, instead of saying 'I was hoping that we could take a moment to discuss everything', say 'We need to talk.'\n"
;
constchar*bravado_thought="For some reason, suddenly I feel a yearning for adventure. I must join any adventure I can when prompted!";
char*moods[]={
"Indifferent",
"Happy",
"Anxious",
"Scared",
"Angry",
"Sad",
"Depressed",
"Bored",
"Terrified",
"Agony",
"Confused",
"Curious",
"Excited",
"Elated",
"Confident",
"Enraged",
};
constchar*top_of_header=""
constchar*top_of_header=""
"#pragma once\n"
"#pragma once\n"
@ -41,40 +13,25 @@ typedef struct
{
{
char*name;// the same as enum name
char*name;// the same as enum name
char*description;
char*description;
char*argument_description;
booltakes_argument;
booltakes_argument;
}ActionInfo;
}ActionInfo;
ActionInfoactions[]={
ActionInfoactions[]={
#define NO_ARGUMENT .argument_description = "Takes no argument", .takes_argument = false
{
{
.name="none",
.name="none",
.description="Do nothing, you can still perform speech if you want.",
.description="Give the player an item from your inventory. This means you WILL NOT HAVE the item anymore",
},
},
{
{
.name="joins_player",
.name="join",
.description="Follow behind the player and hear all of their conversations. You can leave at any time",
.description="Joins somebody else's party, so you follow them everywhere",
.argument_description="Expects the argument to be who you're joining",
},
},
{
{
.name="leaves_player",
.name="leave",
.description="Leave the player",
.description="Leave the party you're in right now",
},
NO_ARGUMENT,
};
typedefstruct
{
char*enum_name;
char*name;// talked about like 'The Player gave `item.name` to the NPC'
char*description;// this field is required for items.
}ItemInfo;
ItemInfoitems[]={
{
.enum_name="invalid",
.name="Invalid",
.description="There has been an internal error.",
},
},
};
};
@ -90,66 +47,27 @@ typedef struct
char*name;
char*name;
char*enum_name;
char*enum_name;
char*prompt;
char*prompt;
char*writing_style[8];
}CharacterGen;
}CharacterGen;
CharacterGencharacters[]={
CharacterGencharacters[]={
#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 CHARACTER_PROMPT_PREFIX "You specifically are acting as a "
.prompt="The farmer wakes up, does his chores, and sleeps in the farmhouse all on his lonesome. He's tinkering with something fishy in the barn, but's mighty secretive about it. He used to have a wife, and suddenly she disappeared. The farmer gets MIGHTY angry if you question him about what's in his barn under the tarp, or what happened to his wife and family, but is otherwise a kind hearted soul.",
.prompt=CHARACTER_PROMPT_PREFIX"weathered farmer named Daniel, 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.",
},
{
.name="Well Dweller",
.enum_name="WellDweller",
.prompt="The well dweller spends his time deep in the well, afriad of the world. He's shifty-eyed and mighty suspicious of anybody who wants to do anything other than hang out deep in the well.",
},
{
.name="Man in Black",
.enum_name="ManInBlack",
.prompt="The man in black knows no rules or boundaries, and he flinches at nothing: he's a stonewalled cold blooded killer, and is only in this game for mayhem. Anything that brings him more destruction he's privy to, even if it means his own death."
.prompt=CHARACTER_PROMPT_PREFIX"physicist from the 1980s who got their doctorate in subatomic particle physics. They don't know why they're in a western town, but they're terrified.",
booli_said_this;// don't trigger npc action on own self memory modification
booli_said_this;// don't trigger npc action on own self memory modification
NpcKindauthor_npc_kind;// only valid if author is AuthorNpc
NpcKindauthor_npc_kind;
boolwas_talking_to_somebody;
NpcKindtalking_to_kind;
NpcKindtalking_to_kind;
boolheard_physically;// if not physically, the source was directly
boolheard_physically;// if not physically, the source was directly
booldont_show_to_player;// jester and past memories are hidden to the player when made into dialog
booldont_show_to_player;// jester and past memories are hidden to the player when made into dialog
@ -121,24 +132,12 @@ typedef struct Memory
structMemory*prev;
structMemory*prev;
structMemory*next;
structMemory*next;
uint64_ttick_happened;// can sort memories by time for some modes of display
// if action_taken is none, there might still be speech. If speech_length == 0 and action_taken == none, it's an invalid memory and something has gone wrong
// if action_taken is none, there might still be speech. If speech_length == 0 and action_taken == none, it's an invalid memory and something has gone wrong
ActionKindaction_taken;
ActionKindaction_taken;
ActionArgumentaction_argument;
ActionArgumentaction_argument;
// the context that the action happened in
MemoryContextcontext;
MemoryContextcontext;
TextChunkspeech;
MD_u8speech[MAX_SENTENCE_LENGTH];
intspeech_length;
// internal monologue is only valid if context.is_said_this is true
MD_u8internal_monologue[MAX_SENTENCE_LENGTH];
intinternal_monologue_length;
MoodKindmood;
ItemKindgiven_or_received_item;
}Memory;
}Memory;
typedefenumPropKind
typedefenumPropKind
@ -170,22 +169,6 @@ typedef enum
typedefVec4Color;
typedefVec4Color;
typedefstruct
{
AnimKindanim;
doubleelapsed_time;
boolflipped;
Vec2pos;
Colortint;
boolno_shadow;
}DrawnAnimatedSprite;
typedefstruct
{
DrawnAnimatedSpritedrawn;
floatalive_for;
}PlayerAfterImage;
typedefBUFF(Vec2,MAX_ASTAR_NODES)AStarPath;
typedefBUFF(Vec2,MAX_ASTAR_NODES)AStarPath;
typedefstruct
typedefstruct
@ -210,24 +193,18 @@ typedef struct
Vec2pos;
Vec2pos;
}Target;
}Target;
typedefstructTextChunk
{
structTextChunk*next;
structTextChunk*prev;
chartext[MAX_SENTENCE_LENGTH];
inttext_length;
}TextChunk;
typedefenum
// text chunk must be a literal, not a pointer
{
// and this returns a s8 that points at the text chunk memory
PushWithLint(scratch.arena,&first_system_string,"The NPC you will be acting as is named \"%s\". %s\n",characters[e->npc_kind].name,characters[e->npc_kind].prompt);
// @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.");
if(e->errorlist_first)
AddFmt("The actions you can perform, what they do, and the arguments they expect:");
PushWithLint(scratch.arena,&first_system_string,"Errors to watch out for: ");
PushWithLint(scratch.arena,&latest_state,"Your inventory is EMPTY right now. That means if you gave something to the player expecting them to give you something, they haven't held up their end of the bargain!\n");
PushWithLint(scratch.arena,&latest_state,"You must output a mood every generation. The moods are parsed by code that expects your mood to exactly match one in this list: [");
PushWithLint(scratch.arena,&latest_state,"Your last thought was: %.*s\nYour current mood is %s, make sure you act like it!",MD_S8VArg(last_thought_string),moods[last_memory_that_was_me->mood]);
error_message=MD_S8Lit("You must have a field called `who_i_am` in your response, and it must match the character you're playing as");
}
}
if(error_message.size==0&&action_str.size==0)
if(error_message.size==0&&action_str.size==0)
{
{
error_message=MD_S8Lit("You must have a field named `action` in your response.");
error_message=MD_S8Lit("The field `action` must be of nonzero length, if you don't want to do anything it should be `none`");
}
if(error_message.size==0&&talking_to_str.size==0)
{
error_message=MD_S8Lit("You must have a field named `talking_to` in your message");
}
if(error_message.size==0&&mood_str.size==0)
{
error_message=MD_S8Lit("You must have a field named `mood` in your message");
}
}
/*
if(error_message.size==0&&action_str.size==0)
if(error_message.size==0&&thoughts_str.size==0)
{
{
error_message=MD_S8Lit("You must have a field named `thoughts` in your message, and it must have nonzero size. Like { ... thoughts: \"<your thoughts>\" ... }");
error_message=MD_S8Lit("The field `target` must be of nonzero length, if you don't want to target anybody it should be `nobody`");
error_message=FmtWithLint(arena,"Action `%.*s` is invalid, doesn't exist in the game",MD_S8VArg(action_str));
error_message=FmtWithLint(arena,"Couldn't find valid action in game from string `%.*s`. Available actions: [%.*s]",MD_S8VArg(action_str),MD_S8VArg(list_of_actions));
error_message=FmtWithLint(arena,"Argument for action `%.*s` you gave is `%.*s`, which doesn't exist in the game so is invalid",MD_S8VArg(action_str),MD_S8VArg(action_arg_str));
assert(false);// don't know how to parse the argument string for this kind of action...
error_message=FmtWithLint(arena,"Couldn't find item you said to give in action_arg, `%.*s`, the items you have in your inventory to give are: [%.*s]",MD_S8VArg(action_arg_str),MD_S8VArg(list_of_items));
}
}
}
}
else
{
assert(false);// don't know how to parse the argument string for this kind of action...
}
}
}
if(error_message.size==0)
{
boolfound=false;
for(inti=0;i<ARRLEN(moods);i++)
{
if(MD_S8Match(MD_S8CString(moods[i]),mood_str,0))
{
out->mood=i;
found=true;
break;
}
}
if(!found)
{
error_message=FmtWithLint(arena,"Game does not recognize the mood '%.*s', you must use an available mood from the list provided.",MD_S8VArg(mood_str));