Do chatgpt requests on desktop build, improve writing of Bill

main
Cameron Murphy Reikes 2 years ago
parent 27b41e089f
commit fde99619f3

@ -4,8 +4,8 @@
if "%1" == "codegen" ( call run_codegen.bat || goto :error ) else ( echo NOT RUNNING CODEGEN )
start /B zig cc -DDEVTOOLS -Igen -Ithirdparty -lDbghelp -lGdi32 -lD3D11 -lOle32 -gfull -gcodeview -o main_zig.exe main.c
cl /diagnostics:caret /DDEVTOOLS /Igen /Ithirdparty /W3 /Zi /WX Dbghelp.lib main.c || goto :error
start /B zig cc -DDEVTOOLS -Igen -Ithirdparty -lDbghelp -lGdi32 -lD3D11 -lOle32 -lwinhttp -gfull -gcodeview -o main_zig.exe main.c
cl /diagnostics:caret /DDEVTOOLS /Igen /Ithirdparty /W3 /Zi /WX Dbghelp.lib winhttp.lib main.c || goto :error
goto :EOF

@ -5,7 +5,6 @@
// @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.\n"
"Actions which have () after them take an argument, which somes from some information in the prompt. For example, ACT_give_item() takes an argument, the item to give to the player from the NPC. So the output text looks something like `ACT_give_item(ITEM_sword) \"Here is my sword, young traveler\"`. This item must come from the NPC's inventory which is specified farther down.\n"
"From within the player's party, NPCs may hear eavesdropped conversations. Often they don't need to interject, so it's fine to say something like `ACT_none ""` to signify that the NPC doesn't need to interject.\n"
"You might see messages that look like this: `Within the player's party, while the player is talking to 'Davis', you hear: 'Davis: ACT_none \"This is some example text\"' . You should MOST of the time respond with `ACT_none \"\"` in these cases, as it's not normal to always respond to words you're eavesdropping\n"
"Do NOT make up details that don't exist in the game, this is a big mistake as it confuses the player. The game is simple and small, so prefer to tell the player in character that you don't know how to do something if you aren't explicitly told the information about the game the player requests. E.g, if the player asks how to get rare metals and you don't know how, DO NOT make up something plausible like 'Go to the frost mines in the north', instead say 'I have no idea, sorry.', unless the detail about the game they're asking for is included below.\n"
;
@ -265,6 +264,10 @@ CharacterGen characters[] = {
"The NPC you will be acting as is named " NPC_NAME ". Unlike other NPCs, he's not from around this medieval fantasy land. He's a divorced car insurance accountant from Philadelphia with a receding hairline in his mid 40s. He lives in a one bedroom studio and his kids don't talk to him. An example of an interaction between the player and the NPC, " NPC_NAME ":\n"
"\n"
PLAYERSAY("Hey what's up")
NPCSAY("Oh...Oh my gosh JESUS FUCKING CHRIST WHERE AM I")
PLAYERSAY("Calm down dude")
NPCSAY("First I was at home, now all the sudden there's all these monsters and FREAKS! GET ME THE FUCK OUT!!")
PLAYERSAY("Freaks? What is the point of this world, where are we?")
NPCDOSAY("ACT_joins_player", "I have no idea man, but I'm freaked out and don't know where I am. I'm like you, from the normal world, not like these crazy fantasy people. Get me out of here GET ME OUT OF HERE!")
"\n"
"You, " NPC_NAME ", are very eager to join the player out of fear for your own survival. You will do anything to escape this weird fantasy world.",

177
main.c

@ -20,6 +20,9 @@
#include <dbghelp.h>
#endif
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#include "buff.h"
#include "sokol_app.h"
#include "sokol_gfx.h"
@ -127,6 +130,16 @@ void web_arena_set_auto_align(WebArena *arena, size_t align)
#endif
#include "profiling.h"
#ifdef DESKTOP
#ifdef WINDOWS
#include <WinHttp.h>
#else
#error "Only know how to do desktop http requests on windows"
#endif // WINDOWS
#endif // DESKTOP
double clamp(double d, double min, double max)
{
const double t = d < min ? min : d;
@ -413,6 +426,18 @@ Vec2 FloorV2(Vec2 v)
MD_Arena *frame_arena = 0;
#ifdef WINDOWS
// uses frame arena
LPCWSTR windows_string(MD_String8 s)
{
int num_characters = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)s.str, (int)s.size, 0, 0);
wchar_t *to_return = MD_PushArray(frame_arena, wchar_t, num_characters + 1); // also allocate for null terminating character
assert(MultiByteToWideChar(CP_UTF8, 0, (LPCCH)s.str, (int)s.size, to_return, num_characters) == num_characters);
to_return[num_characters] = '\0';
return to_return;
}
#endif
MD_String8 tprint(char *format, ...)
{
MD_String8 to_return = {0};
@ -3490,52 +3515,152 @@ void frame(void)
#endif
#ifdef DESKTOP
// desktop http request, no more mocking
MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 mocked_ai_response = {0};
if(false)
MD_String8 ai_response = {0};
bool mocking_the_ai_response = false;
bool succeeded = true; // couldn't get AI response if false
if(mocking_the_ai_response)
{
const char *argument = 0;
MD_String8List dialog_elems = {0};
ActionKind act = ACT_none;
it->times_talked_to++;
if(it->memories.data[it->memories.cur_index-1].context.eavesdropped_from_party)
if(false)
{
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Responding to eavesdropped: ");
const char *argument = 0;
MD_String8List dialog_elems = {0};
ActionKind act = ACT_none;
it->times_talked_to++;
if(it->memories.data[it->memories.cur_index-1].context.eavesdropped_from_party)
{
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Responding to eavesdropped: ");
}
if(it->npc_kind == NPC_TheBlacksmith && it->standing != STANDING_JOINED)
{
assert(it->times_talked_to == 1);
act = ACT_joins_player;
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Joining you...");
}
else
{
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "%d times talked", it->times_talked_to);
}
MD_StringJoin join = {0};
MD_String8 dialog = MD_S8ListJoin(scratch.arena, dialog_elems, &join);
if (argument)
{
ai_response = MD_S8Fmt(scratch.arena, "ACT_%s(%s) \"%.*s\"", actions[act].name, argument, MD_S8VArg(dialog));
}
else
{
ai_response = MD_S8Fmt(scratch.arena, "ACT_%s \"%.*s\"", actions[act].name, MD_S8VArg(dialog));
}
}
if(it->npc_kind == NPC_TheBlacksmith && it->standing != STANDING_JOINED)
else
{
assert(it->times_talked_to == 1);
act = ACT_joins_player;
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "Joining you...");
ai_response = MD_S8Lit(" Within the player's party, while the player is talking to Meld, you hear: ACT_none \"Better have a good reason for bothering me.\"");
}
else
}
else
{
MD_String8 post_request_body = MD_S8Fmt(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
#define WinAssertWithErrorCode(X) if( !( X ) ) { unsigned int error = GetLastError(); Log("Error %u in %s\n", error, #X); assert(false); }
HINTERNET hSession = WinHttpOpen(L"PlayGPT winhttp backend", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
WinAssertWithErrorCode(hSession);
LPCWSTR windows_server_name = windows_string(MD_S8Lit(SERVER_DOMAIN));
HINTERNET hConnect = WinHttpConnect(hSession, windows_server_name, SERVER_PORT, 0);
WinAssertWithErrorCode(hConnect);
int security_flags = 0;
if(IS_SERVER_SECURE)
{
MD_S8ListPushFmt(scratch.arena, &dialog_elems, "%d times talked", it->times_talked_to);
security_flags = WINHTTP_FLAG_SECURE;
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"completion", 0, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, security_flags);
WinAssertWithErrorCode(hRequest);
MD_StringJoin join = {0};
MD_String8 dialog = MD_S8ListJoin(scratch.arena, dialog_elems, &join);
if (argument)
// @IMPORTANT @TODO the windows_string allocates on the frame arena, but
// according to https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest
// the buffer needs to remain available as long as the http request is running, so to make this async and do the loading thing need some other way to allocate the winndows string.... arenas bad?
succeeded = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)post_request_body.str, (DWORD)post_request_body.size, (DWORD)post_request_body.size, 0);
if(!succeeded)
{
mocked_ai_response = MD_S8Fmt(scratch.arena, "ACT_%s(%s) \"%.*s\"", actions[act].name, argument, MD_S8VArg(dialog));
Log("Couldn't do the web: %u\n", GetLastError());
}
if(succeeded)
{
WinAssertWithErrorCode(WinHttpReceiveResponse(hRequest, 0));
DWORD status_code;
DWORD status_code_size = sizeof(status_code);
WinAssertWithErrorCode(WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX));
Log("Status code: %u\n", status_code);
DWORD dwSize = 0;
MD_String8List received_data_list = {0};
do
{
dwSize = 0;
WinAssertWithErrorCode(WinHttpQueryDataAvailable(hRequest, &dwSize));
if(dwSize == 0)
{
Log("Didn't get anything back.\n");
}
else
{
MD_u8* out_buffer = MD_PushArray(scratch.arena, MD_u8, dwSize + 1);
DWORD dwDownloaded = 0;
WinAssertWithErrorCode(WinHttpReadData(hRequest, (LPVOID)out_buffer, dwSize, &dwDownloaded));
out_buffer[dwDownloaded - 1] = '\0';
Log("Got this from http, size %d: %s\n", dwDownloaded, out_buffer);
MD_S8ListPush(scratch.arena, &received_data_list, MD_S8(out_buffer, dwDownloaded));
}
} while (dwSize > 0);
MD_String8 received_data = MD_S8ListJoin(scratch.arena, received_data_list, &(MD_StringJoin){0});
ai_response = MD_S8Substring(received_data, 1, received_data.size);
}
else
{
mocked_ai_response = MD_S8Fmt(scratch.arena, "ACT_%s \"%.*s\"", actions[act].name, MD_S8VArg(dialog));
it->perceptions_dirty = true;
}
}
else
Action a = {0};
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if(succeeded)
{
mocked_ai_response = MD_S8Lit(" Within the player's party, while the player is talking to Meld, you hear: ACT_none \"Better have a good reason for bothering me.\"");
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
Action a = {0};
MD_String8 error_message = parse_chatgpt_response(scratch.arena, it, mocked_ai_response, &a);
assert(error_message.size == 0);
perform_action(it, a);
if(mocking_the_ai_response)
{
assert(succeeded);
assert(error_message.size == 0);
perform_action(it, a);
}
else
{
if(succeeded)
{
if (error_message.size == 0)
{
perform_action(it, a);
}
else
{
Log("There was an error with the AI: %.*s", MD_S8VArg(error_message));
remember_error(it, error_message);
}
}
}
MD_ReleaseScratch(scratch);
#undef SAY

@ -50,7 +50,7 @@ MD_String8 escape_for_json(MD_Arena *arena, MD_String8 from)
}
MD_String8 output = {
.str = MD_ArenaPush(arena, output_size),
.str = MD_PushArray(arena, MD_u8, output_size),
.size = output_size,
};
MD_u64 output_cursor = 0;

@ -12,9 +12,14 @@
#define ARENA_SIZE (1024*1024)
#ifdef DEVTOOLS
#define SERVER_URL "http://localhost:8090"
// server url cannot have trailing slash
#define SERVER_DOMAIN "localhost"
#define SERVER_PORT 8090
#define IS_SERVER_SECURE 0
#else
#define SERVER_URL "https://rpgpt.duckdns.org"
#define SERVER_DOMAIN "rpgpt.duckdns.org"
#define SERVER_PORT 443
#define IS_SERVER_SECURE 1
#endif
// REFACTORING:: also have to update in javascript!!!!!!!!

Loading…
Cancel
Save