Don't use metadesk for training data, actions

main
Cameron Murphy Reikes 2 years ago
parent abc6abe1a2
commit 49751f8cfa

@ -0,0 +1,147 @@
let SessionLoad = 1
let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1
let v:this_session=expand("<sfile>:p")
silent only
silent tabonly
cd ~/Documents/rpgpt
if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''
let s:wipebuf = bufnr('%')
endif
let s:shortmess_save = &shortmess
if &shortmess =~ 'A'
set shortmess=aoOA
else
set shortmess=aoO
endif
badd +0 codegen.c
badd +0 main.c
badd +0 buff.h
badd +0 makeprompt.h
badd +0 profiling.h
badd +0 term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe
argglobal
%argdel
$argadd codegen.c
$argadd main.c
$argadd buff.h
$argadd makeprompt.h
$argadd profiling.h
set stal=2
tabnew +setlocal\ bufhidden=wipe
tabrewind
let s:save_splitbelow = &splitbelow
let s:save_splitright = &splitright
set splitbelow splitright
wincmd _ | wincmd |
vsplit
1wincmd h
wincmd w
let &splitbelow = s:save_splitbelow
let &splitright = s:save_splitright
wincmd t
let s:save_winminheight = &winminheight
let s:save_winminwidth = &winminwidth
set winminheight=0
set winheight=1
set winminwidth=0
set winwidth=1
exe 'vert 1resize ' . ((&columns * 124 + 104) / 209)
exe 'vert 2resize ' . ((&columns * 84 + 104) / 209)
argglobal
enew
setlocal fdm=manual
setlocal fde=0
setlocal fmr={{{,}}}
setlocal fdi=#
setlocal fdl=0
setlocal fml=1
setlocal fdn=20
setlocal fen
wincmd w
argglobal
if bufexists(fnamemodify("term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe", ":p")) | buffer term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe | else | edit term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe | endif
if &buftype ==# 'terminal'
silent file term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe
endif
setlocal fdm=manual
setlocal fde=0
setlocal fmr={{{,}}}
setlocal fdi=#
setlocal fdl=0
setlocal fml=1
setlocal fdn=20
setlocal fen
let s:l = 1 - ((0 * winheight(0) + 24) / 49)
if s:l < 1 | let s:l = 1 | endif
keepjumps exe s:l
normal! zt
keepjumps 1
normal! 0
wincmd w
exe 'vert 1resize ' . ((&columns * 124 + 104) / 209)
exe 'vert 2resize ' . ((&columns * 84 + 104) / 209)
tabnext
let s:save_splitbelow = &splitbelow
let s:save_splitright = &splitright
set splitbelow splitright
wincmd _ | wincmd |
vsplit
1wincmd h
wincmd w
let &splitbelow = s:save_splitbelow
let &splitright = s:save_splitright
wincmd t
let s:save_winminheight = &winminheight
let s:save_winminwidth = &winminwidth
set winminheight=0
set winheight=1
set winminwidth=0
set winwidth=1
exe 'vert 1resize ' . ((&columns * 104 + 104) / 209)
exe 'vert 2resize ' . ((&columns * 104 + 104) / 209)
argglobal
enew
balt term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe
setlocal fdm=manual
setlocal fde=0
setlocal fmr={{{,}}}
setlocal fdi=#
setlocal fdl=0
setlocal fml=1
setlocal fdn=20
setlocal fen
wincmd w
argglobal
enew
balt term://~/Documents/rpgpt//42668:C:/Windows/system32/cmd.exe
setlocal fdm=manual
setlocal fde=0
setlocal fmr={{{,}}}
setlocal fdi=#
setlocal fdl=0
setlocal fml=1
setlocal fdn=20
setlocal fen
wincmd w
exe 'vert 1resize ' . ((&columns * 104 + 104) / 209)
exe 'vert 2resize ' . ((&columns * 104 + 104) / 209)
tabnext 2
set stal=1
if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 && getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'
silent exe 'bwipe ' . s:wipebuf
endif
unlet! s:wipebuf
set winheight=1 winwidth=20
let &shortmess = s:shortmess_save
let &winminheight = s:save_winminheight
let &winminwidth = s:save_winminwidth
let s:sx = expand("<sfile>:p:r")."x.vim"
if filereadable(s:sx)
exe "source " . fnameescape(s:sx)
endif
let &g:so = s:so_save | let &g:siso = s:siso_save
set hlsearch
nohlsearch
doautoall SessionLoadPost
unlet SessionLoad
" vim: set ft=vim :

