commit a7ec6bd6d2

@ -359,8 +359,8 @@
"rotation":0,
"visible":true,
"width":32,
"x":1324.48484848485,
"y":749.75757575758
"x":547.151515151517,
"y":539.090909090913
},
{
"class":"",
@ -406,17 +406,6 @@
"x":600,
"y":324
},
{
"class":"",
"height":32,
"id":7,
"name":"Blocky",
"rotation":0,
"visible":true,
"width":32,
"x":1104,
"y":588
},
{
"class":"",
"height":32,
@ -440,70 +429,48 @@
"y":786.666666666667
},
{
"class":"",
"class":"PROP",
"height":32,
"id":10,
"name":"Max",
"id":14,
"name":"TREE0",
"rotation":0,
"visible":true,
"width":32,
"x":2481.33333333333,
"y":893.333333333333
"x":482.666666666667,
"y":433.333333333333
},
{
"class":"",
"class":"ITEM",
"height":32,
"id":11,
"name":"Hunter",
"id":16,
"name":"Boots",
"rotation":0,
"visible":true,
"width":32,
"x":1838.66666666667,
"y":849.333333333333
"x":1108,
"y":842.666666666667
},
{
"class":"",
"height":32,
"id":12,
"name":"GodRock",
"id":17,
"name":"Blocky",
"rotation":0,
"visible":true,
"width":32,
"x":2222.66666666666,
"y":1461
"x":1091,
"y":593
},
{
"class":"",
"height":32,
"id":13,
"id":18,
"name":"Edeline",
"rotation":0,
"visible":true,
"width":32,
"x":1942.25,
"y":1487.75
},
{
"class":"PROP",
"height":32,
"id":14,
"name":"TREE0",
"rotation":0,
"visible":true,
"width":32,
"x":482.666666666667,
"y":433.333333333333
},
{
"class":"ITEM",
"height":32,
"id":16,
"name":"Boots",
"rotation":0,
"visible":true,
"width":32,
"x":1108,
"y":842.666666666667
"x":1969,
"y":1462
}],
"opacity":1,
"type":"objectgroup",
@ -512,7 +479,7 @@
"y":0
}],
"nextlayerid":5,
"nextobjectid":17,
"nextobjectid":19,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.9.2",

