{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_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"},
// @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"
"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*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).";
constchar*top_of_header=""
"#pragma once\n"
@ -41,40 +13,25 @@ typedef struct
{
char*name;// the same as enum name
char*description;
char*argument_description;
booltakes_argument;
}ActionInfo;
ActionInfoactions[]={
#define NO_ARGUMENT .argument_description = "Takes no argument", .takes_argument = false
{
.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",
.description="Do nothing",
NO_ARGUMENT,
},
{
.name="joins_player",
.description="Follow behind the player and hear all of their conversations. You can leave at any time",
.name="join",
.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",
.description="Leave the player",
},
};
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.",
.name="leave",
.description="Leave the party you're in right now",
NO_ARGUMENT,
},
};
@ -90,66 +47,27 @@ typedef struct
char*name;
char*enum_name;
char*prompt;
char*writing_style[8];
}CharacterGen;
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 "
{
.name="Invalid",
.enum_name="Invalid",
.name="nobody",
.enum_name="nobody",
.prompt="There has been an internal error.",
},
{
.name="Player",
.name="The Player",
.enum_name="Player",
.prompt="There has been an internal error.",
},
{
.name="Farmer",
.enum_name="Farmer",
.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.",
},
{
.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."
.name="Daniel",
.enum_name="Daniel",
.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.",
.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
NpcKindauthor_npc_kind;// only valid if author is AuthorNpc
boolwas_talking_to_somebody;
NpcKindauthor_npc_kind;
NpcKindtalking_to_kind;
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
@ -121,24 +132,12 @@ typedef struct Memory
structMemory*prev;
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
ActionKindaction_taken;
ActionArgumentaction_argument;
// the context that the action happened in
MemoryContextcontext;
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;
TextChunkspeech;
}Memory;
typedefenumPropKind
@ -170,22 +169,6 @@ typedef enum
typedefVec4Color;
typedefstruct
{
AnimKindanim;
doubleelapsed_time;
boolflipped;
Vec2pos;
Colortint;
boolno_shadow;
}DrawnAnimatedSprite;
typedefstruct
{
DrawnAnimatedSpritedrawn;
floatalive_for;
}PlayerAfterImage;
typedefBUFF(Vec2,MAX_ASTAR_NODES)AStarPath;
typedefstruct
@ -210,24 +193,18 @@ typedef struct
Vec2pos;
}Target;
typedefstructTextChunk
{
structTextChunk*next;
structTextChunk*prev;
chartext[MAX_SENTENCE_LENGTH];
inttext_length;
}TextChunk;
typedefenum
{
MACH_invalid,
MACH_idol_dispenser,
MACH_arrow_shooter,
}MachineKind;
// 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);
// writing style
// make first system node
{
if(characters[e->npc_kind].writing_style[0])
PushWithLint(scratch.arena,&first_system_string,"Examples of %s's writing style:\n",characters[e->npc_kind].name);
// @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)
PushWithLint(scratch.arena,&first_system_string,"Errors to watch out for: ");
AddFmt("%s joined %s\n",characters[it->context.author_npc_kind].name,characters[it->action_argument.targeting].name);
}
elseif(it->action_taken==ACT_leave)
{
// Needs better handling of when you leave, because the person you were following died. Maybe entities don't die anymore?
AddFmt("%s left their party\n",characters[it->context.author_npc_kind].name);
}
}
PushWithLint(scratch.arena,&latest_state,"]\n");
}
else
{
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");
}
AvailableActionsavailable={0};
fill_available_actions(e,&available);
PushWithLint(scratch.arena,&latest_state,"The actions you can perform: [\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 named `action` in your response.");
}
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");
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&&thoughts_str.size==0)
if(error_message.size==0&&action_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=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,"Action `%.*s` is invalid, doesn't exist in the game",MD_S8VArg(action_str));
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));
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));
assert(false);// don't know how to parse the argument string for this kind of action...
}
}
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));