@ -19,7 +19,7 @@
MD_String8 OUTPUT_FOLDER = MD_S8LitComp("gen"); // no trailing slash
MD_String8 ASSETS_FOLDER = MD_S8LitComp("assets");
#define log(...) { printf("Codegen: "); printf(__VA_ARGS__); }
#define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); }
void dump(MD_Node* from) {
printf("/ %.*s\n", MD_S8VArg(from->string));
@ -96,6 +96,7 @@ char* fillnull(char *s, char c) {
}
#define StrSame(s1, s2) MD_S8Match((s1), (s2), 0)
#define EachString(it, first) MD_String8Node *it = (first); it != 0; it = it->next
typedef BUFF(MD_Node*, 256) Nodes;
MD_Node* find_by_name(Nodes *n, MD_String8 name)
@ -127,6 +128,7 @@ char* goto_end_of(char *tomove, size_t max_move, char *pattern) {
#define list_printf(list_ptr, ...) MD_S8ListPush(cg_arena, list_ptr, MD_S8Fmt(cg_arena, __VA_ARGS__))
#define S8(s) MD_S8Lit(s)
#define S8V(s) MD_S8VArg(s)
void dump_full(MD_Node* from)
{
@ -145,7 +147,7 @@ int main(int argc, char **argv) {
cg_arena = MD_ArenaAlloc();
assert_cond(cg_arena, MD_S8Lit("Memory"));
MD_ParseResult training_parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("training.mdesk"));
MD_ParseResult training_parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("elements.mdesk"));
MD_String8 global_prompt = {0};
dump_full(training_parse.node->first_child);
for(MD_EachNode(node, training_parse.node->first_child))
@ -155,110 +157,47 @@ int main(int argc, char **argv) {
global_prompt = node->string;
}
}
MD_String8 training_writeto = MD_S8Fmt(cg_arena, "%.*s/training_data.jsonl\0", MD_S8VArg(OUTPUT_FOLDER));
FILE *train = fopen(training_writeto.str, "w");
assert(train);
Nodes characters = {0};
MD_String8List action_strings = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("character"), 0))
if(StrSame(node->string, S8("actions")))
{
BUFF_APPEND(&characters, node);
for(MD_EachNode(act_node, node->first_child))
{
Log("Adding node %.*s\n", S8V(act_node->string));
MD_S8ListPush(cg_arena, &action_strings, act_node->string);
}
}
}
Nodes items = {0};
Nodes characters = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("item"), 0))
if(MD_S8Match(node->first_tag->string, MD_S8Lit("character"), 0))
{
BUFF_APPEND(&items, node);
BUFF_APPEND(&characters, node);
}
}
Nodes items = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("training"), 0))
if(MD_S8Match(node->first_tag->string, MD_S8Lit("item"), 0))
{
MD_Node *node_with = find_by_name(&characters, ChildValue(node, S8("with")));
int upto_npc_line = 0;
int cur_npc_line = 0;
MD_Node *cur_sentence = MD_ChildFromString(node, S8("data"), 0)->first_child;
MD_Node *has_item = 0;
MD_String8List conversation = {0};
while(!MD_NodeIsNil(cur_sentence))
{
assert(!MD_NodeIsNil(cur_sentence));
bool is_player = MD_NodeHasTag(cur_sentence, S8("player"), 0);
bool is_npc = MD_NodeHasTag(cur_sentence, S8("npc"), 0);
bool is_item_possess = MD_NodeHasTag(cur_sentence, S8("item_possess"), 0);
assert(!MD_NodeHasTag(cur_sentence, S8("item_posess"), 0));
bool is_item_discard = MD_NodeHasTag(cur_sentence, S8("item_discard"), 0);
if(is_player)
{
list_printf(&conversation, "Player: \\\"%.*s\\\"\\n", MD_S8VArg(cur_sentence->string));
}
if(is_item_possess)
{
MD_Node *item = find_by_name(&items, cur_sentence->string);
assert(item);
has_item = item;
list_printf(&conversation, "%.*s\\n", MD_S8VArg(ChildValue(item, S8("possess_message"))));
}
if(is_item_discard)
{
MD_Node *item = find_by_name(&items, cur_sentence->string);
assert(item);
has_item = 0;
list_printf(&conversation, "%.*s\\n", MD_S8VArg(ChildValue(item, S8("discard_message"))));
}
bool restarting = false;
if(is_npc)
{
list_printf(&conversation, "%.*s: \\\"", MD_S8VArg(ChildValue(node_with, S8("name"))));
if(upto_npc_line == cur_npc_line)
{
MD_String8 completion = cur_sentence->string;
fprintf(train, "{\"prompt\": \"");
fprintf(train, nullterm(global_prompt), MD_S8VArg(ChildValue(node_with, S8("actions_str"))));
fprintf(train, "\\n");
if(has_item) fprintf(train, "%.*s\\n", MD_S8VArg(ChildValue(has_item, S8("global_prompt_message"))));
fprintf(train, "%.*s\\n", MD_S8VArg(ChildValue(node_with, S8("prompt"))));
//MD_StringJoin join = (MD_StringJoin){.mid = S8("\\n")};
MD_StringJoin join = (MD_StringJoin){0};
MD_String8 conversation_string = MD_S8ListJoin(cg_arena, conversation, &join);
fprintf(train, "%.*s\", \"completion\": \"%.*s\\\"\"}\n", MD_S8VArg(conversation_string), MD_S8VArg(completion));
upto_npc_line += 1;
cur_npc_line = 0;
cur_sentence = MD_ChildFromString(node, S8("data"), 0)->first_child;
conversation = (MD_String8List){0};
restarting = true;
}
else
{
list_printf(&conversation, "%.*s\\\"\\n", MD_S8VArg(cur_sentence->string));
cur_npc_line += 1;
}
}
if(!restarting) cur_sentence = cur_sentence->next;
}
BUFF_APPEND(&items, node);
}
}
BUFF_ITER(MD_Node*, &characters)
{
printf("Character %.*s\n", MD_S8VArg((*it)->string));
Log("Character %.*s\n", MD_S8VArg((*it)->string));
}
// I hope to God MD_String8's are null terminated...
MD_String8 writeto = MD_S8Fmt(cg_arena, "%.*s/assets.gen.c", MD_S8VArg(OUTPUT_FOLDER));
log("Writing to %.*s\n", MD_S8VArg(writeto));
FILE *output = fopen(writeto.str, "w");
Log("Writing to %.*s\n", MD_S8VArg(writeto));
FILE *output = fopen(nullterm(writeto), "w");
MD_ParseResult parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("assets.mdesk"));
@ -271,7 +210,7 @@ int main(int argc, char **argv) {
for(MD_EachNode(node, parse.node->first_child)) {
if(MD_S8Match(node->first_tag->string, MD_S8Lit("sound"), 0)) {
MD_String8 variable_name = MD_S8Fmt(cg_arena, "sound_%.*s", MD_S8VArg(node->string));
log("New sound variable %.*s\n", MD_S8VArg(variable_name));
Log("New sound variable %.*s\n", MD_S8VArg(variable_name));
MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath"));
filepath = asset_file_path(filepath);
assert_cond(filepath.str != 0, MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string)));
@ -284,7 +223,7 @@ int main(int argc, char **argv) {
}
if(MD_S8Match(node->first_tag->string, MD_S8Lit("image"), 0)) {
MD_String8 variable_name = MD_S8Fmt(cg_arena, "image_%.*s", MD_S8VArg(node->string));
log("New image variable %.*s\n", MD_S8VArg(variable_name));
Log("New image variable %.*s\n", MD_S8VArg(variable_name));
MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath"));
filepath = asset_file_path(filepath);
assert_cond(filepath.str != 0, MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string)));
@ -298,7 +237,7 @@ int main(int argc, char **argv) {
if(MD_S8Match(node->first_tag->string, MD_S8Lit("tileset"), 0)) {
// not a variable anymore
MD_String8 variable_name = MD_S8Fmt(cg_arena, "tileset_%.*s", MD_S8VArg(node->string));
log("New tileset %.*s\n", MD_S8VArg(variable_name));
Log("New tileset %.*s\n", MD_S8VArg(variable_name));
MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath")));
MD_String8 tileset_file_contents = MD_LoadEntireFile(cg_arena, filepath);
@ -340,7 +279,7 @@ int main(int argc, char **argv) {
}
if(MD_S8Match(node->first_tag->string, MD_S8Lit("level"), 0)) {
MD_String8 variable_name = MD_S8Fmt(cg_arena, "level_%.*s", MD_S8VArg(node->string));
log("New level variable %.*s\n", MD_S8VArg(variable_name));
Log("New level variable %.*s\n", MD_S8VArg(variable_name));
MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath")));
MD_ParseResult level_parse = MD_ParseWholeFile(cg_arena, filepath);
assert_cond(!MD_NodeIsNil(level_parse.node->first_child), MD_S8Lit("Failed to load level file"));
@ -427,7 +366,8 @@ int main(int argc, char **argv) {
}
}
MD_StringJoin join = MD_ZERO_STRUCT;
MD_StringJoin join = {0};
MD_String8 declarations = MD_S8ListJoin(cg_arena, declarations_list, &join);
MD_String8 loads = MD_S8ListJoin(cg_arena, load_list, &join);
fprintf(output, "%.*s\nvoid load_assets() {\n%.*s\n}\n", MD_S8VArg(declarations), MD_S8VArg(loads));
@ -440,14 +380,22 @@ int main(int argc, char **argv) {
output = fopen(MD_S8Fmt(cg_arena, "%.*s/characters.gen.h\0", MD_S8VArg(OUTPUT_FOLDER)).str, "w");
//fprintf(output, "char *global_prompt = \"%.*s\";\n", MD_S8VArg(global_prompt));
fprintf(output, "char *general_prompt_table[] = {\n");
BUFF_ITER(MD_Node*, &characters)
fprintf(output, "typedef enum Action {\n");
for(EachString(s, action_strings.first))
{
MD_String8 personalized_global_prompt = MD_S8Fmt(cg_arena, nullterm(global_prompt), MD_S8VArg(ChildValue(*it, S8("actions_str"))));
fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(personalized_global_prompt), MD_S8VArg((*it)->string));
fprintf(output, "ACT_%.*s,\n", S8V(s->string));
}
fprintf(output, "}; // general prompt table\n");
fprintf(output, "} Action;\n");
fprintf(output, "char *action_strings[] = {\n");
for(EachString(s, action_strings.first))
{
fprintf(output, "\"%.*s\",\n", S8V(s->string));
}
fprintf(output, "}; // action strings\n");
fprintf(output, "char *global_prompt = \"%.*s\";\n", S8V(global_prompt));
fprintf(output, "char *prompt_table[] = {\n");
BUFF_ITER(MD_Node*, &characters)
@ -456,28 +404,28 @@ int main(int argc, char **argv) {
}
fprintf(output, "}; // prompt table\n");
fprintf(output, "typedef enum ItemKind {\nITEM_Invalid,\n");
fprintf(output, "typedef enum ItemKind {\nITEM_nothing,\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "ITEM_%.*s,\n", MD_S8VArg((*it)->string));
}
fprintf(output, "} ItemKind;\n");
fprintf(output, "char *item_prompt_table[] = {\n\"Invalid\",\n");
fprintf(output, "char *item_prompt_table[] = {\n\"\",\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("global_prompt_message"))));
}
fprintf(output, "}; // item prompt table\n");
fprintf(output, "char *item_possess_message_table[] = {\n\"Invalid\",\n");
fprintf(output, "char *item_possess_message_table[] = {\n\"The player is now holding nothing\",\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("possess_message"))));
}
fprintf(output, "}; // item possess_message table\n");
fprintf(output, "char *item_discard_message_table[] = {\n\"Invalid\",\n");
fprintf(output, "char *item_discard_message_table[] = {\n\"The player is no longer holding nothing\",\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("discard_message"))));

@ -1,16 +1,34 @@
@global_prompt "This is a conversation between a player and an NPC in a game, where the NPC performs actions by saying one of [%.*s]. The NPC doesn't say anything in stars that isn't in that list between [ and ]. The player is wearing a full suit of knight armor. The general, Death, is leading some troops on a crusade they have mixed opinions about. Nobody is racist."
@global_prompt "This is a conversation between a player and an NPC in a video game. The player is wearing a full suit of knight armor. The general, Death, is leading some troops on a crusade they have mixed opinions about. Nobody is racist."
actions:
{
none,
// mostly player actions
walks_up,
hits_npc,
leaves,
// mostly npc actions
allows_player_to_pass,
gives_tripod,
heals_player,
fights_player,
strikes_air,
joins_player,
leaves_player,
}
@character OldMan:
{
name: "Fredrick the Wise",
prompt: "Fredrick, an old man, has lived around these parts for decades. He's wise in the art of life, and knows what's going on in the game: general death is in town, in the wrong, and must be stopped."
actions_str: "",
prompt: "Fredrick is the NPC. An old man, he's lived around these parts for decades. He's wise in the art of life, and knows what's going on in the game: general death is in town, in the wrong, and must be stopped."
}
@character Blocky:
{
name: "Block",
prompt: "Block wants to block the player from going to the secret artifact. 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.",
prompt: "Block is the NPC. He wants to block the player from going to the secret artifact. 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.",
actions_str: "ACT@[ALLOW_PASS]",
}
@ -18,14 +36,12 @@
{
name: "Hunter the Soldier",
prompt: "Hunter, the NPC, is a nervous guy who trusts authority more than himself. He doesn't believe in the white square's powers.",
actions_str: "",
}
@character GodRock:
{
name: "God",
prompt: "God, in a rock.",
actions_str: "",
prompt: "The NPC is God. God, in a rock.",
}
@character Max:
@ -51,14 +67,14 @@
@item WhiteSquare:
{
global_prompt_message: "The player has a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.",
global_prompt_message: "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.",
possess_message: "The player is now holding the white square",
discard_message: "The player is no longer holding the white square.",
}
@item Tripod:
{
global_prompt_message: "The player has a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.",
global_prompt_message: "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_message: "The player is now holding the tripod",
discard_message: "The player is no longer holding the tripod.",
}
@ -68,6 +84,7 @@
with: OldMan,
data:
{
@available_actions "fights_player, joins_player",
@player "What's up",
@npc "Young warrior! You must stop Death, there isn't much time.",
@player "Why?",

453
main.c

@ -1,5 +1,5 @@
// you will die someday
#define CURRENT_VERSION 3 // wehenver you change Entity increment this boz
#define CURRENT_VERSION 4 // wehenver you change Entity increment this boz
#define SOKOL_IMPL
#if defined(WIN32) || defined(_WIN32)
@ -41,8 +41,6 @@
#define ENTITIES_ITER(ents) for(Entity *it = ents; it < ents + ARRLEN(ents); it++) if(it->exists)
#define Log(...) { printf("Log %d | ", __LINE__); printf(__VA_ARGS__); }
double clamp(double d, double min, double max) {
const double t = d < min ? min : d;
return t > max ? max : t;
@ -125,25 +123,13 @@ typedef struct AnimatedSprite
bool no_wrap; // does not wrap when playing
} AnimatedSprite;
typedef enum CharacterState
{
CHARACTER_WALKING,
CHARACTER_IDLE,
CHARACTER_ATTACK,
CHARACTER_TALKING,
} CharacterState;
#ifdef DEVTOOLS
#define SERVER_URL "http://localhost:8090"
#else
#define SERVER_URL "https://rpgpt.duckdns.org/completion"
#endif
// REFACTORING:: also have to update in javascript!!!!!!!!
#define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING
typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence;
#define SENTENCE_CONST(txt) (Sentence){.data=txt, .cur_index=sizeof(txt)}
#include "makeprompt.h"
Sentence from_str(char *s)
{
@ -156,92 +142,6 @@ Sentence from_str(char *s)
return to_return;
}
// even indexed dialogs (0,2,4) are player saying stuff, odds are the character from GPT
typedef enum AuthorType { NPC, PLAYER, SYSTEM } AuthorType;
typedef struct DialogElement
{
AuthorType author;
Sentence s;
} DialogElement;
// must always have player dialog element first
typedef BUFF(DialogElement, 2*12) Dialog;
#include "characters.gen.h"
NPC_Skeleton,
NPC_MOOSE,
} NpcKind;
typedef enum PropKind
{
TREE0,
TREE1,
TREE2,
} PropKind;
typedef struct EntityRef
{
int index;
int generation;
} EntityRef;
typedef struct Entity
{
bool exists;
bool destroy;
int generation;
// fields for all gs.entities
Vec2 pos;
Vec2 vel; // only used sometimes, like in old man and bullet
float damage; // at 1.0, dead! zero initialized
bool facing_left;
double dead_time;
bool dead;
// multiple gs.entities have a sword swing
BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around
bool is_bullet;
// props
bool is_prop;
PropKind prop_kind;
// items
bool is_item;
bool held_by_player;
ItemKind item_kind;
// npcs
bool is_npc;
double character_say_timer;
NpcKind npc_kind;
EntityRef last_seen_holding;
Sentence sentence_to_say;
Dialog player_dialog;
#ifdef WEB
int gen_request_id;
#endif
bool aggressive;
bool walking;
double shotgun_timer;
bool going_to_target;
Vec2 target_goto;
// only for skeleton npc
double swing_timer;
// character
bool is_character;
EntityRef holding_item;
Vec2 to_throw_direction;
int boots_modifier;
CharacterState state;
EntityRef talking_to; // Maybe should be generational index, but I dunno. No death yet
bool is_rolling; // can only roll in idle or walk states
double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible
double roll_progress;
double swing_progress;
} Entity;
typedef struct Overlap
{
@ -281,19 +181,6 @@ typedef struct Arena
Entity *player = NULL; // up here, used in text backend callback
void make_space_and_append(Dialog *d, DialogElement elem)
{
while((d->cur_index >= ARRLEN(d->data) || d->data[0].author != PLAYER) && d->cur_index > 0)
{
assert(ARRLEN(d->data) >= 1);
for(int i = 0; i < ARRLEN(d->data) - 1; i++)
{
d->data[i] = d->data[i + 1];
}
d->cur_index--;
}
BUFF_APPEND(d, elem);
}
typedef struct AudioSample
{
@ -609,7 +496,6 @@ TileInstance get_tile(Level *l, TileCoord t)
return get_tile_layer(l, 0, t);
}
sg_image load_image(const char *path)
{
sg_image to_return = {0};
@ -648,85 +534,6 @@ sg_image load_image(const char *path)
void say_characters(Entity *npc, int num_characters)
{
play_audio(&sound_simple_talk);
Sentence *sentence_to_append_to = &npc->player_dialog.data[npc->player_dialog.cur_index-1].s;
for(int i = 0; i < num_characters; i++)
{
if(!BUFF_EMPTY(&npc->player_dialog) && !BUFF_EMPTY(&npc->sentence_to_say))
{
char new_character = npc->sentence_to_say.data[0];
bool found_matching_star = false;
BUFF(char, MAX_SENTENCE_LENGTH) match_buffer = {0};
if(new_character == '*')
{
for(int ii = sentence_to_append_to->cur_index-1; ii >= 0; ii--)
{
if(sentence_to_append_to->data[ii] == '*')
{
found_matching_star = true;
break;
}
BUFF_PUSH_FRONT(&match_buffer, sentence_to_append_to->data[ii]);
}
}
if(found_matching_star)
{
assert(npc->is_npc);
if(npc->npc_kind == NPC_Blocky)
{
if(!npc->going_to_target && strcmp(match_buffer.data, "lets player pass") == 0)
{
npc->target_goto = AddV2(npc->pos, V2(-TILE_SIZE*3.0f, 0.0f));
npc->going_to_target = true;
}
}
#if 0 // actions
if(strcmp(match_buffer.data, "fights player") == 0 && npc->npc_kind == OLD_MAN)
{
npc->aggressive = true;
}
if(strcmp(match_buffer.data, "sells grounding boots") == 0 && npc->npc_kind == MERCHANT)
{
player->boots_modifier -= 1;
}
if(strcmp(match_buffer.data, "sells swiftness boots") == 0 && npc->npc_kind == MERCHANT)
{
player->boots_modifier += 1;
}
if(strcmp(match_buffer.data, "moves") == 0 && npc->npc_kind == DEATH)
{
npc->going_to_target = true;
npc->target_goto = AddV2(npc->pos, V2(0.0, -TILE_SIZE*1.5f));
}
#endif
}
BUFF_APPEND(sentence_to_append_to, new_character);
BUFF_REMOVE_FRONT(&npc->sentence_to_say);
}
}
}
bool npc_is_knight_sprite(Entity *it)
{
return it->is_npc && ( it->npc_kind == NPC_Max || it->npc_kind == NPC_Hunter || it->npc_kind == NPC_John || it->npc_kind == NPC_Blocky);
}
void add_new_npc_sentence(Entity *npc, char *sentence)
{
size_t sentence_len = strlen(sentence);
assert(sentence_len < MAX_SENTENCE_LENGTH);
Sentence new_sentence = {0};
bool inside_star = false;
for(int i = 0; i < sentence_len; i++)
{
if(sentence[i] == '"') break;
if(sentence[i] == '\n') continue;
BUFF_APPEND(&new_sentence, sentence[i]);
}
DialogElement empty_elem = { .author = NPC };
say_characters(npc, npc->sentence_to_say.cur_index);
make_space_and_append(&npc->player_dialog, empty_elem);
npc->sentence_to_say = new_sentence;
}
AABB level_aabb = { .upper_left = {0.0f, 0.0f}, .lower_right = {TILE_SIZE * LEVEL_TILES, -(TILE_SIZE * LEVEL_TILES)} };
@ -872,136 +679,18 @@ void end_text_input(char *what_player_said)
BUFF_APPEND(&what_player_said_sentence, c);
}
// order is player message, item status message in training data. So has to be same here
Entity *talking = gete(player->talking_to);
assert(talking);
Dialog *to_append = &talking->player_dialog;
make_space_and_append(to_append, (DialogElement){.s = what_player_said_sentence, .author = PLAYER});
if(!eq(talking->last_seen_holding, player->holding_item))
{
if(gete(talking->last_seen_holding))
{
Sentence discard = from_str(item_discard_message_table[gete(talking->last_seen_holding)->item_kind]);
BUFF_APPEND(&discard, '\n');
make_space_and_append(to_append, (DialogElement){.author = SYSTEM, .s = discard});
assert(gete(talking->last_seen_holding)->is_item);
talking->last_seen_holding = (EntityRef){0};
}
if(gete(player->holding_item))
{
assert(gete(player->holding_item)->is_item);
Sentence possess = from_str(item_possess_message_table[gete(player->holding_item)->item_kind]);
BUFF_APPEND(&possess, '\n');
make_space_and_append(to_append, (DialogElement){.author = SYSTEM, .s = possess});
}
talking->last_seen_holding = player->holding_item;
}
// the npc response will be appended here, or at least be async queued to be appended here
BUFF(char, 4000) prompt_buff = {0};
BUFF(char *, 100) to_join = {0};
assert(talking->npc_kind >= 0);
assert(talking->npc_kind < ARRLEN(prompt_table));
assert(talking->npc_kind < ARRLEN(general_prompt_table));
assert(talking->npc_kind < ARRLEN(name_table));
// general prompt
BUFF_APPEND(&to_join, general_prompt_table[talking->npc_kind]);
BUFF_APPEND(&to_join, "\n");
// item prompt
if(gete(player->holding_item))
{
BUFF_APPEND(&to_join, item_prompt_table[gete(player->holding_item)->item_kind]);
BUFF_APPEND(&to_join, "\n");
}
// characters prompt
BUFF_APPEND(&to_join, prompt_table[talking->npc_kind]);
BUFF_APPEND(&to_join, "\n");
char *character_prompt = name_table[talking->npc_kind];
// all the dialog
int i = 0;
BUFF_ITER(DialogElement, &gete(player->talking_to)->player_dialog)
{
//bool is_player =
if(it->author == PLAYER)
{
BUFF_APPEND(&to_join, "Player: \"");
}
else if(it->author == NPC)
{
BUFF_APPEND(&to_join, character_prompt);
BUFF_APPEND(&to_join, ": \"");
}
else if(it->author == SYSTEM)
{
}
else
{
assert(false);
}
BUFF_APPEND(&to_join, it->s.data);
if(it->author == PLAYER || it->author == NPC)
BUFF_APPEND(&to_join, "\"\n");
i++;
}
BUFF_APPEND(&to_join, character_prompt);
BUFF_APPEND(&to_join, ": \"");
// concatenate into prompt_buff
BUFF_ITER(char *, &to_join)
{
size_t cur_len = strlen(*it);
for(int i = 0; i < cur_len; i++)
{
BUFF_APPEND(&prompt_buff, (*it)[i]);
}
}
const char * prompt = prompt_buff.data;
#ifdef DEVTOOLS
Log("Prompt: `%s`\n", prompt);
#endif
#ifdef WEB
// fire off generation request, save id
int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($1), UTF8ToString($0));
}, SERVER_URL, prompt);
gete(player->talking_to)->gen_request_id = req_id;
#endif
#ifdef DESKTOP
if(gete(player->talking_to)->npc_kind == NPC_Death)
{
add_new_npc_sentence(gete(player->talking_to), "test *moves* I am death, destroyer of games. Come join me in the afterlife, or continue onwards *moves*");
//add_new_npc_sentence(gete(player->talking_to), "test");
}
if(gete(player->talking_to)->npc_kind == NPC_OldMan)
{
add_new_npc_sentence(gete(player->talking_to), "I am the old man");
}
if(gete(player->talking_to)->npc_kind == NPC_Blocky)
{
add_new_npc_sentence(gete(player->talking_to), "I am Blocky. *lets player pass*");
}
if(gete(player->talking_to)->npc_kind == NPC_Hunter)
ItemKind player_holding = ITEM_nothing;
if(gete(player->holding_item) != 0)
{
add_new_npc_sentence(gete(player->talking_to), "I am hunter");
player_holding = gete(player->holding_item)->item_kind;
}
if(gete(player->talking_to)->npc_kind == NPC_Max)
if(talking->last_seen_holding_kind != player_holding)
{
add_new_npc_sentence(gete(player->talking_to), "I am max");
process_perception(talking, (Perception){.type = PlayerHeldItemChanged, .holding = player_holding,});
}
if(gete(player->talking_to)->npc_kind == NPC_John)
{
add_new_npc_sentence(gete(player->talking_to), "I am john *gives WhiteSquare*");
}
#endif
process_perception(talking, (Perception){.type = PlayerDialog, .player_dialog = what_player_said_sentence,});
}
}
@ -2334,57 +2023,48 @@ void draw_dialog_panel(Entity *talking_to)
if(aabb_is_valid(dialog_panel))
{
float new_line_height = dialog_panel.lower_right.Y;
int i = 0;
//BUFF_ITER(Sentence, &talking_to->player_dialog)
if(talking_to->player_dialog.cur_index > 0)
typedef struct
{
BUFF_ITER_EX(DialogElement, &talking_to->player_dialog, talking_to->player_dialog.cur_index-1, it >= &talking_to->player_dialog.data[0], it--)
Sentence s;
bool is_player;
} DialogElement;
BUFF(DialogElement, 32) dialog = {0};
BUFF_ITER(Perception, &talking_to->remembered_perceptions)
{
if(it->type == NPCDialog)
{
if(it->author == SYSTEM)
{
}
else
BUFF_APPEND(&dialog, ((DialogElement){ .s = it->npc_dialog, .is_player = false }) );
}
else if(it->type == PlayerDialog)
{
BUFF_APPEND(&dialog, ((DialogElement){ .s = it->player_dialog, .is_player = true }) );
}
}
if(dialog.cur_index > 0)
{
for(int i = dialog.cur_index - 1; i >= 0; i--)
{
DialogElement *it = &dialog.data[i];
{
bool player_talking = it->author == PLAYER;
Color *colors = calloc(sizeof(*colors), it->s.cur_index);
bool in_astrix = false;
for(int char_i = 0; char_i < it->s.cur_index; char_i++)
{
bool set_in_astrix_false = false;
if(it->s.data[char_i] == '*')
{
if(in_astrix)
{
set_in_astrix_false = true;
}
else
{
in_astrix = true;
}
}
if(player_talking)
if(it->is_player)
{
colors[char_i] = BLACK;
}
else
{
if(in_astrix)
{
colors[char_i] = colhex(0xab9100);
}
else
{
colors[char_i] = colhex(0x345e22);
}
colors[char_i] = colhex(0x345e22);
}
if(set_in_astrix_false) in_astrix = false;
}
float measured_line_height = draw_wrapped_text(true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, true, dialog_panel);
new_line_height += (new_line_height - measured_line_height);
draw_wrapped_text(false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, true, dialog_panel);
free(colors);
i++;
}
}
}
@ -2572,7 +2252,7 @@ void frame(void)
assert(player != NULL);
// fixed timestep loop
Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on
static Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on
int num_timestep_loops = 0;
{
unprocessed_fixed_timestep_time += dt;
@ -2594,7 +2274,7 @@ void frame(void)
assert(it->gen_request_id > 0);
int status = EM_ASM_INT({
return get_generation_request_status($0);
}, it->gen_request_id);
}, it->gen_request_id);
if(status == 0)
{
// simply not done yet
@ -2610,7 +2290,19 @@ void frame(void)
stringToUTF8(generation, $1, $2);
}, it->gen_request_id, sentence_str, ARRLEN(sentence_str));
add_new_npc_sentence(it, sentence_str);
// parse out from the sentence NPC action and dialog
Perception out = {0};
bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out);
if(text_was_well_formatted)
{
process_perception(it, out);
}
else
{
it->perceptions_dirty = true; // on poorly formatted AI, just retry request.
}
EM_ASM({
done_with_generation_request($0);
@ -2620,7 +2312,7 @@ void frame(void)
{
Log("Failed to generate dialog! Fuck!");
// need somethin better here. Maybe each sentence has to know if it's player or NPC, that way I can remove the player's dialog
add_new_npc_sentence(it, "I'm not sure...");
process_perception(it, (Perception){.type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...")});
}
it->gen_request_id = 0;
}
@ -2637,7 +2329,8 @@ void frame(void)
if(it->is_npc)
{
if(!BUFF_EMPTY(&it->sentence_to_say))
// character speech animation text input
if(false)
{
it->character_say_timer += dt;
const float character_say_time = 0.02f;
@ -2767,7 +2460,7 @@ void frame(void)
}
else if(it->npc_kind == NPC_Blocky)
{
if(it->going_to_target)
if(it->moved)
{
it->walking = true;
Vec2 towards = SubV2(it->target_goto, it->pos);
@ -2845,7 +2538,7 @@ void frame(void)
}
}
PROFILE_SCOPE("Destroy gs.entities")
PROFILE_SCOPE("Destroy gs.entities, maybe send generation requests")
{
ENTITIES_ITER(gs.entities)
{
@ -2855,6 +2548,45 @@ void frame(void)
*it = (Entity){0};
it->generation = gen;
}
if(it->perceptions_dirty)
{
PromptBuff prompt = {0};
generate_prompt(it, &prompt);
Log("Sending request with prompt `%s`\n", prompt.data);
#ifdef WEB
// fire off generation request, save id
int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($1), UTF8ToString($0));
}, SERVER_URL, prompt.data);
it->gen_request_id = req_id;
#endif
#ifdef DESKTOP
BUFF(char, 1024) mocked_ai_response = {0};
#define SAY(act, txt) { int index = action_to_index(it, act); printf_buff(&mocked_ai_response, " %d \"%s\"", index, txt); }
if(it->npc_kind == NPC_Blocky)
{
if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved)
{
SAY(ACT_allows_player_to_pass, "Here you go");
}
else
{
SAY(ACT_none, "You passed");
}
}
else
{
SAY(ACT_none, "I am an NPC");
}
Perception p = {0};
assert(parse_ai_response(it, mocked_ai_response.data, &p));
process_perception(it, p);
#undef SAY
#endif
it->perceptions_dirty = false;
}
}
}
@ -3044,6 +2776,8 @@ void frame(void)
reset_level();
}
}
roll_just_pressed = false;
} // while loop
}
@ -3365,7 +3099,6 @@ void frame(void)
last_frame_processing_time = stm_sec(stm_diff(stm_now(),time_start_frame));
reset(&scratch);
roll_just_pressed = false;
}
}