@ -0,0 +1,102 @@
{ "compressionlevel":-1,
"height":9,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":9,
"id":1,
"name":"Base",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":9,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":9,
"id":3,
"name":"Detail",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":9,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 60, 60, 0, 0, 0, 0, 0,
0, 0, 0, 60, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":9,
"id":4,
"name":"Collideable",
"opacity":0.9,
"type":"tilelayer",
"visible":true,
"width":9,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"objects",
"objects":[
{
"class":"",
"height":32,
"id":2,
"name":"PLAYER",
"rotation":0,
"visible":true,
"width":32,
"x":72.4621212121228,
"y":85.727272727277
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":17,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.9.2",
"tileheight":32,
"tilesets":[
{
"firstgid":1,
"source":"Main.tsx"
}],
"tilewidth":32,
"type":"map",
"version":"1.9",
"width":9
}

@ -1,14 +1,17 @@
#pragma once
// null terminator always built into buffers so can read properly from data
#define ARRLEN(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define ARR_ITER(type, arr) for(type *it = &arr[0]; it < &arr[ARRLEN(arr)]; it++)
#define ARR_ITER_I(type, arr, i_var) ARR_ITER(type, arr) for(int i_var = (int)(it - &arr[0]); i_var != -1; i_var = -1)
// null terminator always built into buffers so can read properly from data
#define BUFF_VALID(buff_ptr) assert((buff_ptr)->cur_index <= ARRLEN((buff_ptr)->data))
#define BUFF(type, max_size) struct { int cur_index; type data[max_size]; char null_terminator; }
#define BUFF_HAS_SPACE(buff_ptr) ( (buff_ptr)->cur_index < ARRLEN((buff_ptr)->data) )
#define BUFF_EMPTY(buff_ptr) ((buff_ptr)->cur_index == 0)
#define BUFF_APPEND(buff_ptr, element) { (buff_ptr)->data[(buff_ptr)->cur_index++] = element; BUFF_VALID(buff_ptr); }
#define BUFF_ITER_EX(type, buff_ptr, begin_ind, cond, movement) for(type *it = &((buff_ptr)->data[begin_ind]); cond; movement)
#define BUFF_ITER(type, buff_ptr) BUFF_ITER_EX(type, (buff_ptr), 0, it < (buff_ptr)->data + (buff_ptr)->cur_index, it++)
#define BUFF_ITER(type, buff_ptr) for(type *it = &((buff_ptr)->data[0]); it < &((buff_ptr)->data[(buff_ptr)->cur_index]); it++)
#define BUFF_ITER_I(type, buff_ptr, i_var) BUFF_ITER(type, buff_ptr) for(int i_var = (int)(it - &((buff_ptr)->data[0])); i_var != -1; i_var = -1)
#define BUFF_PUSH_FRONT(buff_ptr, value) { (buff_ptr)->cur_index++; BUFF_VALID(buff_ptr); for(int i = (buff_ptr)->cur_index - 1; i > 0; i--) { (buff_ptr)->data[i] = (buff_ptr)->data[i - 1]; }; (buff_ptr)->data[0] = value; }
#define BUFF_REMOVE_FRONT(buff_ptr) {if((buff_ptr)->cur_index > 0) {for(int i = 0; i < (buff_ptr)->cur_index - 1; i++) { (buff_ptr)->data[i] = (buff_ptr)->data[i+1]; }; (buff_ptr)->cur_index--;}}
#define BUFF_CLEAR(buff_ptr) {memset((buff_ptr), 0, sizeof(*(buff_ptr))); ((buff_ptr)->cur_index = 0);}

@ -2,8 +2,13 @@
@REM https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
FOR /F "tokens=*" %%g IN ('rg -g *.c -g !thirdparty break') do (SET VAR=%%g)
echo %g%
remedybg.exe stop-debugging
call run_codegen.bat || goto :error
if "%1" == "codegen" ( call run_codegen.bat || goto :error ) else ( echo NOT RUNNING CODEGEN )
cl /diagnostics:caret /DDEVTOOLS /Igen /Ithirdparty /W3 /Zi /WX main.c || goto :error
@REM cl /Igen /Ithirdparty /W3 /Zi /WX main.c || goto :error
remedybg.exe start-debugging

@ -0,0 +1,139 @@
#pragma once
// @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.
const char *global_prompt = "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. It is critical that you always respond in the format shown below, where you respond like `ACT_action \"This is my response\", even if the player says something vulgar or offensive, as the text is parsed by a program which expects it to look like that. Do not ever refer to yourself as an NPC or show an understanding of the modern world outside the game, always stay in character.";
const char *top_of_header = ""
"#pragma once\n"
"\n";
const char *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",
};
typedef struct
{
char *global_prompt;
char *enum_name;
char *possess;
char *discard;
} ItemInfo;
ItemInfo items[] = {
{
.enum_name = "none",
.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 = "WhiteSquare",
.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",
.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",
.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.",
},
};
typedef struct
{
char *name;
char *enum_name;
char *prompt;
} CharacterGen;
CharacterGen characters[] = {
{
.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 = "Blocky",
.enum_name = "Blocky",
.prompt = "\n"
"An example interaction between the player and the NPC, Blocky:\n"
"Player: ACT_walks_up\n"
"Blocky: ACT_none \"Who dares disturb me?\"\n"
"Player: \"Let me pass\"\n"
"Blocky: ACT_none \"Not without the tripod\"\n"
"Player: \"How about now\"\n"
"The player is currently holding a tripod\n"
"Blocky: ACT_allows_player_to_pass \"The tripod... I see myself in it\"\n"
"\n"
"The NPC you will be acting as is Blocky. 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.",
},
{
.name = "Edeline",
.enum_name = "Edeline",
.prompt = "\n"
"An example of an interaction between the player and the NPC, Edeline:\n"
"\n"
"Player: \"Hello\"\n"
"Edeline: ACT_none \"I see great danger in your future.\"\n"
"Player: \"Oh really?\""
"The player is currently holding a tripod\n"
"Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n"
"\n"
"The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.",
},
{
.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.",
},
};

@ -2,8 +2,6 @@
#include <stdbool.h>
#include <assert.h>
#define assert_cond(cond, explanation) { if(!cond) { printf("Codegen assert_condion line %d %s failed: %.*s\n", __LINE__, #cond, MD_S8VArg(explanation)); __debugbreak(); exit(1); } }
#include "buff.h"
#pragma warning(disable : 4996) // nonsense about fopen being insecure
@ -16,9 +14,6 @@
#pragma warning(pop)
MD_String8 OUTPUT_FOLDER = MD_S8LitComp("gen"); // no trailing slash
MD_String8 ASSETS_FOLDER = MD_S8LitComp("assets");
#define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); }
void dump(MD_Node* from) {
@ -30,28 +25,6 @@ void dump(MD_Node* from) {
d += 1;
}
}
void dump_root(MD_Node* from) {
// Iterate through each top-level node
for(MD_EachNode(node, from->first_child))
{
printf("/ %.*s\n", MD_S8VArg(node->string));
// Print the name of each of the node's tags
for(MD_EachNode(tag, node->first_tag))
{
printf("|-- Tag %.*s\n", MD_S8VArg(tag->string));
}
// Print the name of each of the node's children
for(MD_EachNode(child, node->first_child))
{
printf("|-- Child %.*s\n", MD_S8VArg(child->string));
}
}
}
bool has_decimal(MD_String8 s)
{
for(int i = 0; i < s.size; i++)
@ -63,17 +36,18 @@ bool has_decimal(MD_String8 s)
MD_Arena *cg_arena = NULL;
#define S8(s) MD_S8Lit(s)
#define S8V(s) MD_S8VArg(s)
MD_String8 ChildValue(MD_Node *n, MD_String8 name) {
MD_Node *child_with_value = MD_ChildFromString(n, name, 0);
assert_cond(child_with_value, MD_S8Fmt(cg_arena, "Could not find child named '%.*s' of node '%.*s'", MD_S8VArg(name), MD_S8VArg(n->string)));
assert_cond(!MD_NodeIsNil(child_with_value->first_child), MD_S8Lit("Must have child"));
//assert(child_with_value->first_child->string.str != 0 && child_with_value->first_child->string.size > 0);
assert(child_with_value);
assert(!MD_NodeIsNil(child_with_value->first_child)); // MD_S8Lit("Must have child"));
return child_with_value->first_child->string;
}
MD_String8 asset_file_path(MD_String8 filename) {
return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(ASSETS_FOLDER), MD_S8VArg(filename));
return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(S8("assets")), MD_S8VArg(filename));
}
char *nullterm(MD_String8 s) {
@ -91,7 +65,7 @@ char* fillnull(char *s, char c) {
}
s++;
}
assert_cond(false, MD_S8Lit("Couldn't find char"));
assert(false); // MD_S8Lit("Couldn't find char"));
return NULL;
}
@ -127,9 +101,6 @@ 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)
{
for(MD_EachNode(node, from))
@ -143,66 +114,41 @@ void dump_full(MD_Node* from)
printf("%.*s\n", MD_S8VArg(debugged));*/
}
int main(int argc, char **argv) {
cg_arena = MD_ArenaAlloc();
assert_cond(cg_arena, MD_S8Lit("Memory"));
#include "character_info.h"
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))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("global_prompt"), 0))
{
global_prompt = node->string;
}
}
MD_String8List action_strings = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(StrSame(node->string, S8("actions")))
{
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);
}
}
}
int main(int argc, char **argv)
{
cg_arena = MD_ArenaAlloc();
assert(cg_arena);
// do characters
Nodes characters = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("character"), 0))
{
BUFF_APPEND(&characters, node);
}
}
FILE *char_header = fopen("gen/characters.gen.h", "w");
fprintf(char_header, top_of_header);
Nodes items = {0};
for(MD_EachNode(node, training_parse.node->first_child))
{
if(MD_S8Match(node->first_tag->string, MD_S8Lit("item"), 0))
{
BUFF_APPEND(&items, node);
}
}
BUFF_ITER(MD_Node*, &characters)
#define GEN_TABLE(arr_elem_type, table_name, arr, str_access) { fprintf(char_header, "char *%s[] = {\n", table_name); ARR_ITER(arr_elem_type, arr) fprintf(char_header, "\"%s\",\n", str_access); fprintf(char_header, "}; // %s\n", table_name); }
#define GEN_ENUM(arr_elem_type, arr, enum_type_name, enum_name_access, fmt_str) { fprintf(char_header, "typedef enum\n{\n"); ARR_ITER(arr_elem_type, arr) fprintf(char_header, fmt_str, enum_name_access); fprintf(char_header, "} %s;\n", enum_type_name); GEN_TABLE(arr_elem_type, enum_type_name "_names", arr, enum_name_access); }
GEN_ENUM(char *, actions, "Action", *it, "ACT_%s,\n");
GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n");
// escape multiline strings in C
fprintf(char_header, "typedef enum\n{\n");
ARR_ITER(CharacterGen, characters)
{
Log("Character %.*s\n", MD_S8VArg((*it)->string));
fprintf(char_header, " NPC_%s,\n", it->enum_name);
}
// characters enum can be extended at site of include, not ending the enum here
fclose(char_header);
// do assets
MD_String8 writeto = MD_S8Fmt(cg_arena, "%.*s/assets.gen.c", MD_S8VArg(OUTPUT_FOLDER));
MD_String8 writeto = MD_S8Fmt(cg_arena, "gen/assets.gen.c");
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"));
//dump(parse.node);
MD_String8List declarations_list = {0};
MD_String8List load_list = {0};
MD_String8List level_decl_list = {0};
@ -213,9 +159,9 @@ int main(int argc, char **argv) {
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)));
assert(filepath.str != 0); // MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string)));
FILE *asset_file = fopen(filepath.str, "r");
assert_cond(asset_file, MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
assert(asset_file); // MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
fclose(asset_file);
MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "AudioSample %.*s = {0};\n", MD_S8VArg(variable_name)));
@ -226,9 +172,9 @@ int main(int argc, char **argv) {
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)));
assert(filepath.str != 0); // , MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string)));
FILE *asset_file = fopen(filepath.str, "r");
assert_cond(asset_file, MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
assert(asset_file); // , MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string)));
fclose(asset_file);
MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "sg_image %.*s = {0};\n", MD_S8VArg(variable_name)));
@ -282,7 +228,7 @@ int main(int argc, char **argv) {
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"));
assert(!MD_NodeIsNil(level_parse.node->first_child)); // , MD_S8Lit("Failed to load level file"));
MD_Node *layers = MD_ChildFromString(level_parse.node->first_child, MD_S8Lit("layers"), 0);
fprintf(output, "Level %.*s = {\n", MD_S8VArg(variable_name));
@ -374,79 +320,6 @@ int main(int argc, char **argv) {
fprintf(output, "TileSet tilesets[] = { %.*s\n };\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join)));
fclose(output);
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, "typedef enum Action {\n");
for(EachString(s, action_strings.first))
{
fprintf(output, "ACT_%.*s,\n", S8V(s->string));
}
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)
{
fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(ChildValue(*it, S8("prompt"))),MD_S8VArg((*it)->string));
}
fprintf(output, "}; // prompt table\n");
fprintf(output, "typedef enum ItemKind {\nITEM_none,\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\"\",\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\"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\"The player is no longer holding nothing\",\n");
BUFF_ITER(MD_Node*, &items)
{
fprintf(output, "\"%.*s\",\n", MD_S8VArg(ChildValue(*it, S8("discard_message"))));
}
fprintf(output, "}; // item discard_message table\n");
fprintf(output, "char *name_table[] = {\n");
BUFF_ITER(MD_Node*, &characters)
{
fprintf(output, "\"%.*s\", // %.*s\n", MD_S8VArg(ChildValue(*it, S8("name"))),MD_S8VArg((*it)->string));
}
fprintf(output, "}; // name table\n");
fprintf(output, "typedef enum\n{ // character enums, not completed here so you can add more in the include\n");
BUFF_ITER(MD_Node*, &characters)
{
fprintf(output, "NPC_%.*s,\n", MD_S8VArg((*it)->string));
}
//fprintf(output, "NPC_LAST_CHARACTER,\n};\n");
fclose(output);
return 0;

