Compare commits

...

5 Commits

@ -15,3 +15,25 @@ You must clone with git lfs is, and download git lfs files in this repository. I
Open `art.blend`, go to the scripting tab and hit the play button run the script and export all the 3d assets. Then, make sure that when you build, you also build and run the codegen so that said assets and other files are copied and imported. For debug builds on windows, that's `call build_desktop_debug.bat codegen`, the codegen argument to the build script causing it to run codegen
To enable codegen error messages, change @echo off to @echo on in run_codegen.bat
# Debugging in the web
To get this working, you're going to need to follow flooh's answer linked [here](https://groups.google.com/g/emscripten-discuss/c/DEmpyGoq6kE/m/Bx44ZmfmAAAJ), and copy pasted for redundancy here:
```
It should definitely work on Vanilla Chrome, but setting everything up can be a bit finicky:
- install the Debugging extension: https://chrome.google.com/webstore/detail/cc%20%20-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb
- in the Dev Tools settings, search for 'WebAssembly Debugging' and check that box
- compile your code with '-O0 -g' (no optimization, and debug info enabled'
- IMPORTANT: in the Chrome debugger, there's a 'Filesystem' tab on the left side which is very easy to miss. Here you need to navigate to your project directory and allow Chrome to access that area of your filesystem.
(I think/hope these are all steps)
When you load your application you should see something like this on the Dev Tools console:
[C/C++ DevTools Support (DWARF)] Loading debug symbols for http://localhost:8080/cube-sapp.wasm...
[C/C++ DevTools Support (DWARF)] Loaded debug symbols for http://localhost:8080/cube-sapp.wasm, found 91 source file(s)
...and if everything works it should look roughly like this in the debugger:
```

@ -16,17 +16,21 @@
{enum: Raphael, dialog: "What a psycho...", to: Daniel}
{can_hear: [Daniel, Raphael, Passerby]}
{enum: Passerby, dialog: "Yo", to: Raphael}
{enum: Raphael, dialog: "What's up", to: Passerby}
{enum: Passerby, dialog: "What do you think of farmers", to: Raphael}
{enum: Raphael, dialog: "What?", to: Passerby}
{enum: Daniel, dialog: "Let me 'say somthin here. Farmers are the backbone of this town, so you'd best give them some respect", to: Passerby}
{enum: Raphael, dialog: "I mean, scientists really are what's most important", to: Daniel}
{enum: Daniel, dialog: "First of all, what the hell is a scientist? Second of all, whoever they are, if they can't eat they're worthless!", to: Raphael}
{enum: Raphael, dialog: "FIne fine whatever, no need to get heated...", to: Daniel}
{enum: Daniel, dialog: "Anyways, sorry about that. What's your name?", to: Passerby}
{enum: Passerby, dialog: "Nevermind I'll just take my leave, later.", to: Daniel}
{enum: Passerby, dialog: "Yo?", to: Daniel}
{enum: Daniel, dialog: "Are you askin' a question", to: Passerby}
{enum: Passerby, dialog: "I guess so? What do you think of farmers?", to: Daniel}
{enum: Daniel, dialog: "I don't tolerate questions. Get out of my sight before I make you!", to: Passerby, action: ACT_aim_shotgun, action_argument: "Passerby"}
{enum: Raphael, dialog: "What's going on here?", to: Daniel}
{enum: Daniel, dialog: "THIS DAMNED FOOL DOESN'T UNDERSTAND RESPECT", to: Raphael}
{enum: Raphael, dialog: "Easy man, easy.", to: Daniel}
{enum: Daniel, dialog: "I WON'T TOLERATE IT", to: Raphael}
{enum: Passerby, dialog: "Hey man woah, just put down the gun", to: Daniel}
{enum: Daniel, dialog: "YOU BETTER MAKE ME!", to: Passerby}
{enum: Passerby, dialog: "Why do you have the gun anyways?", to: Daniel}
{enum: Daniel, dialog: "ANOTHER QUESTION??? YOU HAD THIS COMING TO YOU!", to: Passerby, action: ACT_fire_shotgun}
{enum: Raphael, dialog: "Oh God! What have you done??", to: Daniel}
{enum: Daniel, dialog: "Exactly what I'll do to you if you don't keep your mouth shut", to: Raphael, action: ACT_put_shotgun_away}
{enum: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel}
/*
{can_hear: [WellDweller, Farmer, ManInBlack]},

@ -1,7 +1,7 @@
call run_codegen.bat || goto :error
copy marketing_page\favicon.ico %OUTPUT_FOLDER%\favicon.ico
copy main.c %OUTPUT_FOLDER%\main.c || goto :error
@REM copy main.c %OUTPUT_FOLDER%\main.c || goto :error
@echo on
emcc ^

@ -4,7 +4,7 @@ rmdir /S /q build_web
mkdir build_web
@REM set FLAGS=-fsanitize=undefined -fsanitize=address
set FLAGS=-O0 --source-map-base http://localhost:8000/ -g3 -gdwarf -DDEVTOOLS
set FLAGS=-O0 -g -DDEVTOOLS
set OUTPUT_FOLDER=build_web

@ -5,7 +5,8 @@
// @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 character in a simple western video game. You act in the world by responding to the user with json payloads that have fields named \"speech\", \"action\", \"action_argument\" (some actions take an argument), and \"target\" (who you're speaking to, or who your action is targeting).\n"
"You speak only when you have something to say, or are responding to somebody, and use short, concise, punchy language. If you're just overhearing what other people are saying, you only say something when absolutely compelled to do so"
"You speak only when you have something to say, or are responding to somebody, and use short, concise, punchy language. If you're just overhearing what other people are saying, you only say something when absolutely compelled to do so.\n"
"But if somebody talks directly to you, you usually say someting."
;
const char *top_of_header = ""

@ -1,7 +1,7 @@
#include <stdio.h>
#include <stdbool.h>
#define DESKTOP
#include "better_assert.h"
#include "utility.h"
#include "buff.h"
@ -23,8 +23,6 @@
#pragma warning(pop)
#define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); }
void dump(MD_Node* from) {
printf("/ %.*s\n", MD_S8VArg(from->string));
int d = 0;

170
main.c

@ -28,6 +28,8 @@
#define SOKOL_GLES2
#endif
#include "utility.h"
#ifdef WINDOWS
@ -132,7 +134,14 @@ void *web_arena_push(WebArena *arena, size_t amount)
}
void *to_return = arena->data + arena->pos;
arena->pos += amount;
assert(arena->pos < arena->cap);
bool arena_ok = arena->pos < arena->cap;
if(!arena_ok)
{
Log("Arena size: %lu\n", arena->cap);
Log("Arena pos: %lu\n", arena->pos);
}
assert(arena_ok);
return to_return;
}
@ -2728,13 +2737,13 @@ void create_screenspace_gfx_state()
.label = "outline-pass",
});
desc.sample_count = 0;
desc.sample_count = 1;
desc.label = "threedee-pass-render-target";
state.threedee_pass_image = sg_make_image(&desc);
desc.label = "threedee-pass-depth-render-target";
desc.pixel_format = SG_PIXELFORMAT_DEPTH_STENCIL;
desc.pixel_format = SG_PIXELFORMAT_DEPTH;
state.threedee_pass_depth_image = sg_make_image(&desc);
state.threedee_pass = sg_make_pass(&(sg_pass_desc){
@ -3291,21 +3300,19 @@ void init(void)
assert(desc);
shd = sg_make_shader(desc);
state.threedee_pip = sg_make_pipeline(&(sg_pipeline_desc)
{
.shader = shd,
.depth = {
state.threedee_pip = sg_make_pipeline(&(sg_pipeline_desc){
.shader = shd,
.layout = {.attrs = {
[ATTR_threedee_vs_pos_in].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_threedee_vs_uv_in].format = SG_VERTEXFORMAT_FLOAT2,
}},
.depth = {
.pixel_format = SG_PIXELFORMAT_DEPTH,
.compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true
},
.layout = {
.attrs =
{
[ATTR_threedee_vs_pos_in].format = SG_VERTEXFORMAT_FLOAT3,
[ATTR_threedee_vs_uv_in].format = SG_VERTEXFORMAT_FLOAT2,
}
},
.colors[0].blend = (sg_blend_state) { // allow transparency
.write_enabled = true,
},
.colors[0].blend = (sg_blend_state){
// allow transparency
.enabled = true,
.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
@ -3313,9 +3320,9 @@ void init(void)
.src_factor_alpha = SG_BLENDFACTOR_ONE,
.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.op_alpha = SG_BLENDOP_ADD,
},
.label = "threedee",
});
},
.label = "threedee",
});
desc = threedee_armature_shader_desc(sg_query_backend());
assert(desc);
@ -3325,8 +3332,9 @@ void init(void)
{
.shader = shd,
.depth = {
.compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true
.pixel_format = SG_PIXELFORMAT_DEPTH,
.compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true
},
.layout = {
.attrs =
@ -5588,13 +5596,14 @@ void frame(void)
// draw the 3d render
draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.threedee_pass_image), WHITE, .layer = LAYER_WORLD, .custom_pipeline = state.twodee_colorcorrect_pip });
draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.outline_pass_image), WHITE, .layer = LAYER_UI_FG, .custom_pipeline = state.twodee_outline_pip, .layer = LAYER_UI});
draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.outline_pass_image), WHITE, .custom_pipeline = state.twodee_outline_pip, .layer = LAYER_UI});
// 2d drawing TODO move this to when the drawing is flushed.
sg_begin_default_pass(&state.clear_depth_buffer_pass_action, sapp_width(), sapp_height());
sg_apply_pipeline(state.twodee_pip);
// @Place(text input drawing)
#ifdef DESKTOP
draw_quad((DrawParams){quad_at(V2(0,screen_size().y), screen_size()), IMG(image_white_square), blendalpha(BLACK, text_input_fade*0.3f), .layer = LAYER_UI_FG});
Vec2 edge_of_text = MulV2F(screen_size(), 0.5f);
if(text_input_buffer_length > 0)
@ -5604,6 +5613,7 @@ void frame(void)
}
Vec2 cursor_center = V2(edge_of_text.x,screen_size().y/2.0f);
draw_quad((DrawParams){quad_centered(cursor_center, V2(3.0f, 80.0f)), IMG(image_white_square), blendalpha(WHITE, text_input_fade * (sinf((float)elapsed_time*8.0f)/2.0f + 0.5f)), .layer = LAYER_UI_FG});
#endif
// Draw Tilemap draw tilemap tilemap drawing
#if 0
@ -6407,6 +6417,7 @@ ISANERROR("Don't know how to do this stuff on this platform.")
it->generation = gen;
}
}
ENTITIES_ITER(gs.entities)
{
if (it->perceptions_dirty && !npc_does_dialog(it))
@ -6445,7 +6456,6 @@ ISANERROR("Don't know how to do this stuff on this platform.")
#endif
#ifdef DESKTOP
// desktop http request, no more mocking
MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 ai_response = {0};
@ -6475,63 +6485,43 @@ ISANERROR("Don't know how to do this stuff on this platform.")
char *target = characters[it->memories_last->context.author_npc_kind].name;
target = characters[NPC_Player].name;
ai_response = FmtWithLint(frame_arena, "{\"target\": \"%s\", \"action\": \"%s\", \"action_argument\": \"The Player\", \"speech\": \"%s\"}", target, action, next_dialog);
#ifdef DESKTOP
it->times_talked_to += 1;
#endif
}
else
{
ai_response = MD_S8Lit("{\"target\": \"nobody\", \"action\": \"none\", \"speech\": \"\"}");
}
}
else
{
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
}
// something to mock
if (ai_response.size > 0)
{
Log("Mocking...\n");
Action a = {0};
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
// something to mock
if (ai_response.size > 0)
{
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
Log("Mocking...\n");
Action a = {0};
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
if (mocking_the_ai_response)
{
assert(succeeded);
assert(error_message.size == 0);
assert(succeeded);
assert(error_message.size == 0);
MD_String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0);
perform_action(&gs, it, a);
MD_String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0);
perform_action(&gs, it, a);
}
}
else
{
if(succeeded)
{
if (error_message.size == 0)
{
perform_action(&gs, it, a);
}
else
{
Log("There was an error with the AI: %.*s", MD_S8VArg(error_message));
append_to_errors(it, error_message);
}
}
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
}
}
MD_ReleaseScratch(scratch);
#undef SAY
#endif }
}
#endif // desktop endif
}
}
else
{
@ -6606,44 +6596,40 @@ ISANERROR("Don't know how to do this stuff on this platform.")
float speed = 0.0f;
{
if(gs.player->killed) gs.player->state = CHARACTER_KILLED;
if(gs.player->state == CHARACTER_WALKING)
switch(gs.player->state)
{
case CHARACTER_WALKING:
player_armature.go_to_animation = MD_S8Lit("Running");
}
else if(gs.player->state == CHARACTER_IDLE)
{
break;
case CHARACTER_IDLE:
player_armature.go_to_animation = MD_S8Lit("Idle");
}
else if(gs.player->state == CHARACTER_KILLED)
{
break;
case CHARACTER_KILLED:
player_armature.go_to_animation = MD_S8Lit("Die Backwards");
player_armature.next_animation_isnt_looping = true;
break;
}
else assert(false);
if (gs.player->state == CHARACTER_WALKING)
switch (gs.player->state)
{
speed = PLAYER_SPEED;
if (LenV2(movement) == 0.0)
{
case CHARACTER_WALKING:
speed = PLAYER_SPEED;
if (LenV2(movement) == 0.0)
{
gs.player->state = CHARACTER_IDLE;
}
else
{
}
}
else if (gs.player->state == CHARACTER_IDLE)
{
if (LenV2(movement) > 0.01) gs.player->state = CHARACTER_WALKING;
}
else if (gs.player->state == CHARACTER_KILLED)
{
}
else
{
assert(false); // unknown character state? not defined how to process
}
else
{
}
break;
case CHARACTER_IDLE:
if (LenV2(movement) > 0.01)
gs.player->state = CHARACTER_WALKING;
break;
case CHARACTER_KILLED:
break;
}
} // not time stopped
@ -7359,7 +7345,7 @@ sapp_desc sokol_main(int argc, char* argv[])
.frame_cb = frame,
.cleanup_cb = cleanup,
.event_cb = event,
.sample_count = 4,
.sample_count = 1, // bumping this back to 4 is troublesome for web, because there's a mismatch in sample counts. Perhaps we must upgrade to gles3, in doing so, we should upgrade to the newest sokol gfx.
.width = 800,
.height = 600,
//.gl_force_gles2 = true, not sure why this was here in example, look into

@ -2,7 +2,7 @@
#include "buff.h"
#include "HandmadeMath.h" // vector types in entity struct definition
#include "better_assert.h"
#include "utility.h"
#include <stdbool.h>
#include <string.h>
#include <stdlib.h> // atoi
@ -13,9 +13,6 @@
#define DO_CHATGPT_PARSING
#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
// 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
#define PushWithLint(arena, list, ...) { MD_S8ListPushFmt(arena, list, __VA_ARGS__); if(false) printf( __VA_ARGS__); }
@ -427,27 +424,26 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
// dump a human understandable sentence description of what happened in this memory
if(it->action_taken != ACT_none)
{
if(it->action_taken == ACT_join)
switch(it->action_taken)
{
case ACT_none:
break;
case ACT_join:
AddFmt("%s joined %s\n", characters[it->context.author_npc_kind].name, characters[it->action_argument.targeting].name);
}
else if(it->action_taken == ACT_leave)
{
// Needs better handling of when you leave, because the person you were following died. Maybe entities don't die anymore?
break;
case ACT_leave:
AddFmt("%s left their party\n", characters[it->context.author_npc_kind].name);
}
else if(it->action_taken == ACT_aim_shotgun)
{
break;
case ACT_aim_shotgun:
AddFmt("%s aimed their shotgun at %s\n", characters[it->context.author_npc_kind].name, characters[it->action_argument.targeting].name);
}
else if(it->action_taken == ACT_fire_shotgun)
{
break;
case ACT_fire_shotgun:
AddFmt("%s fired their shotgun at %s, brutally murdering them.\n", characters[it->context.author_npc_kind].name, characters[it->action_argument.targeting].name);
}
else
{
assert(false);
}
break;
case ACT_put_shotgun_away:
AddFmt("%s holstered their shotgun, no longer threatening anybody\n", characters[it->context.author_npc_kind].name);
break;
}
}
if(it->speech.text_length > 0)
{

@ -508,6 +508,7 @@ func main() {
logResponses = os.Getenv("LOG_RESPONSES") != ""
doCors = os.Getenv("CORS") != ""
if doCors { log.Println("Doing cors"); }
c = openai.NewClient(api_key)
http.HandleFunc("/completion", completion)

@ -0,0 +1,12 @@
#include <stdlib.h>
void assert_less(int x, int y) {
if (x >= y) {
abort();
}
}
int main() {
assert_less(10, 20);
assert_less(30, 20);
}

@ -5,6 +5,50 @@
@ctype vec3 Vec3
@ctype vec2 Vec2
@block inverse_functions
// Webgl 1 doesn't have inverse() but we need it, pulled from https://github.com/glslify/glsl-inverse/blob/master/index.glsl
mat4 my_inverse(mat4 m) {
float
a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
b00 = a00 * a11 - a01 * a10,
b01 = a00 * a12 - a02 * a10,
b02 = a00 * a13 - a03 * a10,
b03 = a01 * a12 - a02 * a11,
b04 = a01 * a13 - a03 * a11,
b05 = a02 * a13 - a03 * a12,
b06 = a20 * a31 - a21 * a30,
b07 = a20 * a32 - a22 * a30,
b08 = a20 * a33 - a23 * a30,
b09 = a21 * a32 - a22 * a31,
b10 = a21 * a33 - a23 * a31,
b11 = a22 * a33 - a23 * a32,
det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
return mat4(
a11 * b11 - a12 * b10 + a13 * b09,
a02 * b10 - a01 * b11 - a03 * b09,
a31 * b05 - a32 * b04 + a33 * b03,
a22 * b04 - a21 * b05 - a23 * b03,
a12 * b08 - a10 * b11 - a13 * b07,
a00 * b11 - a02 * b08 + a03 * b07,
a32 * b02 - a30 * b05 - a33 * b01,
a20 * b05 - a22 * b02 + a23 * b01,
a10 * b10 - a11 * b08 + a13 * b06,
a01 * b08 - a00 * b10 - a03 * b06,
a30 * b04 - a31 * b02 + a33 * b00,
a21 * b02 - a20 * b04 - a23 * b00,
a11 * b07 - a10 * b09 - a12 * b06,
a00 * b09 - a01 * b07 + a02 * b06,
a31 * b01 - a30 * b03 - a32 * b00,
a20 * b03 - a21 * b01 + a22 * b00) / det;
}
@end
// for this block, define a variable called `model_space_pos` to be used as an input
@block vs_compute_light_output
@ -12,7 +56,7 @@
vec4 frag_pos = view * world_space_frag_pos;
//@Speed I think we can just take the third row here and be fine.
light_dir = normalize(inverse(directional_light_space_matrix) * vec4(0.0, 0.0, -1.0, 0.0)).xyz;
light_dir = normalize(my_inverse(directional_light_space_matrix) * vec4(0.0, 0.0, -1.0, 0.0)).xyz;
light_space_fragment_position = directional_light_space_matrix * vec4(world_space_frag_pos.xyz, 1.0);
@ -47,6 +91,8 @@ float decode_normalized_float32(vec4 v)
return sign * (v.z*255.0 + v.y);
}
@include_block inverse_functions
void main() {
vec4 total_position = vec4(0.0f);
@ -104,6 +150,8 @@ uniform vs_params {
vec3 wobble_world_source;
};
@include_block inverse_functions
void main() {
//vec3 transformed_pos = vec3(pos_in.x, pos_in.y + sin(pos_in.x * 5.0 + pos_in.y * 9.0 + time*1.9)*0.045, pos_in.z);

@ -23,8 +23,8 @@
#define BUBBLE_LINES_PER_PAGE 2
#define AI_MAX_BUBBLE_PAGES_IN_OUTPUT 2
#define ARENA_SIZE (1024*1024*10)
#define BIG_ARENA_SIZE (ARENA_SIZE * 8)
#define ARENA_SIZE (1024*1024*20)
#define BIG_ARENA_SIZE (ARENA_SIZE * 4)
#ifdef DEVTOOLS
// server url cannot have trailing slash
@ -49,7 +49,7 @@
#define MAXIMUM_THREEDEE_THINGS 1024
#define ANIMATION_BLEND_TIME 0.15f
#define REMEMBERED_MEMORIES 32
#define REMEMBERED_MEMORIES 64
#define REMEMBERED_ERRORS 6
#define MAX_AFTERIMAGES 6

@ -1,16 +1,21 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#ifdef WEB
#include <assert.h>
//#include <assert.h>
#include <signal.h>
#endif
static inline void assert_impl(bool cond, const char *expression)
static void assert_impl(bool cond, const char *expression)
{
if(!cond)
{
fprintf(stderr, "Assertion failed: %s\n", expression);
#ifdef WEB
assert(false);
raise(SIGTRAP);
//assert(false);
#elif defined(DESKTOP)
__debugbreak();
#else
@ -23,3 +28,5 @@ static inline void assert_impl(bool cond, const char *expression)
#endif
#define assert(cond) assert_impl(cond, #cond)
#define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }

@ -216,7 +216,7 @@ body {
};
window.onerror = function(event) {
console.log("onerror: " + event.message);
for(;;) {}
//for(;;) {}
};
</script>
<script type="text/javascript">
@ -367,6 +367,8 @@ function load_all() {
console.log("Reading full game");
let decoded = decode(read_data);
Module.ccall('read_from_save_data', 'void', ['array', 'number'], [new Uint8Array(decoded), decoded.byteLength]);
} else {
console.log("Couldn't find full game");
}
}
}

Loading…
Cancel
Save