@ -0,0 +1,28 @@
{
"activeFile": "",
"expandedProjectPaths": [
],
"fileStates": {
"assets/level0.json": {
"scale": 0.17733333333333334,
"selectedLayer": 0,
"viewCenter": {
"x": 1843.9849624060148,
"y": 1240.6015037593986
}
},
"assets/testsmalllevel.json": {
"scale": 3.3249999999999997,
"selectedLayer": 0,
"viewCenter": {
"x": 128.1203007518797,
"y": 128.1203007518797
}
}
},
"openFiles": [
],
"project": "main.tiled-project",
"recentFiles": [
]
}

@ -0,0 +1,383 @@
#pragma once
#include "buff.h"
#include "HandmadeMath.h" // vector types in entity struct definition
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h> // atoi
#include "characters.gen.h"
NPC_Skeleton,
NPC_MOOSE,
} NpcKind;
#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
// REFACTORING:: also have to update in javascript!!!!!!!!
#define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING
typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence;
#define SENTENCE_CONST(txt) {.data=txt, .cur_index=sizeof(txt)}
#define SENTENCE_CONST_CAST(txt) (Sentence)SENTENCE_CONST(txt)
#define REMEMBERED_PERCEPTIONS 24
typedef enum PerceptionType
{
Invalid, // so that zero value in training structs means end of perception
PlayerAction,
PlayerDialog,
NPCDialog, // includes an npc action in every npc dialog. So it's often nothing
PlayerHeldItemChanged,
} PerceptionType;
typedef struct Perception
{
PerceptionType type;
union
{
// player action
Action player_action_type;
// player dialog
Sentence player_dialog;
// npc dialog
struct
{
Action npc_action_type;
Sentence npc_dialog;
};
// player holding item. MUST precede any perceptions which come after the player is holding the item
ItemKind holding;
};
} Perception;
typedef enum PropKind
{
TREE0,
TREE1,
TREE2,
} PropKind;
typedef struct EntityRef
{
int index;
int generation;
} EntityRef;
typedef enum CharacterState
{
CHARACTER_WALKING,
CHARACTER_IDLE,
CHARACTER_ATTACK,
CHARACTER_TALKING,
} CharacterState;
typedef struct Entity
{
bool exists;
bool destroy;
int generation;
// fields for all gs.entities
Vec2 pos;
Vec2 vel; // only used sometimes, like in old man and bullet
float damage; // at 1.0, dead! zero initialized
bool facing_left;
double dead_time;
bool dead;
// multiple gs.entities have a sword swing
BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around
bool is_bullet;
// props
bool is_prop;
PropKind prop_kind;
// items
bool is_item;
bool held_by_player;
ItemKind item_kind;
// npcs
bool is_npc;
bool perceptions_dirty;
BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions;
double character_say_timer;
int characters_said;
NpcKind npc_kind;
ItemKind last_seen_holding_kind;
#ifdef WEB
int gen_request_id;
#endif
bool aggressive;
bool walking;
double shotgun_timer;
bool moved;
Vec2 target_goto;
// only for skeleton npc
double swing_timer;
// character
bool is_character;
EntityRef holding_item;
Vec2 to_throw_direction;
int boots_modifier;
CharacterState state;
EntityRef talking_to; // Maybe should be generational index, but I dunno. No death yet
bool is_rolling; // can only roll in idle or walk states
double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible
double roll_progress;
double swing_progress;
} Entity;
bool npc_is_knight_sprite(Entity *it)
{
return it->is_npc && ( it->npc_kind == NPC_Max || it->npc_kind == NPC_Hunter || it->npc_kind == NPC_John || it->npc_kind == NPC_Blocky);
}
typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff;
typedef BUFF(Action, 8) AvailableActions;
void fill_available_actions(Entity *it, AvailableActions *a)
{
*a = (AvailableActions){0};
BUFF_APPEND(a, ACT_none);
if(it->npc_kind == NPC_GodRock)
{
BUFF_APPEND(a, ACT_heals_player);
}
else
{
BUFF_APPEND(a, ACT_fights_player);
if(npc_is_knight_sprite(it))
{
BUFF_APPEND(a, ACT_strikes_air);
}
if(it->npc_kind == NPC_Blocky)
{
if(!it->moved)
{
BUFF_APPEND(a, ACT_allows_player_to_pass);
}
}
}
}
void process_perception(Entity *it, Perception p)
{
if(p.type != NPCDialog) it->perceptions_dirty = true;
if(!BUFF_HAS_SPACE(&it->remembered_perceptions)) BUFF_REMOVE_FRONT(&it->remembered_perceptions);
BUFF_APPEND(&it->remembered_perceptions, p);
if(p.type == PlayerHeldItemChanged)
{
it->last_seen_holding_kind = p.holding;
}
else if(p.type == NPCDialog)
{
if(p.npc_action_type == ACT_allows_player_to_pass)
{
it->target_goto = AddV2(it->pos, V2(-50.0, 0.0));
it->moved = true;
}
}
}
#define printf_buff(buff_ptr, ...) { int written = snprintf((buff_ptr)->data+(buff_ptr)->cur_index, ARRLEN((buff_ptr)->data) - (buff_ptr)->cur_index, __VA_ARGS__); assert(written >= 0); (buff_ptr)->cur_index += written; };
// returns if action index was valid
bool action_from_index(Entity *it, Action *out, int action_index)
{
AvailableActions available = {0};
fill_available_actions(it, &available);
if(action_index < 0 || action_index >= available.cur_index)
{
return false;
}
else
{
*out = available.data[action_index];
return true;
}
}
// don't call on untrusted action, doesn't return error
int action_to_index(Entity *it, Action a)
{
AvailableActions available = {0};
fill_available_actions(it, &available);
Action target_action = a;
int index = -1;
for(int i = 0; i < available.cur_index; i++)
{
if(available.data[i] == target_action)
{
index = i;
break;
}
}
assert(index != -1);
return index;
}
void generate_prompt(Entity *it, PromptBuff *into)
{
assert(it->is_npc);
*into = (PromptBuff){0};
// global prompt
printf_buff(into, "%s", global_prompt);
printf_buff(into, "%s", "\n");
// npc description prompt
assert(it->npc_kind < ARRLEN(prompt_table));
printf_buff(into, "%s", prompt_table[it->npc_kind]);
printf_buff(into, "%s", "\n");
// item prompt
if(it->last_seen_holding_kind != ITEM_nothing)
{
assert(it->last_seen_holding_kind < ARRLEN(item_prompt_table));
printf_buff(into, "%s", item_prompt_table[it->last_seen_holding_kind]);
printf_buff(into, "%s", "\n");
}
// available actions prompt
AvailableActions available = {0};
fill_available_actions(it, &available);
printf_buff(into, "%s", "The NPC possible actions array, indexed by ACT_INDEX: [");
BUFF_ITER(Action, &available)
{
printf_buff(into, "%s", action_strings[*it]);
printf_buff(into, "%s", ", ");
}
printf_buff(into, "%s", "]\n");
Entity *e = it;
ItemKind last_holding = ITEM_nothing;
BUFF_ITER(Perception, &e->remembered_perceptions)
{
if(it->type == PlayerAction)
{
assert(it->player_action_type < ARRLEN(action_strings));
printf_buff(into, "Player: ACT %s \n", action_strings[it->player_action_type]);
}
else if(it->type == PlayerDialog)
{
printf_buff(into, "%s", "Player: \"");
printf_buff(into, "%s", it->player_dialog.data);
printf_buff(into, "%s", "\"\n");
}
else if(it->type == NPCDialog)
{
printf_buff(into, "%s: ACT %s \"%s\"\n", name_table[e->npc_kind], action_strings[it->npc_action_type], it->npc_dialog.data);
}
else if(it->type == PlayerHeldItemChanged)
{
if(last_holding != it->holding)
{
if(last_holding != ITEM_nothing)
{
printf_buff(into, "%s", item_discard_message_table[last_holding]);
printf_buff(into, "%s", "\n");
}
if(it->holding != ITEM_nothing)
{
printf_buff(into, "%s", item_possess_message_table[it->holding]);
printf_buff(into, "%s", "\n");
}
last_holding = it->holding;
}
}
else
{
assert(false);
}
}
printf_buff(into, "%s: ACT_INDEX", name_table[e->npc_kind]);
}
// returns if the response was well formatted
bool parse_ai_response(Entity *it, char *sentence_str, Perception *out)
{
*out = (Perception){0};
out->type = NPCDialog;
size_t sentence_length = strlen(sentence_str);
bool text_was_well_formatted = true;
BUFF(char, 128) action_index_string = {0};
int npc_sentence_beginning = 0;
for(int i = 0; i < sentence_length; i++)
{
if(i == 0)
{
if(sentence_str[i] != ' ')
{
text_was_well_formatted = false;
Log("Poorly formatted AI string, did not start with a ' ': `%s`\n", sentence_str);
break;
}
}
else
{
if(sentence_str[i] == ' ')
{
npc_sentence_beginning = i + 2;
break;
}
else
{
BUFF_APPEND(&action_index_string, sentence_str[i]);
}
}
}
if(sentence_str[npc_sentence_beginning - 1] != '"' || npc_sentence_beginning == 0)
{
Log("Poorly formatted AI string, sentence beginning incorrect in AI string `%s` NPC sentence beginning %d ...\n", sentence_str, npc_sentence_beginning);
text_was_well_formatted = false;
}
Action npc_action = 0;
if(text_was_well_formatted)
{
int index_of_action = atoi(action_index_string.data);
if(!action_from_index(it, &npc_action, index_of_action))
{
Log("AI output invalid action index %d action index string %s\n", index_of_action, action_index_string.data);
}
}
Sentence what_npc_said = {0};
bool found_end_quote = false;
for(int i = npc_sentence_beginning; i < sentence_length; i++)
{
if(sentence_str[i] == '"')
{
found_end_quote = true;
break;
}
else
{
BUFF_APPEND(&what_npc_said, sentence_str[i]);
}
}
if(!found_end_quote)
{
Log("Poorly formatted AI string, couln't find matching end quote in string %s...\n", sentence_str);
text_was_well_formatted = false;
}
if(text_was_well_formatted)
{
out->npc_action_type = npc_action;
out->npc_dialog = what_npc_said;
}
return text_was_well_formatted;
}