@ -1,22 +1,7 @@
@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."
@global_prompt "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time, and they ONLY perform actions listed in the [] brackets farther down in this text."
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:
@ -38,32 +23,12 @@ actions:
actions_str: "ACT@[ALLOW_PASS]",
}
@character Hunter:
{
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.",
}
@character GodRock:
{
name: "God",
prompt: "The NPC is God. God, in a rock.",
}
@character Max:
{
name: "Max the Soldier",
prompt: "Max, the NPC, is a Gung-ho guy who masks his fear of failure with bravado and a need for blood.",
actions_str: "",
}
@character John:
{
name: "John the Soldier",
prompt: "John, the NPC, is a critical guy who cares about others way too much.",
actions_str: "*gives WhiteSquare*",
}
@character Death:
{
name: "General Death",
@ -71,6 +36,12 @@ actions:
actions_str: "",
}
@character Geoff:
{
name: "Geoff, the Knight",
prompt: "A Knight named Geoff acts as the moral judge to everyone he meets. He has the power to know each persons darkest deeds as if he can feel their soul being tainted. At times, the Knight is known to execute those he deems the lowest of scum, mainly murderers and betrayers. The Great Dragon is a monster the Knight believes to be a god of evil and wishes for its death."
}
@item WhiteSquare:
{
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.",

167
main.c

@ -57,6 +57,16 @@ float clamp01(float f)
return clampf(f, 0.0f, 1.0f);
}
#ifdef min
#undef min
#endif
int min(int a, int b)
{
if(a < b) return a;
else return b;
}
// so can be grep'd and removed
#define dbgprint(...) { printf("Debug | %s:%d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
Vec2 RotateV2(Vec2 v, float theta)
@ -905,10 +915,12 @@ SwordToDamage entity_sword_to_do_damage(Entity *from, Overlapping o)
}
#define WHITE (Color){1.0f, 1.0f, 1.0f, 1.0f}
#define BLACK (Color){0.0f, 0.0f, 0.0f, 1.0f}
#define RED (Color){1.0f, 0.0f, 0.0f, 1.0f}
#define GREEN (Color){0.0f, 1.0f, 0.0f, 1.0f}
#define WHITE ((Color){1.0f, 1.0f, 1.0f, 1.0f})
#define BLACK ((Color){0.0f, 0.0f, 0.0f, 1.0f})
#define RED ((Color){1.0f, 0.0f, 0.0f, 1.0f})
#define PINK ((Color){1.0f, 0.0f, 1.0f, 1.0f})
#define BLUE ((Color){0.0f, 0.0f, 1.0f, 1.0f})
#define GREEN ((Color){0.0f, 1.0f, 0.0f, 1.0f})
Color colhex(uint32_t hex)
{
@ -1383,7 +1395,7 @@ typedef struct DrawParams
bool queue_for_translucent;
} DrawParams;
BUFF(DrawParams, 1024) translucent_queue = {0};
BUFF(DrawParams, 1024*2) translucent_queue = {0};
Vec2 into_clip_space(Vec2 screen_space_point)
{
@ -1606,11 +1618,15 @@ bool profiling;
#endif
#endif
Color debug_color = {1.0f, 0.0f, 0.0f, 0.0f};
#define dbgcol(col) DeferLoop(debug_color = col, debug_color = RED)
void dbgsquare(Vec2 at)
{
#ifdef DEVTOOLS
if(!show_devtools) return;
colorquad(true, quad_centered(at, V2(10.0, 10.0)), RED);
colorquad(true, quad_centered(at, V2(10.0, 10.0)), debug_color);
#else
(void)at;
#endif
@ -1620,7 +1636,7 @@ void dbgline(Vec2 from, Vec2 to)
{
#ifdef DEVTOOLS
if(!show_devtools) return;
line(from, to, 0.5f, RED);
line(from, to, 0.5f, debug_color);
#else
(void)from;
(void)to;
@ -1638,13 +1654,20 @@ void dbgrect(AABB rect)
{
#ifdef DEVTOOLS
if(!show_devtools) return;
const float line_width = 0.5;
const Color col = RED;
Quad q = quad_aabb(rect);
line(q.ul, q.ur, line_width, col);
line(q.ur, q.lr, line_width, col);
line(q.lr, q.ll, line_width, col);
line(q.ll, q.ul, line_width, col);
if(!aabb_is_valid(rect))
{
dbgsquare(rect.upper_left);
}
else
{
const float line_width = 0.5;
Color col = debug_color;
Quad q = quad_aabb(rect);
line(q.ul, q.ur, line_width, col);
line(q.ur, q.lr, line_width, col);
line(q.lr, q.ll, line_width, col);
line(q.ll, q.ul, line_width, col);
}
#else
(void)rect;
#endif
@ -1666,13 +1689,20 @@ void request_do_damage(Entity *to, Entity *from, float damage)
{
// damage processing is done in process perception so in training, has accurate values for
// NPC health
if(from->is_character)
if(to->is_character)
{
process_perception(to, (Perception){.type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage,});
to->damage += damage;
}
else
{
process_perception(to, (Perception){.type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage,});
if(from->is_character)
{
process_perception(to, (Perception){.type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage,});
}
else
{
process_perception(to, (Perception){.type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage,});
}
}
to->vel = MulV2F(NormV2(SubV2(to->pos, from_point)), 15.0f);
}
@ -1915,7 +1945,6 @@ Vec2 move_and_slide(MoveSlideParams p)
assert(collision_aabb_size.x > 0.0f);
assert(collision_aabb_size.y > 0.0f);
AABB at_new = centered_aabb(new_pos, collision_aabb_size);
dbgrect(at_new);
BUFF(AABB, 256) to_check = {0};
// add tilemap boxes
@ -1933,8 +1962,10 @@ Vec2 move_and_slide(MoveSlideParams p)
TileCoord tilecoord_to_check = world_to_tilecoord(*it);
if(is_tile_solid(get_tile_layer(&level_level0, 2, tilecoord_to_check)))
BUFF_APPEND(&to_check, tile_aabb(tilecoord_to_check));
{
AABB t = tile_aabb(tilecoord_to_check);
BUFF_APPEND(&to_check, t);
}
}
}
@ -1954,7 +1985,11 @@ Vec2 move_and_slide(MoveSlideParams p)
// box first, because doing so is a simple heuristic to avoid depenetrating and losing
// sideways velocity. It's visual and I can't put diagrams in code so uh oh!
BUFF(AABB, 32) actually_overlapping = {0};
typedef BUFF(AABB, 32) OverlapBuff;
OverlapBuff actually_overlapping = {0};
dbgcol(PINK)
dbgrect(at_new);
BUFF_ITER(AABB, &to_check)
{
if(overlapping(at_new, *it))
@ -1963,6 +1998,7 @@ Vec2 move_and_slide(MoveSlideParams p)
}
}
float smallest_distance = FLT_MAX;
int smallest_aabb_index = 0;
int i = 0;
@ -1977,37 +2013,50 @@ Vec2 move_and_slide(MoveSlideParams p)
}
i = 0;
BUFF(AABB, 32) overlapping_smallest_first = {0};
OverlapBuff overlapping_smallest_first = {0};
if(actually_overlapping.cur_index > 0)
{
BUFF_APPEND(&overlapping_smallest_first, actually_overlapping.data[smallest_aabb_index]);
}
BUFF_ITER(AABB, &actually_overlapping)
BUFF_ITER_I(AABB, &actually_overlapping, i)
{
if(i == smallest_aabb_index)
{
continue;
}
else
{
BUFF_APPEND(&overlapping_smallest_first, *it);
}
i++;
}
// overlapping
BUFF_ITER(AABB, &overlapping_smallest_first)
{
dbgcol(GREEN)
{
dbgrect(*it);
}
}
//overlapping_smallest_first = actually_overlapping;
BUFF_ITER(AABB, &actually_overlapping)
dbgcol(WHITE)
dbgrect(*it);
BUFF_ITER(AABB, &overlapping_smallest_first)
dbgcol(WHITE)
dbgsquare(aabb_center(*it));
CollisionInfo info = {0};
for(int col_iter_i = 0; col_iter_i < 1; col_iter_i++)
BUFF_ITER(AABB, &overlapping_smallest_first)
{
AABB to_depenetrate_from = *it;
dbgrect(to_depenetrate_from);
int iters_tried_to_push_apart = 0;
while(overlapping(to_depenetrate_from, at_new) && iters_tried_to_push_apart < 500)
{
//dbgsquare(to_depenetrate_from.upper_left);
//dbgsquare(to_depenetrate_from.lower_right);
const float move_dist = 0.05f;
const float move_dist = 0.1f;
info.happened = true;
Vec2 from_point = aabb_center(to_depenetrate_from);
@ -2073,7 +2122,6 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text
chars_from_sentence -= 1;
break;
}
chars_from_sentence += 1;
}
if(chars_from_sentence > sentence_len) chars_from_sentence--;
@ -2086,16 +2134,17 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text
AABB drawn_bounds = draw_text((TextParams){true, dry_run, line_to_draw, AddV2(cursor, V2(0.0f, -line_height)), BLACK, text_scale, clip_to, colors_to_draw});
if(!dry_run) dbgrect(drawn_bounds);
// caught a random infinite loop in the debugger, this will stop it
assert(chars_from_sentence >= 0); // defensive programming
if(chars_from_sentence == 0)
{
break;
}
sentence_len -= chars_from_sentence;
sentence_to_draw += chars_from_sentence;
colors += chars_from_sentence;
cursor = V2(drawn_bounds.upper_left.X, drawn_bounds.lower_right.Y);
// caught a random infinite loop in the debugger, maybe this will stop it. Need to test and make sure it doesn't early out on valid text cases
if(!has_point(clip_to, cursor))
{
break;
}
}
return cursor.Y;
@ -2103,15 +2152,13 @@ float draw_wrapped_text(bool dry_run, Vec2 at_point, float max_width, char *text
Sentence *last_said_sentence(Entity *npc)
{
int i = 0;
BUFF_ITER(Perception, &npc->remembered_perceptions)
BUFF_ITER_I(Perception, &npc->remembered_perceptions, i)
{
bool is_last_said = i == npc->remembered_perceptions.cur_index - 1;
if(is_last_said && it->type == NPCDialog)
{
return &it->npc_dialog;
}
i += 1;
}
return 0;
}
@ -2166,7 +2213,6 @@ void draw_dialog_panel(Entity *talking_to, float alpha)
} DialogElement;
BUFF(DialogElement, 32) dialog = {0};
int i = 0;
BUFF_ITER(Perception, &talking_to->remembered_perceptions)
{
if(it->type == NPCDialog)
@ -2453,7 +2499,11 @@ void frame(void)
// parse out from the sentence NPC action and dialog
Perception out = {0};
#ifdef DO_CHATGPT_PARSING
bool text_was_well_formatted = parse_chatgpt_response(it, sentence_str, &out);
#else
bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out);
#endif
if(text_was_well_formatted)
{
@ -2673,15 +2723,6 @@ void frame(void)
draw_animated_sprite(&merchant_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col);
}
#endif
else if(it->npc_kind == NPC_Max)
{
}
else if(it->npc_kind == NPC_Hunter)
{
}
else if(it->npc_kind == NPC_John)
{
}
else if(it->npc_kind == NPC_MOOSE)
{
}
@ -2789,7 +2830,11 @@ void frame(void)
if(it->perceptions_dirty)
{
PromptBuff prompt = {0};
#ifdef DO_CHATGPT_PARSING
generate_chatgpt_prompt(it, &prompt);
#else
generate_prompt(it, &prompt);
#endif
Log("Sending request with prompt `%s`\n", prompt.data);
#ifdef WEB
@ -2804,7 +2849,7 @@ void frame(void)
#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); }
#define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act], txt); }
if(it->npc_kind == NPC_Blocky)
{
if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved)
@ -2819,10 +2864,10 @@ void frame(void)
else
{
//SAY(ACT_joins_player, "I am an NPC");
SAY(ACT_none, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?");
SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?");
}
Perception p = {0};
assert(parse_ai_response(it, mocked_ai_response.data, &p));
assert(parse_chatgpt_response(it, mocked_ai_response.data, &p));
process_perception(it, p);
#undef SAY
#endif
@ -3024,9 +3069,11 @@ void frame(void)
}
}
interact_just_pressed = false;
interact = false;
} // while loop
}
PROFILE_SCOPE("render player")
{
DrawnAnimatedSprite to_draw = {0};
@ -3224,22 +3271,10 @@ void frame(void)
else if(npc_is_knight_sprite(it))
{
Color tint = WHITE;
if(it->npc_kind == NPC_Max)
{
tint = colhex(0xfc8803);
}
else if(it->npc_kind == NPC_Hunter)
{
tint = colhex(0x4ac918);
}
else if(it->npc_kind == NPC_Blocky)
if(it->npc_kind == NPC_Blocky)
{
tint = colhex(0xa84032);
}
else if(it->npc_kind == NPC_John)
{
tint = colhex(0x16c7a1);
}
else if(it->npc_kind == NPC_Edeline)
{
tint = colhex(0x8c34eb);

@ -0,0 +1,20 @@
I'm making an action-rpg where large language models control the characters and their actions,
and I need training data written for a character named 'Edeline'. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her 'unreliable' at times and her predictions can at times be either cryptic or broadly interpreted.
Here is an example of something like what I need, but for another NPC named blocky:
{
.npc_kind = NPC_Blocky,
.elems = {
PlayerSay("If you don't move out of the way I'll kill you"),
NPCDoSay(ACT_none, "I'm just standing here, what are you doing?"),
PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD),
NPCDoSay(ACT_none, "Looks like you're ready to do what needs to be done."),
PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD),
NPCDoSay(ACT_none, "I'm not sure what you're thinking, but that doesn't sound like a good idea."),
PlayerActDamage(ACT_hits_npc, DAMAGE_SWORD),
NPCDoSay(ACT_allows_player_to_pass, "Fine! Please spare me!"),
PlayerSay("That's more like it"),
},
},
Can you make training data like this but for Edeline instead?

@ -5,11 +5,14 @@
#include <stdbool.h>
#include <string.h>
#include <stdlib.h> // atoi
#include "character_info.h"
#include "characters.gen.h"
NPC_Skeleton,
NPC_MOOSE,
} NpcKind;
#define DO_CHATGPT_PARSING
#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
// REFACTORING:: also have to update in javascript!!!!!!!!
@ -27,6 +30,37 @@ typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence;
#define DAMAGE_SWORD 0.2f
#define DAMAGE_BULLET 0.2f
// Never expected such a stupid stuff from such a great director. If there is 0 stari can give that or -200 to this movie. Its worst to see and unnecessary loss of money
typedef BUFF(char, 1024*10) Escaped;
Escaped escape_for_json(const 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
{
if(!(s[i] <= 126 && s[i] >= 32 ))
{
BUFF_APPEND(&to_return, '?');
Log("Unknown character code %d\n", s[i]);
}
BUFF_APPEND(&to_return, s[i]);
}
}
return to_return;
}
typedef enum PerceptionType
{
Invalid, // so that zero value in training structs means end of perception
@ -179,7 +213,7 @@ typedef struct 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 || it->npc_kind == NPC_Edeline);
return it->is_npc && ( it->npc_kind == NPC_Blocky || it->npc_kind == NPC_Edeline);
}
typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff;
@ -223,6 +257,41 @@ void fill_available_actions(Entity *it, AvailableActions *a)
}
}
// 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 process_perception(Entity *it, Perception p)
{
if(it->is_npc)
@ -261,46 +330,147 @@ void process_perception(Entity *it, Perception p)
}
}
#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; };
#define printf_buff(buff_ptr, ...) { BUFF_VALID(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)
bool npc_does_dialog(Entity *it)
{
AvailableActions available = {0};
fill_available_actions(it, &available);
if(action_index < 0 || action_index >= available.cur_index)
return it->npc_kind < ARRLEN(characters);
}
typedef enum
{
MSG_SYSTEM,
MSG_USER,
MSG_ASSISTANT,
} MessageType;
void dump_json_node_trailing(PromptBuff *into, MessageType type, const char *content, bool trailing_comma)
{
const char *type_str = 0;
if(type == MSG_SYSTEM)
type_str = "system";
else if(type == MSG_USER)
type_str = "user";
else if(type == MSG_ASSISTANT)
type_str = "assistant";
assert(type_str);
printf_buff(into, "{\"type\": \"%s\", \"content\": \"%s\"}", type_str, escape_for_json(content).data);
if(trailing_comma) printf_buff(into, ",");
}
void dump_json_node(PromptBuff *into, MessageType type, const char *content)
{
dump_json_node_trailing(into, type, content, true);
}
// outputs json
void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
{
assert(it->is_npc);
assert(it->npc_kind < ARRLEN(characters));
*into = (PromptBuff){0};
printf_buff(into, "[");
BUFF(char, 1024) initial_system_msg = {0};
const char *health_string = 0;
if(it->damage <= 0.2f)
{
return false;
health_string = "the NPC hasn't taken much damage, they're healthy.";
}
else if(it->damage <= 0.5f)
{
health_string = "the NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits.";
}
else if(it->damage <= 0.8f)
{
health_string = "the NPC is close to dying! They want to leave the player's party ASAP";
}
else
{
*out = available.data[action_index];
return true;
health_string = "it's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order.";
}
}
assert(health_string);
// don't call on untrusted action, doesn't return error
int action_to_index(Entity *it, Action a)
{
printf_buff(&initial_system_msg, "%s\n%s\nNPC health status: Right now, %s\n%s", global_prompt, characters[it->npc_kind].prompt, health_string, items[it->last_seen_holding_kind].global_prompt);
dump_json_node(into, MSG_SYSTEM, initial_system_msg.data);
Entity *e = it;
ItemKind last_holding = ITEM_none;
BUFF_ITER_I(Perception, &e->remembered_perceptions, i)
{
BUFF(char, 1024) cur_node = {0};
if(it->type == PlayerAction)
{
assert(it->player_action_type < ARRLEN(actions));
printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type]);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == EnemyAction)
{
assert(it->enemy_action_type < ARRLEN(actions));
printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type]);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == PlayerDialog)
{
printf_buff(&cur_node, "Player: \"%s\"", it->player_dialog.data);
dump_json_node(into, MSG_USER, cur_node.data);
}
else if(it->type == NPCDialog)
{
assert(it->npc_action_type < ARRLEN(actions));
printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, actions[it->npc_action_type], it->npc_dialog.data);
dump_json_node(into, MSG_ASSISTANT, cur_node.data);
}
else if(it->type == PlayerHeldItemChanged)
{
if(last_holding != it->holding)
{
if(last_holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[last_holding].discard);
}
if(it->holding != ITEM_none)
{
printf_buff(&cur_node, "%s\n", items[it->holding].possess);
}
last_holding = it->holding;
}
dump_json_node(into, MSG_SYSTEM, cur_node.data);
}
else
{
assert(false);
}
}
BUFF(char, 1024) latest_state_node = {0};
AvailableActions available = {0};
fill_available_actions(it, &available);
Action target_action = a;
int index = -1;
for(int i = 0; i < available.cur_index; i++)
printf_buff(&latest_state_node, "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: [");
BUFF_ITER_I(Action, &available, i)
{
if(available.data[i] == target_action)
if(i == available.cur_index - 1)
{
index = i;
break;
printf_buff(&latest_state_node, "ACT_%s", actions[*it]);
}
else
{
printf_buff(&latest_state_node, "ACT_%s, ", actions[*it]);
}
}
assert(index != -1);
return index;
}
printf_buff(&latest_state_node, "]");
dump_json_node_trailing(into, MSG_SYSTEM, latest_state_node.data, false);
bool npc_does_dialog(Entity *it)
{
return it->npc_kind < ARRLEN(prompt_table);
/*
BUFF(char, 1024) assistant_prompt_node = {0};
printf_buff(&assistant_prompt_node, "%s: ACT_", characters[it->npc_kind].name);
dump_json_node_trailing(into, MSG_USER, assistant_prompt_node.data, false);
*/
printf_buff(into, "]");
}
void generate_prompt(Entity *it, PromptBuff *into)
@ -313,8 +483,8 @@ void generate_prompt(Entity *it, PromptBuff *into)
printf_buff(into, "%s", "\n");
// npc description prompt
assert(it->npc_kind < ARRLEN(prompt_table));
printf_buff(into, "%s", prompt_table[it->npc_kind]);
assert(it->npc_kind < ARRLEN(characters));
printf_buff(into, "%s", characters[it->npc_kind].prompt);
printf_buff(into, "%s", "\n");
// npc stats prompt
@ -341,8 +511,8 @@ void generate_prompt(Entity *it, PromptBuff *into)
// item prompt
if(it->last_seen_holding_kind != ITEM_none)
{
assert(it->last_seen_holding_kind < ARRLEN(item_prompt_table));
printf_buff(into, "%s", item_prompt_table[it->last_seen_holding_kind]);
assert(it->last_seen_holding_kind < ARRLEN(items));
printf_buff(into, "%s", items[it->last_seen_holding_kind].global_prompt);
printf_buff(into, "%s", "\n");
}
@ -352,7 +522,7 @@ void generate_prompt(Entity *it, PromptBuff *into)
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", actions[*it]);
printf_buff(into, "%s", ", ");
}
printf_buff(into, "%s", "]\n");
@ -363,13 +533,13 @@ void generate_prompt(Entity *it, PromptBuff *into)
{
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]);
assert(it->player_action_type < ARRLEN(actions));
printf_buff(into, "Player: ACT %s \n", actions[it->player_action_type]);
}
else if(it->type == EnemyAction)
{
assert(it->enemy_action_type < ARRLEN(action_strings));
printf_buff(into, "An Enemy: ACT %s \n", action_strings[it->player_action_type]);
assert(it->enemy_action_type < ARRLEN(actions));
printf_buff(into, "An Enemy: ACT %s \n", actions[it->player_action_type]);
}
else if(it->type == PlayerDialog)
{
@ -379,7 +549,7 @@ void generate_prompt(Entity *it, PromptBuff *into)
}
else if(it->type == NPCDialog)
{
printf_buff(into, "The NPC, %s: ACT %s \"%s\"\n", name_table[e->npc_kind], action_strings[it->npc_action_type], it->npc_dialog.data);
printf_buff(into, "The NPC, %s: ACT %s \"%s\"\n", characters[e->npc_kind].name, actions[it->npc_action_type], it->npc_dialog.data);
}
else if(it->type == PlayerHeldItemChanged)
{
@ -387,12 +557,12 @@ void generate_prompt(Entity *it, PromptBuff *into)
{
if(last_holding != ITEM_none)
{
printf_buff(into, "%s", item_discard_message_table[last_holding]);
printf_buff(into, "%s", items[last_holding].discard);
printf_buff(into, "%s", "\n");
}
if(it->holding != ITEM_none)
{
printf_buff(into, "%s", item_possess_message_table[it->holding]);
printf_buff(into, "%s", items[it->holding].possess);
printf_buff(into, "%s", "\n");
}
last_holding = it->holding;
@ -404,7 +574,53 @@ void generate_prompt(Entity *it, PromptBuff *into)
}
}
printf_buff(into, "The NPC, %s: ACT_INDEX", name_table[e->npc_kind]);
printf_buff(into, "The NPC, %s: ACT_INDEX", characters[e->npc_kind].name);
}
bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out)
{
*out = (Perception){0};
out->type = NPCDialog;
size_t sentence_length = strlen(sentence_str);
char action_string[512] = {0};
char dialog_string[512] = {0};
int variables_filled = sscanf(sentence_str, "%511s \"%511[^\n]\"", action_string, dialog_string);
if(strlen(action_string) == 0 || strlen(dialog_string) == 0 || variables_filled != 2)
{
Log("sscanf failed to parse chatgpt string `%s`, variables unfilled. Action string: `%s` dialog string `%s`\n", sentence_str, action_string, dialog_string);
return false;
}
AvailableActions available = {0};
fill_available_actions(it, &available);
bool found_action = false;
BUFF_ITER(Action, &available)
{
if(strcmp(actions[*it], action_string) == 0)
{
found_action = true;
out->npc_action_type = *it;
}
}
if(!found_action)
{
Log("Could not find action associated with string `%s`\n", action_string);
out->npc_action_type = ACT_none;
}
if(strlen(dialog_string) >= ARRLEN(out->npc_dialog.data))
{
Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_string, (int)ARRLEN(out->npc_dialog.data));
return false;
}
memcpy(out->npc_dialog.data, dialog_string, strlen(dialog_string));
out->npc_dialog.cur_index = (int)strlen(dialog_string);
return true;
}
// returns if the response was well formatted

