Remove dead content, item/act desc and fix msg type bug

main
Cameron Murphy Reikes 2 years ago
parent c65315b4c0
commit dfb4e06fff

@ -7,7 +7,7 @@ const char *global_prompt = "You are a colorful and interesting personality in a
"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: `{action: your_action, speech: \"Hey player!\", thoughts: \"Your thoughts\"}`. The required fields are `action` and `thoughts`\n"
"Some actions take an argument, which you can provide with the field `action_arg`, e.g for the action `give_item` you would provide an item in your inventory, like {action: give_item, action_arg: Chalice}. The item must come from your inventory which is listed below\n"
"Do NOT make up details that don't exist in the game, this is a big mistake as it confuses the player. The game is simple and small, so prefer to tell the player in character that you don't know how to do something if you aren't explicitly told the information about the game the player requests. E.g, if the player asks how to get rare metals and you don't know how, DO NOT make up something plausible like 'Go to the frost mines in the north', instead say 'I have no idea, sorry.', unless the detail about the game they're asking for is included below.\n"
"Do NOT give away an item until the player gives you something you think is of equal value\n"
;
const char *top_of_header = ""
@ -17,90 +17,64 @@ const char *top_of_header = ""
typedef struct
{
char *name; // the same as enum name
char *description;
bool takes_argument;
} ActionInfo;
ActionInfo actions[] = {
{.name = "none", },
{.name = "give_item", .takes_argument = true, },
// mostly player actions
{.name = "walks_up", },
{.name = "hits_npc", },
{.name = "leaves", },
// mostly npc actions
{.name = "allows_player_to_pass", },
{.name = "gives_tripod", },
{.name = "heals_player", },
{.name = "fights_player", },
{.name = "strikes_air", },
{.name = "joins_player", },
{.name = "leaves_player", },
{.name = "stops_fighting_player", },
{.name = "gives_peace_token", },
{
.name = "none",
.description = "Do nothing, you can still perform speech if you want.",
},
{
.name = "give_item", .takes_argument = true,
.description = "Give the player an item from your inventory.",
},
{
.name = "fights_player",
},
{
.name = "joins_player",
.description = "Follow behind the player and hear all of their conversations. You can leave at any time",
},
{
.name = "leaves_player",
.description = "Leave the player",
},
{
.name = "stops_fighting_player",
},
// Actions used by jester and other characters only in
// the prologue for the game
{.name = "causes_testicular_torsion", },
{.name = "undoes_testicular_torsion", },
{.name = "enchant_fish", },
{.name = "steal_iron_pole", },
{.name = "knights_player", },
{ .name = "causes_testicular_torsion", },
{ .name = "undoes_testicular_torsion", },
{ .name = "enchant_fish", },
{ .name = "steal_iron_pole", },
{ .name = "knights_player", },
};
typedef struct
{
char *global_prompt;
char *enum_name;
char *name; // talked about like 'The Player gave `item.name` to the NPC'
char *possess;
char *discard;
char *description; // this field is required for items.
} ItemInfo;
ItemInfo items[] = {
{
.enum_name = "none",
.name = "Nothing",
.global_prompt = "The player isn't holding anything",
.possess = "The player is no longer holding anything",
.discard = "The player is no longer holding nothing",
.enum_name = "invalid",
.name = "Invalid",
.description = "There has been an internal error.",
},
{
.enum_name = "GoldCoin",
.name = "Gold Coin",
.global_prompt = "The player is holding a gold coin",
.possess = "The player is now holding a gold coin",
.discard = "The player isn't holding a gold coin anymore",
.description = "A bog-standard coin made out of gold, it's fairly valuable but nothing to cry about.",
},
{
.enum_name = "Chalice",
.name = "The Chalice of Gold",
.global_prompt = "The player is holding a glimmering Chalice of Gold. It is a beautiful object, mesmerizing in the light.",
.possess = "The player has ascertained the beautiful chalice of gold",
.discard = "The player no longer has the chalice of gold",
},
{
.enum_name = "WhiteSquare",
.name = "the white square",
.global_prompt = "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.",
.possess = "The player is now holding the white square",
.discard = "The player is no longer holding the white square.",
},
{
.enum_name = "Boots",
.name = "some boots",
.global_prompt = "The player is holding the boots of speed. He is a force to be recogned with in this state, he has great speed while holding the boots.",
.possess = "The player is now holding the boots of speed",
.discard = "The player is no longer holding the boots of speed",
},
{
.enum_name = "Tripod",
.name = "the tripod",
.global_prompt = "The player is holding a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.",
.possess = "The player is now holding the tripod",
.discard = "The player is no longer holding the tripod.",
.description = "A beautiful, glimmering chalice of gold. Some have said that drinking from it gives you eternal life.",
},
};
@ -133,7 +107,7 @@ CharacterGen characters[] = {
{
.name = "Peace Totem",
.enum_name = "PeaceTotem",
.prompt = "THere has been an internal error.",
.prompt = "There has been an internal error.",
},
{
.name = "Player",
@ -145,51 +119,6 @@ CharacterGen characters[] = {
.enum_name = "Jester",
.prompt = "He is evil and joy incarnate, The Jester has caused chaos so the player will listen. He writes his own dialog.",
},
{
.name = "Fredrick",
.enum_name = "OldMan",
.prompt = "\n"
"An example interaction between the player and the NPC, Fredrick:\n"
"Player: ACT_walks_up\n"
"Fredrick: ACT_none \"Hey\"\n"
"Player: \"fsdakfjsd give me gold\"\n"
"Fredrick: ACT_none \"No? I can't do that\"\n"
"Player: \"Who can?\"\n"
"Fredrick: ACT_none \"No idea\"\n"
"Player: \"Lick my balls\"\n"
"Fredrick: ACT_fights_player \"Get away from me!\"\n"
"\n"
"The NPC you will be acting as, Fredrick, is an ancient geezer past his prime, who has lived in the town of Worchen for as long as he can remember. Your many adventures brought you great wisdom about the beauties of life. Now your precious town is under threat, General Death is leading the charge and he's out for blood.",
},
{
.name = "God",
.enum_name = "GodRock",
.prompt = "\n"
"An example interaction between the player and the NPC, God in a rock:\n"
"Player: ACT_walks_up\n"
"God: ACT_none \"I am\"\n"
"Player: \"fsdakfjsd give me gold\"\n"
"God: ACT_none \"You are...Unworthy\"\n"
"Player: \"Why?\"\n"
"God: ACT_none \"You let Death rage on\"\n"
"\n"
"The NPC you will be acting as, is God. God, in a rock.",
},
{
.name = "TheGuard",
.enum_name = "TheGuard",
.prompt = "\n"
"An example interaction between the player and the NPC, TheGuard:\n"
"Player: ACT_walks_up\n"
"TheGuard: ACT_none \"Who dares disturb me?\"\n"
"Player: \"Let me pass\"\n"
"TheGuard: ACT_none \"Not without the tripod\"\n"
"Player: \"How about now\"\n"
"The player is currently holding a tripod\n"
"TheGuard: ACT_allows_player_to_pass \"The tripod... I see myself in it\"\n"
"\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.",
},
{
#undef NPC_NAME
#define NPC_NAME "Red"
@ -219,33 +148,6 @@ CharacterGen characters[] = {
.enum_name = "Edeline",
.prompt = "She is the town fortuneteller, 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."
},
{
.name = "Death",
.enum_name = "Death",
.prompt = "\n"
"An example of an interaction between the player and the NPC, Death:\n"
"\n"
"Player: \"Hello\"\n"
"Death: ACT_none \"We will annihilate the villagers no matter what you do\"\n"
"\n"
"The NPC you will be acting as is named Death. He is leading a crusade against the town of Worchen, wreaking havock among the villagers. He believes that all things, even villages, must die, to be replaced by the new, and avoid becoming stagnant.",
},
{
.name = "Mike (undead)",
.enum_name = "MikeSkeleton",
.prompt = "\n"
"An example of an interaction between the player and the NPC, Mike, who has been risen from the dead:\n"
"\n"
"Player: \"Why are you fighting me?\"\n"
"Mike (undead): ACT_none \"I...I don't know. Who are you? Where is Mary?\"\n"
"Player: \"I think her, and you, are dead.\"\n"
"Mike (undead): ACT_none \"Oh... Oh god. Why? Why am I alive?\"\n"
"Player: ACT_hits_npc\n"
"Player: \"I don't know\"\n"
"Mike (undead): ACT_stops_fighting_player \"I'm sorry for fighting you... I. I don't know why I'm alive\"\n"
"\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 "Bill"

@ -649,26 +649,10 @@ Vec2 entity_aabb_size(Entity *e)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
}
else if (e->npc_kind == NPC_GodRock)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
}
else if (e->npc_kind == NPC_OldMan)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
}
else if (e->npc_kind == NPC_Death)
{
return V2(TILE_SIZE*1.10f, TILE_SIZE*1.10f);
}
else if (npc_is_skeleton(e))
{
return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f);
}
else if (e->npc_kind == NPC_TheGuard)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
}
else if (e->npc_kind == NPC_PeaceTotem)
{
return V2(TILE_SIZE, TILE_SIZE);
@ -1009,16 +993,9 @@ void cause_action_side_effects(Entity *from, Entity *to, Action a)
assert(false);
}
if(a.kind == ACT_gives_peace_token)
{
assert(!from->has_given_peace_token);
from->has_given_peace_token = true;
to->peace_tokens += 1;
}
if(a.kind == ACT_give_item)
{
assert(a.argument.item_to_give != ITEM_none);
assert(a.argument.item_to_give != ITEM_invalid);
assert(to);
int item_to_remove = -1;
@ -1336,9 +1313,8 @@ void reset_level()
#ifdef DEVTOOLS
if(false)
{
BUFF_APPEND(&player->held_items, ITEM_WhiteSquare);
for (int i = 0; i < 20; i++)
BUFF_APPEND(&player->held_items, ITEM_Boots);
BUFF_APPEND(&player->held_items, ITEM_GoldCoin);
}
ENTITIES_ITER(gs.entities)
@ -3221,15 +3197,7 @@ bool imbutton_key(AABB button_aabb, float text_scale, MD_String8 text, int key,
void draw_item(bool world_space, ItemKind kind, AABB in_aabb, float alpha)
{
Quad drawn = quad_aabb(in_aabb);
if (kind == ITEM_Tripod)
{
draw_quad((DrawParams) { world_space, drawn, IMG(image_tripod), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG });
}
else if (kind == ITEM_Boots)
{
draw_quad((DrawParams) { world_space, drawn, IMG(image_boots), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG });
}
else if (kind == ITEM_Chalice)
if (kind == ITEM_Chalice)
{
draw_quad((DrawParams) { world_space, drawn, IMG(image_chalice), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG });
}
@ -3237,10 +3205,6 @@ void draw_item(bool world_space, ItemKind kind, AABB in_aabb, float alpha)
{
draw_quad((DrawParams) { world_space, drawn, IMG(image_gold_coin), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG });
}
else if (kind == ITEM_WhiteSquare)
{
colorquad(world_space, drawn, blendalpha(WHITE, alpha));
}
else
{
assert(false);
@ -3884,7 +3848,7 @@ void frame(void)
}
}
if (it->npc_kind == NPC_OldMan)
if (false) // used to be old man code
{
/*
draw_dialog_panel(it);
@ -3925,41 +3889,9 @@ void frame(void)
{
} // skelton combat and movement
}
else if (it->npc_kind == NPC_Death)
{
}
#if 0
else if (it->npc_kind == DEATH)
{
draw_animated_sprite(&death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col);
}
else if (it->npc_kind == MERCHANT)
{
draw_animated_sprite(&merchant_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col);
}
#endif
else if (it->npc_kind == NPC_GodRock)
{
}
else if (it->npc_kind == NPC_Edeline)
{
}
else if (it->npc_kind == NPC_TheGuard)
{
if (it->moved)
{
it->walking = true;
Vec2 towards = SubV2(it->target_goto, it->pos);
if (LenV2(towards) > 1.0f)
{
it->pos = LerpV2(it->pos, dt*5.0f, it->target_goto);
}
}
else
{
it->walking = false;
}
}
else if (it->npc_kind == NPC_TheKing)
{
}
@ -4457,7 +4389,7 @@ void frame(void)
it->dialog_panel_opacity = Lerp(it->dialog_panel_opacity, unwarped_dt*10.0f, alpha);
draw_dialog_panel(it, it->dialog_panel_opacity);
if (it->npc_kind == NPC_OldMan)
if (false) // used to be old man code
{
bool face_left = SubV2(player->pos, it->pos).x < 0.0f;
draw_animated_sprite((DrawnAnimatedSprite) { ANIM_old_man_idle, elapsed_time, face_left, it->pos, col });
@ -4489,25 +4421,10 @@ void frame(void)
}
}
}
else if (it->npc_kind == NPC_Death)
{
draw_animated_sprite((DrawnAnimatedSprite) { ANIM_death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col });
}
else if (it->npc_kind == NPC_GodRock)
{
Vec2 prop_size = V2(46.0f, 40.0f);
DrawParams d = (DrawParams) { true, quad_centered(AddV2(it->pos, V2(-0.0f, 0.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(15.0f, 219.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.7f, .layer = LAYER_WORLD, };
draw_shadow_for(d);
draw_quad(d);
}
else if (npc_is_knight_sprite(it))
{
Color tint = WHITE;
if (it->npc_kind == NPC_TheGuard)
{
tint = colhex(0xa84032);
}
else if (it->npc_kind == NPC_Edeline)
if (it->npc_kind == NPC_Edeline)
{
tint = colhex(0x8c34eb);
}

@ -237,7 +237,6 @@ typedef struct Entity
bool is_npc;
bool being_hovered;
bool perceptions_dirty;
bool has_given_peace_token;
#ifdef DESKTOP
int times_talked_to; // for better mocked response string
@ -278,8 +277,10 @@ typedef struct Entity
bool npc_is_knight_sprite(Entity *it)
{
return it->is_npc && (it->npc_kind == NPC_TheGuard || it->npc_kind == NPC_Edeline || it->npc_kind == NPC_TheKing ||
it->npc_kind == NPC_TheBlacksmith
return it->is_npc && (false
|| it->npc_kind == NPC_Edeline
|| it->npc_kind == NPC_TheKing
|| it->npc_kind == NPC_TheBlacksmith
|| it->npc_kind == NPC_Red
|| it->npc_kind == NPC_Blue
|| it->npc_kind == NPC_Davis
@ -290,7 +291,7 @@ bool npc_is_knight_sprite(Entity *it)
bool npc_is_skeleton(Entity *it)
{
return it->is_npc && (it->npc_kind == NPC_MikeSkeleton);
return it->is_npc && false;
}
float entity_max_damage(Entity *e)
@ -312,7 +313,7 @@ bool npc_attacks_with_sword(Entity *it)
bool npc_attacks_with_shotgun(Entity *it)
{
return it->is_npc && (it->npc_kind == NPC_OldMan);
return it->is_npc && (false);
}
@ -327,48 +328,25 @@ void fill_available_actions(Entity *it, AvailableActions *a)
{
BUFF_APPEND(a, ACT_give_item);
}
if (!it->has_given_peace_token)
{
BUFF_APPEND(a, ACT_gives_peace_token);
}
if (it->npc_kind == NPC_TheKing)
{
BUFF_APPEND(a, ACT_knights_player);
}
if (it->npc_kind == NPC_GodRock)
if (it->standing == STANDING_INDIFFERENT)
{
BUFF_APPEND(a, ACT_heals_player);
BUFF_APPEND(a, ACT_fights_player);
BUFF_APPEND(a, ACT_joins_player);
}
else
else if (it->standing == STANDING_JOINED)
{
if (it->standing == STANDING_INDIFFERENT)
{
BUFF_APPEND(a, ACT_fights_player);
BUFF_APPEND(a, ACT_joins_player);
}
else if (it->standing == STANDING_JOINED)
{
BUFF_APPEND(a, ACT_leaves_player);
BUFF_APPEND(a, ACT_fights_player);
}
else if (it->standing == STANDING_FIGHTING)
{
BUFF_APPEND(a, ACT_stops_fighting_player);
}
if (npc_is_knight_sprite(it))
{
BUFF_APPEND(a, ACT_strikes_air);
}
if (it->npc_kind == NPC_TheGuard)
{
if (!it->moved)
{
BUFF_APPEND(a, ACT_allows_player_to_pass);
}
}
BUFF_APPEND(a, ACT_leaves_player);
BUFF_APPEND(a, ACT_fights_player);
}
else if (it->standing == STANDING_FIGHTING)
{
BUFF_APPEND(a, ACT_stops_fighting_player);
}
}
@ -453,18 +431,6 @@ MD_String8 is_action_valid(MD_Arena *arena, Entity *from, Entity *to_might_be_nu
}
}
if(a.kind == ACT_gives_peace_token)
{
if(from->has_given_peace_token)
{
return MD_S8Lit("You can't give away a peace token when you've already given one away");
}
if(!to_might_be_null || !to_might_be_null->is_character)
{
return MD_S8Lit("Must be targeting the player to give away your peace token");
}
}
if(a.kind == ACT_leaves_player && from->standing != STANDING_JOINED)
{
return MD_S8Lit("You can't leave the player unless you joined them.");
@ -491,10 +457,8 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
PushWithLint(scratch.arena, &first_system_string, "The NPC you will be acting as is named \"%s\". %s", characters[e->npc_kind].name, characters[e->npc_kind].prompt);
//MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, first_system_string, &(MD_StringJoin){0})));
ItemKind last_holding = ITEM_none;
BUFF_ITER(Memory, &e->memories)
{
if(it->is_error)
{
MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, FmtWithLint(scratch.arena, "ERROR, what you said is incorrect because: %.*s", it->speech_length, it->speech)));
@ -576,7 +540,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
PushWithLint(scratch.arena, &cur_list, "}");
assert(sent_type != -1);
MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, cur_list, &(MD_StringJoin){0})));
MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, sent_type, MD_S8ListJoin(scratch.arena, cur_list, &(MD_StringJoin){0})));
}
}
@ -603,42 +567,33 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
if(e->held_items.cur_index > 0)
{
MD_String8List item_strings = held_item_strings(scratch.arena, e);
MD_String8 items = MD_S8ListJoin(scratch.arena, item_strings, &(MD_StringJoin){.mid = MD_S8Lit(", ")});
PushWithLint(scratch.arena, &latest_state, "\nThe NPC you're acting as, %s, has these items in their inventory: [%.*s]\n", characters[e->npc_kind].name, MD_S8VArg(items));
PushWithLint(scratch.arena, &latest_state, "You have these items in their inventory: [\n");
BUFF_ITER(ItemKind, &e->held_items)
{
PushWithLint(scratch.arena, &latest_state, "%s - %s,\n", items[*it].enum_name, items[*it].description);
}
PushWithLint(scratch.arena, &latest_state, "]\n");
}
else
{
PushWithLint(scratch.arena, &latest_state, "\nThe NPC doesn't have any items.\n");
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");
}
AvailableActions available = { 0 };
fill_available_actions(e, &available);
PushWithLint(scratch.arena, &latest_state, "The NPC can now ONLY do these actions, and HAS to respond with an action every time in character, even when faced with sensitive content: [");
PushWithLint(scratch.arena, &latest_state, "The actions you can perform: [\n");
BUFF_ITER_I(ActionKind, &available, i)
{
if (i == available.cur_index - 1)
if(actions[*it].description)
{
PushWithLint(scratch.arena, &latest_state, "%s", actions[*it].name);
PushWithLint(scratch.arena, &latest_state, "%s - %s,\n", actions[*it].name, actions[*it].description);
}
else
{
PushWithLint(scratch.arena, &latest_state, "%s, ", actions[*it].name);
PushWithLint(scratch.arena, &latest_state, "%s,\n", actions[*it].name);
}
}
PushWithLint(scratch.arena, &latest_state, "]");
// peace token
{
if(e->has_given_peace_token)
{
PushWithLint(scratch.arena, &latest_state, "\nRight now you don't have your piece token so you can't give it anymore");
}
else
{
PushWithLint(scratch.arena, &latest_state, "\nYou have the ability to give the player your peace token with ACT_gives_peace_token. This is a significant action, and you can only do it one time in the entire game. Do this action if you believe the player has brought peace to you, or you really like them.");
}
}
PushWithLint(scratch.arena, &latest_state, "]\n");
// last thought explanation and re-prompt
{
@ -650,7 +605,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
last_thought_string = MD_S8(it->internal_monologue, it->internal_monologue_length);
}
}
PushWithLint(scratch.arena, &latest_state, "\nYour last thought was: %.*s", MD_S8VArg(last_thought_string));
PushWithLint(scratch.arena, &latest_state, "Your last thought was: %.*s\n", MD_S8VArg(last_thought_string));
}
MD_String8 latest_state_string = MD_S8ListJoin(scratch.arena, latest_state, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit("")});

@ -373,7 +373,7 @@ func completion(w http.ResponseWriter, req *http.Request) {
var parsed []ChatGPTElem
log.Printf("----------------------------------------------------------")
defer log.Printf("----------------------------------------------------------")
log.Printf("Parsing prompt string `%s`\n", promptString)
log.Printf("Parsing prompt string `%s`\n\n\n", promptString)
err = json.Unmarshal([]byte(promptString), &parsed)
if err != nil {
log.Println("Error bad json given for prompt: ", err)

@ -1,7 +1,8 @@
DONE - rewrite to have metadesk format for speech and actions
- action and item explanations in system message, along with available actions and items
- remove party eavesdropping, but make clear to AI when things are heard physically or told directly
- remove party eavesdropping, but make clear to AI when things are heard physically or told directly. Allow AI to choose people in vicinity to target with conversation and action. I.e a `talking_to` field. Also add a required character: field in chatgpt response, and make sure it matches the character it's supposed to act as.
- delete peace tokens, replace with key items and scroll item that says word.
- display actions, like giving an item, in memory history
- door npc that refuses to open unless player says the word
- The Mighty Sword that's conservative, when convinced to be pulled player gets it as an item
- The using of items and backpack inventory view.

Loading…
Cancel
Save