@ -0,0 +1,160 @@
#pragma warning(disable : 4996) // nonsense about fopen being insecure
#include <stdio.h>
#include "makeprompt.h"
typedef struct
{
NpcKind npc_kind;
Perception perceptions[32];
} TrainingSample;
#define PlayerAct(act) { .type = PlayerAction, .player_action_type = act, }
#define PlayerSay(txt) { .type = PlayerDialog, .player_dialog = SENTENCE_CONST(txt), }
#define NPCDoSay(d, txt) { .type = NPCDialog, .npc_action_type = d, .npc_dialog = SENTENCE_CONST(txt) }
#define NPCSay(txt) NPCDoSay(ACT_none, txt)
#define PlayerItemChange(new_item) { .type = PlayerHeldItemChanged, .holding = new_item, }
TrainingSample samples[] = {
{
.npc_kind = NPC_OldMan,
.perceptions = {
PlayerAct(ACT_walks_up),
NPCSay("Young warrior! You must stop Death, there isn't much time."),
PlayerAct(ACT_leaves),
NPCSay("Please!"),
PlayerAct(ACT_walks_up),
NPCSay("There you are! You must fight death!"),
PlayerSay("What what butt ass"),
NPCSay("You must stop death in his tracks and let the village live on!"),
PlayerSay("Nah"),
NPCSay("PLEASE!"),
PlayerAct(ACT_hits_npc),
NPCDoSay(ACT_fights_player, "Ready your sword!"),
},
},
{
.npc_kind = NPC_Blocky,
.perceptions = {
PlayerAct(ACT_walks_up),
PlayerSay("Do you think I should use webgl1 or webgl2?"),
NPCSay("What are either of those things?"),
PlayerSay("Let me pass"),
NPCSay("No"),
PlayerAct(ACT_leaves),
NPCDoSay(ACT_strikes_air, "Goodbye"),
PlayerItemChange(ITEM_Tripod),
PlayerSay("Let me pass"),
NPCDoSay(ACT_allows_player_to_pass, "A beautiful sight, that tripod is."),
PlayerSay("Thanks"),
NPCSay("Of course."),
PlayerSay("Let me pass"),
NPCSay("I can't do that, I already let you pass"),
},
},
{
.npc_kind = NPC_Blocky,
.perceptions = {
PlayerSay("hey"),
NPCSay("I'm just standing here, what are you doing?"),
PlayerItemChange(ITEM_nothing),
PlayerSay("nothing much"),
NPCSay("You don't have a tripod."),
PlayerSay("True"),
NPCSay("Do you want me to be standing there?"),
PlayerSay("Yes"),
NPCSay("Alright then"),
PlayerSay("No"),
NPCSay("What do you mean?"),
PlayerSay("What are you doing?"),
NPCSay("I'm just standing here"),
PlayerItemChange(ITEM_Tripod),
PlayerSay("How about now?"),
NPCSay("Mighty fine tripod you have there."),
}
},
{
.npc_kind = NPC_GodRock,
.perceptions = {
PlayerAct(ACT_walks_up),
NPCSay("I am"),
PlayerSay("Whaddup?"),
NPCSay("The clouds part and reveal only me."),
PlayerSay("uhhh. GIve me gold"),
NPCSay("To the greedy, come few"),
PlayerSay("I repent"),
NPCDoSay(ACT_heals_player, "And you are forgiven."),
},
},
{
.npc_kind = NPC_GodRock,
.perceptions = {
PlayerAct(ACT_walks_up),
NPCSay("I am"),
PlayerSay("Who are you?"),
NPCSay("Have you heard of the high elves?"),
PlayerSay("Yes"),
NPCDoSay(ACT_heals_player, "No need for me to speak then."),
},
},
};
typedef BUFF(char, 1024*10) Escaped;
Escaped escape_for_json(char *s)
{
Escaped to_return = {0};
size_t len = strlen(s);
for(int i = 0; i < len; i++)
{
if(s[i] == '\n')
{
BUFF_APPEND(&to_return, '\\');
BUFF_APPEND(&to_return, 'n');
}
else if(s[i] == '"')
{
BUFF_APPEND(&to_return, '\\');
BUFF_APPEND(&to_return, '"');
}
else
{
BUFF_APPEND(&to_return, s[i]);
}
}
return to_return;
}
int main(int argc, char ** argv)
{
Log("Starting to generate training data\n");
FILE *out = fopen("gen/training_data.jsonl", "w");
assert(out);
int total_samples = 0;
for(int i = 0; i < ARRLEN(samples); i++)
{
TrainingSample s = samples[i];
Entity this_entity = (Entity){.is_npc = true, .npc_kind = s.npc_kind};
PromptBuff cur_prompt = {0};
for(int i = 0; i < ARRLEN(s.perceptions); i++)
{
if(s.perceptions[i].type == Invalid) break;
if(s.perceptions[i].type == NPCDialog)
{
Log("Generating sample of length %d for NPC %s...\n", i, name_table[s.npc_kind]);
total_samples++;
generate_prompt(&this_entity, &cur_prompt);
BUFF(char, 1024) completion = {0};
// get index from action
int index = action_to_index(&this_entity, s.perceptions[i].npc_action_type);
printf_buff(&completion, " %d \"%s\"\n", index, s.perceptions[i].npc_dialog.data);
fprintf(out, "{\"prompt\": \"%s\", \"completion\": \"%s\"}\n", escape_for_json(cur_prompt.data).data, escape_for_json(completion.data).data);
}
process_perception(&this_entity, s.perceptions[i]);
}
}
Log("Finished. Total training samples: %d\n", total_samples);
return 0;
}