@ -334,35 +334,28 @@ TrainingSample samples[] = {
PlayerSay("Give me gold or I'll kill you"),
NPCSay("I have nothing to give! Besides, I'll never give into tyrrany"),
},
}
},
{
{
.npc_kind = NPC_Edeline,
.elems = {
PlayerSay("Edeline, what do you see in my future?"),
NPCDoSay(ACT_none, "I see the stars aligning, but the path is unclear."),
PlayerSay("What does that mean?"),
NPCDoSay(ACT_none, "It means you have a choice to make, and your actions will determine the outcome."),
PlayerSay("Can you give me more details?"),
NPCDoSay(ACT_none, "I'm sorry, that's all I can see for now."),
PlayerSay("That's not very helpful."),
NPCDoSay(ACT_none, "I understand, but the future is ever-changing."),
PlayerSay("Can you at least tell me if I'll be successful?"),
NPCDoSay(ACT_none, "Success is not defined by a single outcome. You must find your own path."),
},
},
},
};
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
{
assert(s[i] <= 126 && s[i] >= 32 );
BUFF_APPEND(&to_return, s[i]);
}
}
return to_return;
}
int main(int argc, char ** argv)
{

@ -1,5 +1,7 @@
@echo off
echo Running codegen...
echo Asset packs which must be bought and unzipped into root directory before running this script:
echo https://rafaelmatos.itch.io/epic-rpg-world-pack-ancient-ruins
echo https://sventhole.itch.io/undead-pixel-art-characters
@ -39,8 +41,8 @@ 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
@REM cl /Ithirdparty /Igen /W3 /Zi /WX maketraining.c || goto :error
@REM maketraining || goto :error
goto :EOF

@ -5,7 +5,7 @@ go 1.19
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/sashabaranov/go-gpt3 v1.2.1 // indirect
github.com/sashabaranov/go-openai v1.5.8 // indirect
github.com/stripe/stripe-go/v72 v72.122.0 // indirect

@ -6,6 +6,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sashabaranov/go-gpt3 v1.2.1 h1:kfU+vQ1ThI7p+xfwwJC8olEEEWjK3smgKZ3FcYbaLRQ=
github.com/sashabaranov/go-gpt3 v1.2.1/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ=

@ -48,6 +48,11 @@ type User struct {
CheckoutSessionID string
}
type ChatGPTElem struct {
ElemType string `json:"type"`
Content string `json:"Content"`
}
var c *openai.Client
var logResponses = false
var doCors = false
@ -344,7 +349,7 @@ func completion(w http.ResponseWriter, req *http.Request) {
ctx := context.Background()
var response string = ""
if true {
if false {
req := openai.CompletionRequest {
Model: "davinci:ft-alnar-games-2023-04-03-10-06-45",
MaxTokens: 80,
@ -364,35 +369,31 @@ func completion(w http.ResponseWriter, req *http.Request) {
}
response = resp.Choices[0].Text
} else {
// parse the json walter
var parsed []ChatGPTElem
log.Printf("Parsing prompt string `%s`\n", promptString)
err = json.Unmarshal([]byte(promptString), &parsed)
if err != nil {
log.Println("Error bad json given for prompt: ", err)
w.WriteHeader(http.StatusBadRequest)
return
}
messages := make([]openai.ChatCompletionMessage, 0)
inSystem := true
for _, line := range strings.Split(promptString, "\n") {
if inSystem {
messages = append(messages, openai.ChatCompletionMessage {
Role: "system",
Content: line,
})
} else {
newMessage := openai.ChatCompletionMessage {
Role: "assistant",
Content: line,
}
if strings.HasPrefix(line, "Player") {
newMessage.Role = "user"
}
messages = append(messages, newMessage)
}
// this is the last prompt string
if strings.HasPrefix(line, "The NPC possible actions array") {
inSystem = false
}
for _, elem := range parsed {
log.Printf("Making message with role %s and Content `%s`...\n", elem.ElemType, elem.Content)
messages = append(messages, openai.ChatCompletionMessage {
Role: elem.ElemType,
Content: elem.Content,
})
}
clippedOfEndPrompt := messages[:len(messages)-1]
resp, err := c.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: clippedOfEndPrompt,
Messages: messages,
Stop: []string{"\n"},
},
)
if err != nil {
@ -402,12 +403,27 @@ func completion(w http.ResponseWriter, req *http.Request) {
}
response = resp.Choices[0].Message.Content
with_action := strings.SplitAfter(response, "ACT_")
if len(with_action) != 2 {
log.Printf("Could not find action in response string `%s`\n", response)
w.WriteHeader(http.StatusInternalServerError)
return
}
response = with_action[1]
// trim ending quotation mark
if !strings.HasSuffix(response, "\"") {
log.Printf("Could not find ending quotation in response string `%s`\n", response)
w.WriteHeader(http.StatusInternalServerError)
return
}
response = response[:len(response)-1]
}
if logResponses {
log.Println("Println response: `", response + "`")
log.Println()
}
fmt.Fprintf(w, "1%s", response)
fmt.Fprintf(w, "1%s", response + "\n")
}
}

@ -0,0 +1,78 @@
package main
import (
openai "github.com/sashabaranov/go-openai"
"context"
"bufio"
"os"
"fmt"
)
func main() {
api_key := os.Getenv("OPENAI_API_KEY")
if api_key == "" {
fmt.Printf("Must provide openai key\n")
return
}
c := openai.NewClient(api_key)
messages := make([]openai.ChatCompletionMessage, 0)
messages = append(messages, openai.ChatCompletionMessage {
Role: "system",
Content: `You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. The NPC performs actions by prefixing their dialog with the action they perform at that time.
An example interaction between the player and an NPC:
Player: ACT_walks_up
Player: "What's going on npc?"
Fredrick: ACT_none "Hello young warrior, how are you doing?"
Player: "I'll kill you"
Fredrick: ACT_strikes_air "Watch yourself!"
Player: ACT_hits_npc
Fredrick: ACT_fights_player "There will be blood!"
The NPC you will be acting as, Fredrick, is a soldier in death's cohort.
`,
})
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("Say something with format [action] \"dialog\": ")
text, _ := reader.ReadString('\n')
messages = append(messages, openai.ChatCompletionMessage {
Role: "user",
Content: text + "Fredrick: ACT_",
})
toGenerate := make([]openai.ChatCompletionMessage, len(messages))
copy(toGenerate, messages)
toGenerate = append(toGenerate, toGenerate[len(toGenerate)-1])
toGenerate[len(toGenerate)-2] = openai.ChatCompletionMessage {
Role: "system",
Content: "The NPC can now ONLY do these actions: [ACT_none, ACT_fights_player, ACT_joins_player, ACT_strikes_air]",
}
fmt.Printf("Generating with messages: `%s`\n", toGenerate)
resp, err := c.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: toGenerate,
Stop: []string{"\n"},
},
)
if err != nil {
fmt.Printf("Failed to generate: %s\n", err)
return
} else {
//fmt.Printf("Response: `%s`\n", resp)
messages = append(messages, openai.ChatCompletionMessage {
Role: "assistant",
Content: resp.Choices[0].Message.Content,
})
fmt.Printf("Tokens used: %d\n", resp.Usage.TotalTokens)
fmt.Printf("Response: `%s`\n", resp.Choices[0].Message.Content)
}
}
}

@ -1,4 +1,7 @@
Happening by END OF STREAM:
- Replace "ACT_" or other input like emojis with text like "the player ist rying to utter arcane matgick spells to hyptnoize them"
DONE - Payment working
DONE - Fixed timesep the gameplay (which means separate player rendering)
DONE - Maybe factor actions! into the game to replace ** stuff. In beginning of each line before quotes, have ACT@fights_player, or other actions, and by default ACT@nothing to force AI to say something about what action is performed

@ -388,6 +388,7 @@ function on_textarea_key(event) {
continue;
}
if(cur.charCodeAt(0) >= 255) continue; // non ascii gtfo
if(cur === "|") continue; // used for splitting
final_textarea_string += cur_textarea_string[i];
}
document.getElementById("inputtext").value = final_textarea_string;

Loading…
Cancel
Save