From 357e3766fbad52a6ad07af3194ea23d24c101e6f Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 12 Mar 2024 11:46:48 -0400 Subject: [PATCH] Fix blank animqueues, and add facing=math.auto to animate --- luprex/cpp/core/animqueue.cpp | 84 +++++++++++++++++++++++++----- luprex/cpp/core/animqueue.hpp | 46 ++++++++++++++-- luprex/cpp/core/drivenengine.cpp | 3 +- luprex/cpp/core/luastack.cpp | 25 ++++----- luprex/cpp/core/source.cpp | 1 + luprex/cpp/core/world-accessor.cpp | 19 ++++--- luprex/cpp/core/world-core.cpp | 4 +- luprex/ext/base-buffer.hpp | 5 ++ luprex/lua/login.lua | 2 +- 9 files changed, 147 insertions(+), 42 deletions(-) diff --git a/luprex/cpp/core/animqueue.cpp b/luprex/cpp/core/animqueue.cpp index 45162d7c..ec64519f 100644 --- a/luprex/cpp/core/animqueue.cpp +++ b/luprex/cpp/core/animqueue.cpp @@ -10,6 +10,12 @@ #include +util::SharedStdString AnimQueue::blankqueue_; + +void AnimQueue::initialize_module() { + AnimQueue queue; + blankqueue_ = queue.get_encoded_queue(); +} static uint64_t hash_encstep(uint64_t prev, std::string_view s) { return util::hash_string(util::HashValue(123, prev), s).first; @@ -67,6 +73,10 @@ static AnimValue parse_anim_value(LuaCoreStack &LS, LuaSlot val, LuaSlot tmp) { if (!LS.isnumber(tmp)) return result; xyz.z = LS.cknumber(tmp); result.set_dxyz(xyz); + } else if (LS.rawequal(val, LuaToken("auto"))) { + result.set_auto(); + } else { + result.set_uninitialized(); } return result; } @@ -111,6 +121,8 @@ void AnimState::print_debug_string(eng::ostringstream &oss) { oss << ":"; } switch (value.type) { + case SimpleDynamicTag::UNINITIALIZED: oss << "UNINITIALIZED"; break; + case SimpleDynamicTag::AUTO: oss << "AUTO"; break; case SimpleDynamicTag::NUMBER: oss << value.x; break; case SimpleDynamicTag::BOOLEAN: oss << ((value.x == 1.0) ? "true":"false"); break; case SimpleDynamicTag::VECTOR: oss << value.x << "," << value.y << "," << value.z; break; @@ -205,46 +217,91 @@ eng::string AnimState::add_defaults(const AnimState *other) { err = add_default("facing", defval, other); if (!err.empty()) return err; - defval.set_string("stdbp"); + defval.set_string("unknown"); err = add_default("bp", defval, other); if (!err.empty()) return err; - defval.set_string("stdmodel"); - err = add_default("model", defval, other); - if (!err.empty()) return err; - return ""; } -eng::string AnimState::apply_lua(LuaCoreStack &LS0, LuaSlot tab, bool setpersist) { +eng::string AnimState::from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto) { LuaVar key, val, tmp; LuaExtStack LS(LS0.state(), key, val, tmp); util::DXYZ xyz; + clear(); if (!LS.istable(tab)) { - return "An animstate must be a table."; + return "A lua animstate must be a table."; } LS.set(key, LuaNil); while (LS.next(tab, key, val)) { if (!LS.isstring(key)) { return "in animation key-value pairs, key must be a string."; } + eng::string name = LS.ckstring(key); + if (!LS.valididentifier(name)) { + return "in animation key-value pairs, key must be a valid lua identifier."; + } AnimValue parsedvalue = parse_anim_value(LS, val, tmp); if (parsedvalue.type == SimpleDynamicTag::UNINITIALIZED) { - return "in animation key-value pairs, val must be number, string, boolean, or xyz"; + return "in animation key-value pairs, value must be number, string, boolean, or xyz"; + } + if ((parsedvalue.type == SimpleDynamicTag::AUTO) && !allowauto) { + return "in animation key-value pairs, value must not be AUTO here."; } - eng::string name = LS.ckstring(key); AnimValue &mapentry = map_[name]; - if ((mapentry.type != SimpleDynamicTag::UNINITIALIZED) && (mapentry.type != parsedvalue.type)) { - return util::ss("animation '", name, "' must be a ", mapentry.type_name()); - } mapentry.copy_value(parsedvalue); - if (setpersist) mapentry.persistent = true; + mapentry.persistent = persistent; } return ""; } +eng::string AnimState::merge(const AnimState &previous, const AnimState &update) { + // Copy everything over from the previous entry. + map_ = previous.map_; + + for (const auto &pair : update.map_) { + const eng::string &name = pair.first; + AnimValue &dst = map_[name]; + const AnimValue &src = pair.second; + + // Handle autocalculation rules. + if (src.type == SimpleDynamicTag::AUTO) { + if (name == "facing") { + if (!dst.persistent || dst.type != SimpleDynamicTag::NUMBER) { + return "Cannot auto-calculate facing because facing has not been specified as a persistent number"; + } + const auto xyz_previous = previous.map_.find("xyz"); + const auto xyz_update = update.map_.find("xyz"); + if ((xyz_previous == previous.map_.end()) || + (xyz_update == update.map_.end()) || + (xyz_previous->second.type != SimpleDynamicTag::VECTOR) || + (xyz_update->second.type != SimpleDynamicTag::VECTOR)) { + return "Cannot auto-calculate facing because before/after xyz coordinates are not present"; + } + double dx = xyz_update->second.x - xyz_previous->second.x; + double dy = xyz_update->second.y - xyz_previous->second.y; + // If dx and dy are both zero, leave the facing unmodified. + if ((dx != 0.0) || (dy != 0.0)) { + double facing = atan2(dy, dx) * 180.0 / M_PI; + dst.set_number(facing); + } + } else { + return util::ss("No rule to automatically calculate ", name); + } + continue; + } + + if (dst.persistent && (src.type != dst.type)) { + return util::ss("Wrong data type for ", name, ", should be ", dst.type_name()); + } + dst.copy_value(src); + } + return ""; +} + + void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent) { LuaVar name, val; LuaExtStack LS(LS0.state(), name, val); @@ -269,6 +326,7 @@ void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent) { } } + // The syntax used by this parser is not general enough to represent all // possible strings. That's OK, though, since it's just for unit testing. void AnimState::parse(std::string_view config) { diff --git a/luprex/cpp/core/animqueue.hpp b/luprex/cpp/core/animqueue.hpp index f4cbfd27..4cec99e8 100644 --- a/luprex/cpp/core/animqueue.hpp +++ b/luprex/cpp/core/animqueue.hpp @@ -185,12 +185,35 @@ public: // eng::string add_defaults(const AnimState *other); - // Apply a configuration from a lua table. + // Parse an animstate from a Lua Table. // - // If a key already exists in the state, then type type from the table - // must match the type from the existing state. + // Table keys must be valid lua identifiers. Table values may be string, + // floats, bool, or coordinates. Persistent flags are all set the same, + // from the persistent parameter. Returns empty string on success, or an + // error message on failure. + // + // If 'allowauto' is true, then the lua table may contain a key-value + // pair of the form (key, math.auto). These keys will be stored in the + // AnimState map with mapentry.type == SimpleDynamicTag::AUTO. + // This is done to express an intent that the value should be + // automatically computer later. + // + eng::string from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto); + + // Generate a merged animstate using a previous state and an update. // - eng::string apply_lua(LuaCoreStack &LS0, LuaSlot tab, bool setpersist); + // Keys from both previous and update are combined to create this AnimState. + // Values from 'update' override values from 'previous'. Persistent flags + // are taken from 'previous'. If a key exists in both previous and update, + // and the key is persistent in 'previous', then the types must match, + // otherwise an error is generated. Returns empty string on success or + // an error message on failure. + // + // If a key in the 'update' map has type AUTO, then we will attempt to find + // a rule to compute that value automatically. Failure to find a rule + // results in an error. + // + eng::string merge(const AnimState &previous, const AnimState &update); // Convert an animstate to a lua table. // @@ -294,6 +317,17 @@ public: // util::SharedStdString get_encoded_queue() const { return encqueue_; } + // Get a serialized representation of a blank queue. + // + // Since an animqueue must have at least one step, the blank queue + // contains a single default step. + // + static util::SharedStdString get_encoded_blank_queue() { return blankqueue_; } + + // Initialize the animqueue module. + // + static void initialize_module(); + private: struct QueueRange { int size; @@ -343,6 +377,10 @@ private: // can't have the graphics engine affecting the behavior of the engine heap. // util::SharedStdString encqueue_; + + // The blank animation queue. + // + static util::SharedStdString blankqueue_; }; #endif // ANIMQUEUE_HPP diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp index eddaa93c..cdc7c3bd 100644 --- a/luprex/cpp/core/drivenengine.cpp +++ b/luprex/cpp/core/drivenengine.cpp @@ -426,9 +426,8 @@ void DrivenEngine::drv_get_animation_queues(uint32_t count, const int64_t *ids, anim_queues_.resize(count); if (visible_world_ == nullptr) { - util::SharedStdString empty = std::make_shared(""); for (int i = 0; i < int(count); i++) { - anim_queues_[i] = empty; + anim_queues_[i] = AnimQueue::get_encoded_blank_queue(); } } else { visible_world_->get_encoded_animation_queues(count, ids, anim_queues_); diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index a0142a0d..05eb7f2f 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -572,21 +572,18 @@ eng::string LuaKeywordParser::final_check() { if (not_table_) { return "expected a keyword table"; } - if (lua_nkeys(L_, slot_) != int(parsed_.size())) { - lua_pushnil(L_); - while (lua_next(L_, slot_) != 0) { - lua_pop(L_, 1); // Don't need the value. - if (!lua_isstring(L_, -1)) { - return "keyword table contains non-string key"; - } - const char *kw = lua_tostring(L_, -1); - if (parsed_.find(kw) == parsed_.end()) { - eng::ostringstream oss; - oss << "keyword " << kw << " not known"; - return oss.str(); - } + lua_pushnil(L_); + while (lua_next(L_, slot_) != 0) { + lua_pop(L_, 1); // Don't need the value. + if (!lua_isstring(L_, -1)) { + return "keyword table contains non-string key"; + } + const char *kw = lua_tostring(L_, -1); + if (parsed_.find(kw) == parsed_.end()) { + eng::ostringstream oss; + oss << "keyword " << kw << " not known"; + return oss.str(); } - assert(false && "should never get here in check_unparsed_keywords"); } return ""; } diff --git a/luprex/cpp/core/source.cpp b/luprex/cpp/core/source.cpp index 16b6a45d..1586aaab 100644 --- a/luprex/cpp/core/source.cpp +++ b/luprex/cpp/core/source.cpp @@ -765,6 +765,7 @@ LuaNumberConstant(math_pi, M_PI, ""); LuaNumberConstant(math_huge, HUGE_VAL, ""); LuaNumberConstant(math_nan, NAN, ""); LuaNumberConstant(math_maxint, LuaCoreStack::MAXINT, ""); +LuaTokenConstant(math_auto, "auto", "A value used to request that a value should be automatically computed"); // math.random and math.randomseed are in world-accessor.cpp, because // generating random numbers must manipulate global state which is diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index f4aa9703..5f932dc0 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -130,7 +130,7 @@ LuaDefine(tangible_animinit, "tan,config", World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); AnimState state; - eng::string error = state.apply_lua(LS, config, true); + eng::string error = state.from_lua(LS, config, true, false); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } @@ -185,18 +185,25 @@ LuaDefine(tangible_animate, "tan,options,config", if (kp.parse(option, "replace")) { replace = LS.ckboolean(option); } + kp.final_check_throw(); } World *w = World::fetch_global_pointer(L); Tangible *tan = w->tangible_get(LS, tanobj, false); - AnimState state = tan->anim_queue_.get_final_persistent(); - eng::string error = state.apply_lua(LS, steptab, false); + AnimState previous = tan->anim_queue_.get_final_persistent(); + AnimState update; + eng::string error = update.from_lua(LS, steptab, false, true); + if (!error.empty()) { + luaL_error(L, "%s", error.c_str()); + } + AnimState merge; + error = merge.merge(previous, update); if (!error.empty()) { luaL_error(L, "%s", error.c_str()); } if (replace) { - tan->anim_queue_.replace(state); + tan->anim_queue_.replace(merge); } else { - tan->anim_queue_.add(state); + tan->anim_queue_.add(merge); } tan->update_plane_item(); return LS.result(); @@ -289,7 +296,7 @@ LuaDefine(tangible_build, "config", // Calculate the initial animation state. AnimState state; - err = state.apply_lua(LS, animstate, true); + err = state.from_lua(LS, animstate, true, false); if (err != "") { luaL_error(L, "%s", err.c_str()); } diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 05d78de4..62554e91 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -234,12 +234,11 @@ void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool } void World::get_encoded_animation_queues(uint32_t count, const int64_t *ids, util::SharedStdStringVec &into) { - util::SharedStdString empty = std::make_shared(""); into.resize(count); for (int i = 0; i < int(count); i++) { Tangible *tan = tangible_get(ids[i]); if (tan == nullptr) { - into[i] = empty; + into[i] = AnimQueue::get_encoded_blank_queue(); } else { into[i] = tan->anim_queue_.get_encoded_queue(); } @@ -1191,6 +1190,7 @@ void World::rollback() { // void engine_initialization() { SourceDB::register_lua_builtins(); + AnimQueue::initialize_module(); } static DrivenEngineInitializerReg eireg(engine_initialization); diff --git a/luprex/ext/base-buffer.hpp b/luprex/ext/base-buffer.hpp index b31b3991..38cf76b4 100644 --- a/luprex/ext/base-buffer.hpp +++ b/luprex/ext/base-buffer.hpp @@ -33,6 +33,7 @@ enum class SimpleDynamicTag { UNINITIALIZED, + AUTO, STRING, NUMBER, BOOLEAN, @@ -70,6 +71,10 @@ struct SimpleDynamic { type=SimpleDynamicTag::UNINITIALIZED; s.clear(); x=y=z=0; } + void set_auto() { + type=SimpleDynamicTag::AUTO; s.clear(); x=y=z=0; + } + void set_string(std::string_view is) { type=SimpleDynamicTag::STRING; s=is; x=y=z=0; } diff --git a/luprex/lua/login.lua b/luprex/lua/login.lua index fdce20ea..cc7fdda4 100644 --- a/luprex/lua/login.lua +++ b/luprex/lua/login.lua @@ -4,7 +4,7 @@ function login.initialize(actor, place) dprint("login.initialize:", actor) local x = math.random(1, 100) local y = math.random(1, 100) - tangible.animate(actor, nil, {action="warpto", plane="earth", xyz={x, y, 90}}) + tangible.animate(actor, nil, {bp="tangiblecharacter", action="warpto", plane="earth", xyz={x, y, 90}}) end function login.interface(actor, place)