From fde99619f3c8b0ab0db7a5b64e31b39257a3cadc Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Mon, 29 May 2023 02:11:13 -0700 Subject: [PATCH] Do chatgpt requests on desktop build, improve writing of Bill --- build_desktop_debug.bat | 4 +- character_info.h | 5 +- main.c | 177 ++++++++++++++++++++++++++++++++++------ makeprompt.h | 2 +- tuning.h | 9 +- 5 files changed, 165 insertions(+), 32 deletions(-) diff --git a/build_desktop_debug.bat b/build_desktop_debug.bat index 0ba36ef..e686fb2 100644 --- a/build_desktop_debug.bat +++ b/build_desktop_debug.bat @@ -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 diff --git a/character_info.h b/character_info.h index 1b67a17..e19c9e9 100644 --- a/character_info.h +++ b/character_info.h @@ -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.", diff --git a/main.c b/main.c index 19fbcbc..e81627d 100644 --- a/main.c +++ b/main.c @@ -20,6 +20,9 @@ #include #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 +#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 diff --git a/makeprompt.h b/makeprompt.h index 9ae7142..8d0f321 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -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; diff --git a/tuning.h b/tuning.h index f4a932a..bdd7eb1 100644 --- a/tuning.h +++ b/tuning.h @@ -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!!!!!!!!