diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 5b61f3a..e84515b 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -20,6 +20,7 @@
+
@@ -28,39 +29,25 @@
-
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
@@ -72,6 +59,13 @@
+
+
+
@@ -99,47 +93,65 @@
- {
- "keyToString": {
- "CMake Application.quakespasm.executor": "Run",
- "CidrDebugProfileConversion": "true",
- "ModuleVcsDetector.initialDetectionPerformed": "true",
- "RunOnceActivity.RadMigrateCodeStyle": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.cidr.known.project.marker": "true",
- "RunOnceActivity.git.unshallow": "true",
- "RunOnceActivity.readMode.enableVisualFormatting": "true",
- "RunOnceActivity.typescript.service.memoryLimit.init": "true",
- "SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "cf.first.check.clang-format": "false",
- "cidr.known.project.marker": "true",
- "codeWithMe.voiceChat.enabledByDefault": "false",
- "git-widget-placeholder": "master",
- "last_opened_file_path": "/home/iikorni/Downloads/quakespasm-0.96.3/cmake-build-debug/id1",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.jshint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.jshint": "",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "language.cpp.clang-tidy"
+
+}]]>
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
"profileIdByStamp": [
@@ -148,10 +160,10 @@
"##RUN_CONFIGURATION##": "CMake Application.quakespasm",
"cmake": "CMakeBuildProfile:Debug"
},
- "second": "27852a6b-aee6-4e6c-a703-78c08d632bb7"
+ "second": "e1a0f256-4845-48cc-a80a-0ace00fa20d6"
}
],
- "modificationCount": 4
+ "modificationCount": 5
}
@@ -192,6 +204,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0110a52..77a6b4e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,9 @@ find_package(SDL_net)
find_package(OpenGL)
add_executable(quakespasm Quake/bgmusic.cpp Quake/cd_sdl.cpp Quake/cfgfile.cpp Quake/chase.cpp Quake/cl_demo.cpp Quake/cl_input.cpp Quake/cl_main.cpp Quake/cl_parse.cpp Quake/cl_tent.cpp Quake/cmd.cpp Quake/common.cpp Quake/console.cpp Quake/crc.cpp Quake/convar.cpp Quake/gl_draw.cpp Quake/gl_fog.cpp Quake/gl_mesh.cpp Quake/gl_model.cpp Quake/gl_refrag.cpp Quake/gl_rlight.cpp Quake/gl_rmain.cpp Quake/gl_rmisc.cpp Quake/gl_screen.cpp Quake/gl_sky.cpp Quake/gl_texmgr.cpp Quake/gl_vidsdl.cpp Quake/gl_warp.cpp Quake/host.cpp Quake/host_cmd.cpp Quake/image.cpp Quake/in_sdl.cpp Quake/keys.cpp Quake/main_sdl.cpp Quake/mathlib.cpp Quake/menu.cpp Quake/miniz.cpp Quake/net_bsd.cpp Quake/net_dgrm.cpp Quake/net_loop.cpp Quake/net_main.cpp Quake/net_udp.cpp Quake/pl_linux.cpp Quake/pr_cmds.cpp Quake/pr_edict.cpp Quake/pr_exec.cpp Quake/r_alias.cpp Quake/r_brush.cpp Quake/r_part.cpp Quake/r_sprite.cpp Quake/r_world.cpp Quake/sbar.cpp Quake/snd_codec.cpp Quake/snd_dma.cpp Quake/snd_flac.cpp Quake/snd_mem.cpp Quake/snd_mikmod.cpp Quake/snd_mix.cpp Quake/snd_modplug.cpp Quake/snd_mp3.cpp Quake/snd_mp3tag.cpp Quake/snd_mpg123.cpp Quake/snd_opus.cpp Quake/snd_sdl.cpp Quake/snd_umx.cpp Quake/snd_vorbis.cpp Quake/snd_wave.cpp Quake/snd_xmp.cpp Quake/strlcat.cpp Quake/strlcpy.cpp Quake/sv_main.cpp Quake/sv_move.cpp Quake/sv_phys.cpp Quake/sv_user.cpp Quake/sys_sdl_unix.cpp Quake/view.cpp Quake/wad.cpp Quake/world.cpp Quake/zone.cpp
- Quake/net_msg.cpp)
+ Quake/net_msg.cpp
+ Quake/pr_wren.cpp
+ Quake/vec.cpp)
target_link_libraries(quakespasm OpenGL::GL)
target_link_libraries(quakespasm SDL::SDL)
@@ -18,4 +20,16 @@ target_link_libraries(quakespasm ogg)
target_link_libraries(quakespasm mad)
target_link_libraries(quakespasm m)
-add_compile_definitions(USE_CODEC_VORBIS)
\ No newline at end of file
+add_subdirectory(wren)
+
+target_link_libraries(quakespasm wren)
+include_directories(wren/include)
+
+add_compile_definitions(USE_CODEC_VORBIS)
+
+#add_custom_command(
+# TARGET quakespasm POST_BUILD
+# COMMAND ${CMAKE_COMMAND} -E copy_directory
+# "${CMAKE_CURRENT_SOURCE_DIR}/wprog"
+# "${CMAKE_CURRENT_BINARY_DIR}/id1/wprog"
+#)
\ No newline at end of file
diff --git a/Quake/cl_input.cpp b/Quake/cl_input.cpp
index 2c847a1..5becba9 100644
--- a/Quake/cl_input.cpp
+++ b/Quake/cl_input.cpp
@@ -90,11 +90,10 @@ void KeyDown(kbutton_t *b) {
void KeyUp(kbutton_t *b) {
int k;
- const char *c;
- c = command::argv(1)->c_str();
- if (c[0])
- k = atoi(c);
+ auto c = command::argv(1);
+ if (c.has_value())
+ k = atoi(c->c_str());
else {
// typed manually at the console, assume for unsticking, so clear all
b->down[0] = b->down[1] = 0;
diff --git a/Quake/cmd.cpp b/Quake/cmd.cpp
index 48cac65..a58bbae 100644
--- a/Quake/cmd.cpp
+++ b/Quake/cmd.cpp
@@ -446,14 +446,18 @@ namespace command {
return; // not really connected
cls.message.write_byte(clc_stringcmd);
- if (std::is_eq(common::strcasecmp(argv(0).value_or(""), "cmd"))) {
+ if (std::is_neq(common::strcasecmp(argv(0).value_or(""), "cmd"))) {
if (argc() > 1) {
cls.message.write_string(std::format("{} {}", argv(0).value(), args()));
} else {
cls.message.write_string(std::format("{} \n", argv(0).value()));
}
} else {
- cls.message.write_string("\n");
+ if (argc() > 1) {
+ cls.message.write_string(std::format("{}", args()));
+ } else {
+ cls.message.write_string("\n");
+ }
}
}
diff --git a/Quake/common.cpp b/Quake/common.cpp
index c2fef92..538bb07 100644
--- a/Quake/common.cpp
+++ b/Quake/common.cpp
@@ -1257,9 +1257,9 @@ static pack_t *COM_LoadPackFile(const char *packfile) {
Sys_Error("Error reading %s", packfile);
// crc the directory to check for modifications
- CRC_Init(&crc);
+ crc::init(&crc);
for (i = 0; i < header.dirlen; i++)
- CRC_ProcessByte(&crc, ((byte *) info)[i]);
+ crc::process_byte(&crc, ((byte *) info)[i]);
if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100)
com_modified = true;
diff --git a/Quake/gl_model.cpp b/Quake/gl_model.cpp
index 6ceedc9..0ec925a 100644
--- a/Quake/gl_model.cpp
+++ b/Quake/gl_model.cpp
@@ -893,7 +893,7 @@ static void Mod_LoadEntities (lump_t *l)
mark = Hunk_LowMark();
if (l->filelen > 0) {
- crc = CRC_Block(mod_base + l->fileofs, l->filelen - 1);
+ crc = crc::block(mod_base + l->fileofs, l->filelen - 1);
}
q_strlcpy(basemapname, loadmodel->name, sizeof(basemapname));
diff --git a/Quake/gl_texmgr.cpp b/Quake/gl_texmgr.cpp
index 51b1092..a5446da 100644
--- a/Quake/gl_texmgr.cpp
+++ b/Quake/gl_texmgr.cpp
@@ -1057,7 +1057,7 @@ static void TexMgr_LoadImage8(gltexture_t *glt, byte *data) {
// HACK HACK HACK -- taken from tomazquake
if (strstr(glt->name, "shot1sid") &&
glt->width == 32 && glt->height == 32 &&
- CRC_Block(data, 1024) == 65393) {
+ crc::block(data, 1024) == 65393) {
// This texture in b_shell1.bsp has some of the first 32 pixels painted white.
// They are invisible in software, but look really ugly in GL. So we just copy
// 32 pixels from the bottom to make it look nice.
@@ -1160,13 +1160,13 @@ gltexture_t *TexMgr_LoadImage(qmodel_t *owner, const char *name, int width, int
// cache check
switch (format) {
case SRC_INDEXED:
- crc = CRC_Block(data, width * height);
+ crc = crc::block(data, width * height);
break;
case SRC_LIGHTMAP:
- crc = CRC_Block(data, width * height * lightmap_bytes);
+ crc = crc::block(data, width * height * lightmap_bytes);
break;
case SRC_RGBA:
- crc = CRC_Block(data, width * height * 4);
+ crc = crc::block(data, width * height * 4);
break;
default: /* not reachable but avoids compiler warnings */
crc = 0;
diff --git a/Quake/gl_vidsdl.cpp b/Quake/gl_vidsdl.cpp
index 60e3b8f..c37b087 100644
--- a/Quake/gl_vidsdl.cpp
+++ b/Quake/gl_vidsdl.cpp
@@ -1335,6 +1335,7 @@ void GL_EndRendering(void) {
#if defined(USE_SDL2)
SDL_GL_SwapWindow(draw_context);
#else
+
SDL_GL_SwapBuffers();
#endif
}
diff --git a/Quake/host.cpp b/Quake/host.cpp
index 3563eee..6de74c8 100644
--- a/Quake/host.cpp
+++ b/Quake/host.cpp
@@ -26,6 +26,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "bgmusic.hpp"
#include
+#include "pr_wren.hpp"
+
/*
A server can allways be started, even if the system started out as a client
@@ -792,6 +794,7 @@ void Host_Init(void) {
console::init();
}
PR_Init();
+ wren::init();
Mod_Init();
NET_Init();
SV_Init();
diff --git a/Quake/pr_edict.cpp b/Quake/pr_edict.cpp
index d1f0294..1657ff1 100644
--- a/Quake/pr_edict.cpp
+++ b/Quake/pr_edict.cpp
@@ -21,9 +21,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// sv_edict.c -- entity dictionary
+#include
#include
+#include "pr_wren.hpp"
#include "quakedef.hpp"
+#include "wren.hpp"
dprograms_t *progs;
dfunction_t *pr_functions;
@@ -1006,6 +1009,7 @@ void ED_LoadFromFile (const char *data)
pr_global_struct->time = sv.time;
std::istringstream ss{data};
+ sv.wentities = std::map{};
// parse ents
@@ -1022,8 +1026,12 @@ void ED_LoadFromFile (const char *data)
ent = EDICT_NUM(0);
else
ent = ED_Alloc ();
+ std::istringstream copy{data};
+ copy.seekg(ss.tellg());
ED_ParseEdict (ss, ent);
+ auto vars = wren::parse_fields(copy);
+
// remove things from different skill levels or deathmatch
if (deathmatch.value)
{
@@ -1069,6 +1077,11 @@ void ED_LoadFromFile (const char *data)
pr_global_struct->self = EDICT_TO_PROG(ent);
PR_ExecuteProgram (func - pr_functions);
+ auto e = wren::create_entity(PR_GetString(ent->v.classname), vars);
+ if (e.has_value()) {
+ ent->wid = e->first;
+ sv.wentities[e->first] = e->second;
+ }
}
console::debug ("%i entities inhibited\n", inhibit);
@@ -1175,7 +1188,7 @@ void PR_LoadProgs (void)
for (i = 0; i < GEFV_CACHESIZE; i++)
gefvCache[i].field[0] = 0;
- CRC_Init (&pr_crc);
+ crc::init (&pr_crc);
progs = (dprograms_t *)COM_LoadHunkFile ("progs.dat", NULL);
if (!progs)
@@ -1183,7 +1196,7 @@ void PR_LoadProgs (void)
console::debug ("Programs occupy %iK.\n", com_filesize/1024);
for (i = 0; i < com_filesize; i++)
- CRC_ProcessByte (&pr_crc, ((byte *)progs)[i]);
+ crc::process_byte (&pr_crc, ((byte *)progs)[i]);
// byte swap the header
for (i = 0; i < (int) sizeof(*progs) / 4; i++)
@@ -1296,7 +1309,6 @@ void PR_Init (void)
saved4.inscribe();
}
-
edict_t *EDICT_NUM(int n)
{
if (n < 0 || n >= sv.max_edicts)
diff --git a/Quake/pr_exec.cpp b/Quake/pr_exec.cpp
index e552c46..52c135a 100644
--- a/Quake/pr_exec.cpp
+++ b/Quake/pr_exec.cpp
@@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include "pr_wren.hpp"
#include "quakedef.hpp"
typedef struct
diff --git a/Quake/pr_wren.cpp b/Quake/pr_wren.cpp
new file mode 100644
index 0000000..c514bac
--- /dev/null
+++ b/Quake/pr_wren.cpp
@@ -0,0 +1,695 @@
+#include "pr_wren.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "quakedef.hpp"
+#include "q_stdinc.hpp"
+#include "console.hpp"
+#include "sys.hpp"
+#include "vec.hpp"
+#include "../wren/vm/wren_debug.h"
+
+
+namespace {
+ WrenVM *_vm;
+ int entity_counter{0};
+ std::map class_handles{};
+ std::map function_handles{};
+ std::map entity_vars{};
+ bool silentErrors{false};
+
+ void writeFn(WrenVM *vm, const char *text) {
+ console::info("%s", text);
+ }
+
+ void errorFn(WrenVM *vm, WrenErrorType type, const char *module, int line,
+ const char *message) {
+ Host_Error("%s:%d - %s", module, line, message);
+ }
+
+ void _set_size(WrenVM *vm) {
+ wrenEnsureSlots(vm, 7);
+ auto id = static_cast(wrenGetSlotDouble(vm, 1));
+ auto vars = entity_vars[id];
+ wrenGetListElement(vm, 2, 0, 4);
+ wrenGetListElement(vm, 2, 1, 5);
+ wrenGetListElement(vm, 2, 2, 6);
+ const auto mins = math::vec3f{
+ static_cast(wrenGetSlotDouble(vm, 4)), static_cast(wrenGetSlotDouble(vm, 5)),
+ static_cast(wrenGetSlotDouble(vm, 6))
+ };
+ wrenGetListElement(vm, 3, 0, 4);
+ wrenGetListElement(vm, 3, 1, 5);
+ wrenGetListElement(vm, 3, 2, 6);
+ const auto maxs = math::vec3f{
+ static_cast(wrenGetSlotDouble(vm, 4)), static_cast(wrenGetSlotDouble(vm, 5)),
+ static_cast(wrenGetSlotDouble(vm, 6))
+ };
+
+ vars->mins = mins;
+ vars->maxs = maxs;
+ }
+
+ // error(err)
+ void _error(WrenVM *vm) {
+ const std::string err = wrenGetSlotString(vm, 1);
+
+ console::info("WREN ERROR: %s\n", err.c_str());
+ // TODO: see if there's literally any way to get the call stack
+ Host_Error("Program error");
+ }
+
+ // normalize(vec)
+ void _normalize(WrenVM *vm) {
+ wrenEnsureSlots(vm, 5);
+ wrenGetListElement(vm, 1, 0, 2);
+ wrenGetListElement(vm, 1, 1, 3);
+ wrenGetListElement(vm, 1, 2, 4);
+ auto vec = math::vec3f{
+ static_cast(wrenGetSlotDouble(vm, 2)), static_cast(wrenGetSlotDouble(vm, 3)),
+ static_cast(wrenGetSlotDouble(vm, 4))
+ };
+ vec = vec.normalize();
+ wrenEnsureSlots(vm, 4);
+ wrenSetSlotNewList(vm, 0);
+ wrenSetSlotDouble(vm, 1, vec.x);
+ wrenSetSlotDouble(vm, 2, vec.y);
+ wrenSetSlotDouble(vm, 3, vec.z);
+ wrenInsertInList(vm, 0, 0, 1);
+ wrenInsertInList(vm, 0, 1, 2);
+ wrenInsertInList(vm, 0, 2, 3);
+ }
+
+ void _remove(WrenVM *vm) {
+ const auto id = static_cast(wrenGetSlotDouble(vm, 1));
+ const auto handle = sv.wentities[id];
+ sv.wentities[id] = nullptr;
+ entity_vars[id]->free = true;
+ if (handle != nullptr) {
+ wrenReleaseHandle(vm, handle);
+ }
+ }
+
+ void _precache_model(WrenVM *vm) {
+ const auto model = wrenGetSlotString(vm, 1);
+
+ for (auto i = 0; i < MAX_MODELS; i++) {
+ if (sv.model_precache[i] == nullptr) {
+ sv.model_precache[i] = model;
+ sv.models[i] = Mod_ForName(model, true);
+ return;
+ }
+ if (std::strcmp(sv.model_precache[i], model) == 0) {
+ return;
+ }
+ }
+ }
+
+ // preache_sound(sound)
+ void _precache_sound(WrenVM *vm) {
+ const auto sample = wrenGetSlotString(vm, 1);
+
+ if (sv.state != ss_loading) {
+ console::info("Precache can only be done in spawn functions");
+ Host_Error("Wren Error");
+ }
+
+ for (auto &i: sv.sound_precache) {
+ if (i == nullptr) {
+ i = sample;
+ break;
+ }
+ if (std::strcmp(i, sample) == 0) {
+ return;
+ }
+ }
+ }
+
+ void _find_radius(WrenVM *vm) {
+ wrenEnsureSlots(vm, 5);
+ auto radius = static_cast(wrenGetSlotDouble(vm, 2));
+ radius *= radius;
+ wrenGetListElement(vm, 1, 0, 2);
+ wrenGetListElement(vm, 1, 1, 3);
+ wrenGetListElement(vm, 1, 2, 4);
+ auto origin = math::vec3f{
+ static_cast(wrenGetSlotDouble(vm, 2)), static_cast(wrenGetSlotDouble(vm, 3)),
+ static_cast(wrenGetSlotDouble(vm, 4))
+ };
+
+ wrenSetSlotNewList(vm, 0);
+ auto count = 0;
+ for (auto [id, ent]: sv.wentities) {
+ float d;
+ //
+ // if (ent->v.solid == SOLID_NOT)
+ // continue;
+ auto vars = entity_vars[id];
+
+ d = origin.x - (vars->origin.x + (vars->mins.x + vars->maxs.x) * 0.5f);
+ float lensq = d * d;
+ if (lensq > radius)
+ continue;
+ d = origin.y - (vars->origin.y + (vars->mins.y + vars->maxs.y) * 0.5f);
+ lensq += d * d;
+ if (lensq > radius)
+ continue;
+ d = origin.z - (vars->origin.z + (vars->mins.z + vars->maxs.z) * 0.5f);
+ lensq += d * d;
+ if (lensq > radius)
+ continue;
+
+ wrenSetSlotHandle(vm, 1, ent);
+ wrenInsertInList(vm, 0, count++, 1);
+ }
+ }
+
+ // sound(entity, channel, sample, volume, attenuation)
+ void _sound(WrenVM *vm) {
+ const auto entity = static_cast(wrenGetSlotDouble(vm, 1));
+ const auto channel = static_cast(wrenGetSlotDouble(vm, 2));
+ std::string sample{};
+ if (wrenGetSlotType(vm, 3) == WREN_TYPE_STRING) {
+ sample = wrenGetSlotString(vm, 3);
+ } else {
+ return;
+ }
+ const auto volume = static_cast(wrenGetSlotDouble(vm, 4) * 255);
+ const auto attenuation = static_cast(wrenGetSlotDouble(vm, 5));
+
+ SV_StartSound(&sv.edicts[entity], channel, sample.c_str(), volume, attenuation);
+ }
+
+ net::msg *_write_destination(const int dest) {
+ switch (dest) {
+ case 0: // MSG_BROADCAST
+ return &sv.datagram;
+
+ case 1: {
+ // MSG_ONE
+ auto ent = PROG_TO_EDICT(pr_global_struct->msg_entity);
+ auto entnum = NUM_FOR_EDICT(ent);
+ if (entnum < 1 || entnum > svs.maxclients)
+ PR_RunError("WriteDest: not a client");
+ return &svs.clients[entnum - 1].message;
+ }
+
+ case 2: // MSG_ALL
+ return &sv.reliable_datagram;
+
+ case 3: // MSG_INIT
+ return &sv.signons[sv.current_signon];
+
+ default:
+ PR_RunError("_write_destination: bad destination");
+ break;
+ }
+ }
+
+ void _write_byte(WrenVM *vm) {
+ auto dest = _write_destination(static_cast(wrenGetSlotDouble(vm, 1)));
+ const auto value = static_cast(wrenGetSlotDouble(vm, 2));
+
+ dest->write_byte(value);
+ }
+
+ void _write_char(WrenVM *vm) {
+ auto dest = _write_destination(static_cast(wrenGetSlotDouble(vm, 1)));
+ const auto value = static_cast(wrenGetSlotDouble(vm, 2));
+
+ dest->write_char(value);
+ }
+
+ void _write_coord(WrenVM *vm) {
+ auto dest = _write_destination(static_cast(wrenGetSlotDouble(vm, 1)));
+ const auto value = static_cast(wrenGetSlotDouble(vm, 2));
+
+ dest->write_coord(value, static_cast(sv.protocolflags));
+ }
+
+ void _time(WrenVM *vm) {
+ wrenSetSlotDouble(vm, 0, sv.time);
+ }
+
+ void _deathmatch(WrenVM *vm) {
+ wrenSetSlotBool(vm, 0, deathmatch.value != 0.0);
+ }
+
+ void _entvars_allocate(WrenVM *vm) {
+ auto vars = static_cast(wrenSetSlotNewForeign(vm, 0, 0, sizeof(wren::entvars)));
+ vars->id = entity_counter++;
+ vars->origin = math::ORIGIN;
+ vars->next_think = 0;
+ entity_vars[vars->id] = vars;
+ }
+
+ void _entvars_id(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+
+ wrenSetSlotDouble(vm, 0, vars->id);
+ }
+
+ void _entvars_origin(WrenVM *vm) {
+ wrenEnsureSlots(vm, 4);
+
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+
+ wrenSetSlotNewList(vm, 0);
+ wrenSetSlotDouble(vm, 1, vars->origin.x);
+ wrenSetSlotDouble(vm, 2, vars->origin.y);
+ wrenSetSlotDouble(vm, 3, vars->origin.z);
+ wrenInsertInList(vm, 0, 0, 1);
+ wrenInsertInList(vm, 0, 1, 2);
+ wrenInsertInList(vm, 0, 2, 3);
+ }
+
+ void _entvars_set_origin(WrenVM *vm) {
+ wrenEnsureSlots(vm, 5);
+
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+
+ wrenGetListElement(vm, 1, 0, 2);
+ wrenGetListElement(vm, 1, 1, 3);
+ wrenGetListElement(vm, 1, 2, 4);
+ const auto origin = math::vec3f{
+ static_cast(wrenGetSlotDouble(vm, 2)), static_cast(wrenGetSlotDouble(vm, 3)),
+ static_cast(wrenGetSlotDouble(vm, 4))
+ };
+
+ vars->origin = origin;
+ }
+
+ void _entvars_next_think(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ wrenEnsureSlots(vm, 1);
+ wrenSetSlotDouble(vm, 0, vars->next_think);
+ }
+
+ void _entvars_set_next_think(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ const auto next_think = static_cast(wrenGetSlotDouble(vm, 1));
+ vars->next_think = next_think;
+ }
+
+ void _entvars_target_name(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ if (!vars->target_name.empty()) {
+ wrenSetSlotString(vm, 0, vars->target_name.c_str());
+ } else {
+ wrenSetSlotNull(vm, 0);
+ }
+ }
+
+ void _entvars_class_name(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ if (!vars->class_name.empty()) {
+ wrenSetSlotString(vm, 0, vars->class_name.c_str());
+ } else {
+ wrenSetSlotNull(vm, 0);
+ }
+ }
+
+ void _entvars_spawn_flags(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ wrenSetSlotDouble(vm, 0, vars->spawn_flags);
+ }
+
+ void _entvars_set_spawn_flags(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ const auto spawn_flags = static_cast(wrenGetSlotDouble(vm, 1));
+ vars->spawn_flags = spawn_flags;
+ }
+
+ void _entvars_sounds(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ wrenSetSlotDouble(vm, 0, vars->sounds);
+ }
+
+ void _entvars_noise(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ if (!vars->noise.empty()) {
+ wrenSetSlotString(vm, 0, vars->noise.c_str());
+ } else {
+ wrenSetSlotNull(vm, 0);
+ }
+ }
+
+ void _entvars_set_noise(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ vars->noise = wrenGetSlotString(vm, 1);
+ }
+
+ std::map _entvars_type_map{
+ {"targetname", wren::typing::STRING},
+ {"classname", wren::typing::STRING},
+ {"origin", wren::typing::VECTOR},
+ {"spawnflags", wren::typing::INT},
+ {"sounds", wren::typing::FLOAT}
+ };
+
+
+ void _entvars_field_setter(WrenVM *vm) {
+ wrenEnsureSlots(vm, 9);
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ std::string name = wrenGetSlotString(vm, 1);
+ if (name == "classname") {
+ vars->class_name = wrenGetSlotString(vm, 2);
+ return;
+ }
+ if (name == "targetname") {
+ vars->target_name = wrenGetSlotString(vm, 2);
+ return;
+ }
+ if (name == "origin") {
+ vars->origin = math::vec3f{};
+ wrenGetListElement(vm, 2, 0, 3);
+ wrenGetListElement(vm, 2, 1, 4);
+ wrenGetListElement(vm, 2, 2, 5);
+ vars->origin.x = static_cast(wrenGetSlotDouble(vm, 3));
+ vars->origin.y = static_cast(wrenGetSlotDouble(vm, 4));
+ vars->origin.z = static_cast(wrenGetSlotDouble(vm, 5));
+ return;
+ }
+ if (name == "spawnflags") {
+ vars->spawn_flags = static_cast(wrenGetSlotDouble(vm, 2));
+ return;
+ }
+ if (name == "sounds") {
+ vars->sounds = static_cast(wrenGetSlotDouble(vm, 2));
+ return;
+ }
+ Host_Error("_entvars_field_setter: tried to set %s, but no setter defined", name.c_str());
+ }
+
+ void _entvars_field_type(WrenVM *vm) {
+ const auto vars = static_cast(wrenGetSlotForeign(vm, 0));
+ const std::string name = wrenGetSlotString(vm, 1);
+ auto kind = wren::typing::UNDEFINED;
+ if (_entvars_type_map.contains(name)) {
+ kind = _entvars_type_map[name];
+ }
+ wrenSetSlotDouble(vm, 0, static_cast(kind));
+ }
+
+ WrenForeignClassMethods bindForeignClassFn(
+ WrenVM *vm, const char *module, const char *className) {
+ const std::string modname{module};
+ const std::string classname{className};
+ WrenForeignClassMethods methods{.allocate = nullptr, .finalize = nullptr};
+
+ if (classname == "EntityVariables") {
+ methods.allocate = _entvars_allocate;
+ }
+
+ return methods;
+ }
+
+
+ std::map > exported_functions{
+ {
+ "EntityVariables",
+ {
+ {"id", _entvars_id},
+ {"origin", _entvars_origin},
+ {"origin=(_)", _entvars_set_origin},
+ {"next_think", _entvars_next_think},
+ {"next_think=(_)", _entvars_set_next_think},
+ {"target_name", _entvars_target_name},
+ {"class_name", _entvars_class_name},
+ {"field_setter(_,_)", _entvars_field_setter},
+ {"field_type(_)", _entvars_field_type},
+ {"spawn_flags", _entvars_spawn_flags},
+ {"spawn_flags=(_)", _entvars_set_spawn_flags},
+ {"sounds", _entvars_sounds},
+ {"noise", _entvars_noise},
+ {"noise=(_)", _entvars_set_noise},
+ },
+ },
+ {
+ "Quake",
+ {
+ {"set_size(_,_,_)", _set_size},
+ {"error(_)", _error},
+ {"normalize(_)", _normalize},
+ {"remove(_)", _remove},
+ {"precache_model(_)", _precache_model},
+ {"precache_sound(_)", _precache_sound},
+ {"find_radius(_,_)", _find_radius},
+ {"sound(_,_,_,_,_)", _sound},
+ {"write_byte(_,_)", _write_byte},
+ {"write_char(_,_)", _write_char},
+ {"write_coord(_,_)", _write_coord},
+ {"time", _time},
+ {"deathmatch", _deathmatch},
+ }
+ },
+ };
+
+ WrenForeignMethodFn bindForeignMethodFn(WrenVM *vm, const char *module, const char *className, bool isStatic,
+ const char *signature) {
+ const std::string modname{module};
+ const std::string classname{className};
+ const std::string sig{signature};
+
+ if (modname == "quake") {
+ if (exported_functions.contains(classname)) {
+ if (auto &clazz = exported_functions[classname]; clazz.contains(sig)) {
+ return clazz[sig];
+ }
+ }
+ }
+ return nullptr;
+ }
+}
+
+void wren::init() {
+ WrenConfiguration config;
+ wrenInitConfiguration(&config);
+ config.writeFn = writeFn;
+ config.errorFn = errorFn;
+ config.bindForeignMethodFn = bindForeignMethodFn;
+ config.bindForeignClassFn = bindForeignClassFn;
+ _vm = wrenNewVM(&config);
+ function_handles["vars"] = nullptr;
+ function_handles["new()"] = nullptr;
+ function_handles["spawn()"] = nullptr;
+ function_handles["think()"] = nullptr;
+ function_handles["field_type(_)"] = nullptr;
+ function_handles["field_setter(_,_)"] = nullptr;
+}
+
+std::optional > wren::create_entity(const std::string &classname,
+ std::map pairs) {
+ if (!wrenHasVariable(_vm, "quake", classname.c_str())) {
+ console::safe_print("No Wren spawn function for %s\n", classname.c_str());
+ return std::nullopt;
+ }
+
+ wrenEnsureSlots(_vm, 2);
+ if (!class_handles.contains(classname)) {
+ wrenEnsureSlots(_vm, 1);
+ wrenGetVariable(_vm, "quake", classname.c_str(), 0);
+ class_handles[classname.c_str()] = wrenGetSlotHandle(_vm, 0);
+ }
+
+ wrenSetSlotHandle(_vm, 0, class_handles[classname]);
+ wrenCall(_vm, function_handles["new()"]);
+ const auto handle = wrenGetSlotHandle(_vm, 0);
+
+ for (const auto &[key, value]: pairs) {
+ set_field(handle, key, value);
+ }
+
+ entity_vars[entity_counter - 1]->next_think = sv.time;
+
+ wrenSetSlotHandle(_vm, 0, handle);
+ wrenCall(_vm, function_handles["spawn()"]);
+ if (entity_vars[entity_counter - 1]->free) {
+ return std::nullopt;
+ }
+
+ console::info("loaded %s\n", classname.c_str());
+
+ return std::pair{entity_counter - 1, handle};
+}
+
+
+void wren::load_progs() {
+ wrenNukeModules(_vm);
+ std::filesystem::directory_iterator dir{"id1/wprog"};
+ for (auto f: dir) {
+ if (f.path().extension() == ".wren") {
+ std::ifstream ifs{f.path()};
+ std::stringstream ss{};
+ ss << ifs.rdbuf();
+ wrenInterpret(_vm, "quake", ss.str().c_str());
+ }
+ }
+
+ for (const auto& [name, handle] : class_handles) {
+ if (handle) wrenReleaseHandle(_vm, handle);
+ wrenEnsureSlots(_vm, 1);
+ wrenGetVariable(_vm, "quake", name.c_str(), 0);
+ class_handles[name] = wrenGetSlotHandle(_vm, 0);
+ }
+
+ for (const auto &[signature, handle]: function_handles) {
+ if (handle) wrenReleaseHandle(_vm, handle);
+ function_handles[signature] = wrenMakeCallHandle(_vm, signature.c_str());
+ }
+
+ entity_vars.clear();
+ entity_counter = 0;
+
+ wrenEnsureSlots(_vm, 1);
+ wrenGetVariable(_vm, "quake", "EntityVariables", 0);
+ class_handles["EntityVariables"] = wrenGetSlotHandle(_vm, 0);
+ wrenEnsureSlots(_vm, 1);
+ wrenGetVariable(_vm, "quake", "Typing", 0);
+ class_handles["Typing"] = wrenGetSlotHandle(_vm, 0);
+}
+
+void wren::run_think(const int index) {
+ const auto ent = sv.wentities[index];
+ if (ent == nullptr) {
+ return;
+ }
+
+ auto vars = entity_vars[index];
+
+ auto thinktime = vars->next_think;
+ if (thinktime <= 0 || thinktime > sv.time + host_frametime)
+ return;
+
+ if (thinktime < sv.time)
+ thinktime = sv.time;
+
+ // ent->oldthinktime = thinktime;
+ // ent->oldframe = ent->v.frame; //johnfitz
+
+ vars->next_think = 0;
+
+ wrenEnsureSlots(_vm, 1);
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenCall(_vm, function_handles["think()"]);
+}
+
+void wren::set_field(WrenHandle *ent, std::string key, std::string value) {
+ wrenEnsureSlots(_vm, 9);
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenSetSlotString(_vm, 1, key.c_str());
+ wrenCall(_vm, function_handles["field_type(_)"]);
+ auto kind = static_cast(wrenGetSlotDouble(_vm, 0));
+ switch (kind) {
+ case typing::UNDEFINED:
+ console::info("FIELD %s IS UNDEFINED\n", key.c_str());
+ break;
+ case typing::STRING:
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenSetSlotString(_vm, 1, key.c_str());
+ wrenSetSlotString(_vm, 2, value.c_str());
+ wrenCall(_vm, function_handles["field_setter(_,_)"]);
+ break;
+ case typing::INT: {
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenSetSlotString(_vm, 1, key.c_str());
+ auto i = std::atoi(value.c_str());
+ wrenSetSlotDouble(_vm, 2, i);
+ wrenCall(_vm, function_handles["field_setter(_,_)"]);
+ break;
+ }
+ case typing::FLOAT: {
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenSetSlotString(_vm, 1, key.c_str());
+ auto i = static_cast(std::atof(value.c_str()));
+ wrenSetSlotDouble(_vm, 2, i);
+ wrenCall(_vm, function_handles["field_setter(_,_)"]);
+ break;
+ }
+ case typing::VECTOR: {
+ math::vec3f res{};
+ std::sscanf(value.c_str(), "%f %f %f", &res.x, &res.y, &res.z);
+ wrenSetSlotHandle(_vm, 0, ent);
+ wrenSetSlotString(_vm, 1, key.c_str());
+ wrenSetSlotNewList(_vm, 2);
+ wrenSetSlotDouble(_vm, 3, res.x);
+ wrenSetSlotDouble(_vm, 4, res.y);
+ wrenSetSlotDouble(_vm, 5, res.z);
+ wrenInsertInList(_vm, 2, 0, 3);
+ wrenInsertInList(_vm, 2, 1, 4);
+ wrenInsertInList(_vm, 2, 2, 5);
+ wrenCall(_vm, function_handles["field_setter(_,_)"]);
+ break;
+ }
+ }
+}
+
+std::map wren::parse_fields(std::istringstream &ss) {
+ std::map vars{};
+ bool anglehack{false};
+
+
+ while (true) {
+ auto token = common::parse_token(ss);
+ if (token.has_value() && token->front() == '}') {
+ break;
+ }
+ if (ss.eof()) {
+ Host_Error("wren::parse_fields: EOF without closing brace");
+ }
+
+ // anglehack is to allow QuakeEd to write single scalar angles
+ // and allow them to be turned into vectors. (FIXME...)
+ if (token == "angle") {
+ token = "angles";
+ anglehack = true;
+ } else {
+ anglehack = false;
+ }
+
+ // FIXME: change light to _light to get rid of this hack
+ if (token == "light") {
+ token = "light_maplev";
+ }
+
+ auto keyname = *token;
+
+ // another hack to fix keynames with trailing spaces
+ while (!keyname.empty() && keyname.back() == ' ') {
+ keyname.pop_back();
+ }
+
+ // parse value
+ // HACK: we allow truncation when reading the wad field,
+ // otherwise maps using lots of wads with absolute paths
+ // could cause a parse error
+ token = common::parse_token(ss);
+ if (ss.eof()) {
+ Host_Error("wren::parse_fields: EOF without closing brace");
+ }
+
+ if (!token->empty() && token->front() == '}') {
+ Host_Error("wren::parse_fields: closing brace without data");
+ }
+
+
+ // keynames with a leading underscore are used for utility comments,
+ // and are immediately discarded by quake
+ if (keyname[0] == '_') {
+ continue;
+ }
+
+ if (anglehack) {
+ token = std::format("0 {} 0", *token);
+ }
+
+ vars[keyname] = *token;
+ }
+
+ return vars;
+}
diff --git a/Quake/pr_wren.hpp b/Quake/pr_wren.hpp
new file mode 100644
index 0000000..752cd75
--- /dev/null
+++ b/Quake/pr_wren.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include