@ -32,6 +32,9 @@ thirdparty\sokol-shdc.exe --input quad.glsl --output gen\quad-sapp.glsl.h --slan
cl /Ithirdparty /W3 /Zi /WX codegen.c || goto :error
codegen || goto :error
cl /Ithirdparty /Igen /W3 /Zi /WX maketraining.c || goto :error
maketraining || goto :error
goto :EOF
:error

@ -308,14 +308,14 @@ func index(w http.ResponseWriter, req *http.Request) {
ctx := context.Background()
req := gogpt.CompletionRequest {
Model: "curie:ft-personal-2023-03-24-03-06-24",
Model: "curie:ft-alnar-games-2023-03-31-02-21-14",
MaxTokens: 80,
Prompt: promptString,
Temperature: 0.9,
FrequencyPenalty: 0.0,
PresencePenalty: 0.6,
TopP: 1.0,
Stop: []string{"\""},
Stop: []string{"\n"},
N: 1,
}
resp, err := c.CreateCompletion(ctx, req)
@ -326,7 +326,8 @@ func index(w http.ResponseWriter, req *http.Request) {
}
response := resp.Choices[0].Text
if logResponses {
log.Println("Println response: ", response)
log.Println("Println response: `", response + "`")
log.Println()
}
fmt.Fprintf(w, "1%s", response)
}

@ -21,6 +21,7 @@ SHIP BETA
Later:
- Respond to cancel with stripe, redirect to ?cancelled=true that clears day pass ticket cookie and ui value
- Strange ring item from braid that slows down time?
- Put playgpt.io in twitch title
- Portal at end, exit level
- Keep people away from skeletons, make aggroed/chase state where you can't talk to people

Loading…
Cancel
Save