#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; }