Fix blank animqueues, and add facing=math.auto to animate

This commit is contained in:
2024-03-12 11:46:48 -04:00
parent 044bb89edf
commit 357e3766fb
9 changed files with 147 additions and 42 deletions

View File

@@ -10,6 +10,12 @@
#include <cstdlib> #include <cstdlib>
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) { static uint64_t hash_encstep(uint64_t prev, std::string_view s) {
return util::hash_string(util::HashValue(123, prev), s).first; 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; if (!LS.isnumber(tmp)) return result;
xyz.z = LS.cknumber(tmp); xyz.z = LS.cknumber(tmp);
result.set_dxyz(xyz); result.set_dxyz(xyz);
} else if (LS.rawequal(val, LuaToken("auto"))) {
result.set_auto();
} else {
result.set_uninitialized();
} }
return result; return result;
} }
@@ -111,6 +121,8 @@ void AnimState::print_debug_string(eng::ostringstream &oss) {
oss << ":"; oss << ":";
} }
switch (value.type) { switch (value.type) {
case SimpleDynamicTag::UNINITIALIZED: oss << "UNINITIALIZED"; break;
case SimpleDynamicTag::AUTO: oss << "AUTO"; break;
case SimpleDynamicTag::NUMBER: oss << value.x; break; case SimpleDynamicTag::NUMBER: oss << value.x; break;
case SimpleDynamicTag::BOOLEAN: oss << ((value.x == 1.0) ? "true":"false"); break; case SimpleDynamicTag::BOOLEAN: oss << ((value.x == 1.0) ? "true":"false"); break;
case SimpleDynamicTag::VECTOR: oss << value.x << "," << value.y << "," << value.z; 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); err = add_default("facing", defval, other);
if (!err.empty()) return err; if (!err.empty()) return err;
defval.set_string("stdbp"); defval.set_string("unknown");
err = add_default("bp", defval, other); err = add_default("bp", defval, other);
if (!err.empty()) return err; if (!err.empty()) return err;
defval.set_string("stdmodel");
err = add_default("model", defval, other);
if (!err.empty()) return err;
return ""; 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; LuaVar key, val, tmp;
LuaExtStack LS(LS0.state(), key, val, tmp); LuaExtStack LS(LS0.state(), key, val, tmp);
util::DXYZ xyz; util::DXYZ xyz;
clear();
if (!LS.istable(tab)) { if (!LS.istable(tab)) {
return "An animstate must be a table."; return "A lua animstate must be a table.";
} }
LS.set(key, LuaNil); LS.set(key, LuaNil);
while (LS.next(tab, key, val)) { while (LS.next(tab, key, val)) {
if (!LS.isstring(key)) { if (!LS.isstring(key)) {
return "in animation key-value pairs, key must be a string."; 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); AnimValue parsedvalue = parse_anim_value(LS, val, tmp);
if (parsedvalue.type == SimpleDynamicTag::UNINITIALIZED) { 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]; 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); mapentry.copy_value(parsedvalue);
if (setpersist) mapentry.persistent = true; mapentry.persistent = persistent;
} }
return ""; 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) { void AnimState::to_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent) {
LuaVar name, val; LuaVar name, val;
LuaExtStack LS(LS0.state(), 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 // 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. // possible strings. That's OK, though, since it's just for unit testing.
void AnimState::parse(std::string_view config) { void AnimState::parse(std::string_view config) {

View File

@@ -185,12 +185,35 @@ public:
// //
eng::string add_defaults(const AnimState *other); 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 // Table keys must be valid lua identifiers. Table values may be string,
// must match the type from the existing state. // 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. // Convert an animstate to a lua table.
// //
@@ -294,6 +317,17 @@ public:
// //
util::SharedStdString get_encoded_queue() const { return encqueue_; } 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: private:
struct QueueRange { struct QueueRange {
int size; int size;
@@ -343,6 +377,10 @@ private:
// can't have the graphics engine affecting the behavior of the engine heap. // can't have the graphics engine affecting the behavior of the engine heap.
// //
util::SharedStdString encqueue_; util::SharedStdString encqueue_;
// The blank animation queue.
//
static util::SharedStdString blankqueue_;
}; };
#endif // ANIMQUEUE_HPP #endif // ANIMQUEUE_HPP

View File

@@ -426,9 +426,8 @@ void DrivenEngine::drv_get_animation_queues(uint32_t count, const int64_t *ids,
anim_queues_.resize(count); anim_queues_.resize(count);
if (visible_world_ == nullptr) { if (visible_world_ == nullptr) {
util::SharedStdString empty = std::make_shared<std::string>("");
for (int i = 0; i < int(count); i++) { for (int i = 0; i < int(count); i++) {
anim_queues_[i] = empty; anim_queues_[i] = AnimQueue::get_encoded_blank_queue();
} }
} else { } else {
visible_world_->get_encoded_animation_queues(count, ids, anim_queues_); visible_world_->get_encoded_animation_queues(count, ids, anim_queues_);

View File

@@ -572,21 +572,18 @@ eng::string LuaKeywordParser::final_check() {
if (not_table_) { if (not_table_) {
return "expected a keyword table"; return "expected a keyword table";
} }
if (lua_nkeys(L_, slot_) != int(parsed_.size())) { lua_pushnil(L_);
lua_pushnil(L_); while (lua_next(L_, slot_) != 0) {
while (lua_next(L_, slot_) != 0) { lua_pop(L_, 1); // Don't need the value.
lua_pop(L_, 1); // Don't need the value. if (!lua_isstring(L_, -1)) {
if (!lua_isstring(L_, -1)) { return "keyword table contains non-string key";
return "keyword table contains non-string key"; }
} const char *kw = lua_tostring(L_, -1);
const char *kw = lua_tostring(L_, -1); if (parsed_.find(kw) == parsed_.end()) {
if (parsed_.find(kw) == parsed_.end()) { eng::ostringstream oss;
eng::ostringstream oss; oss << "keyword " << kw << " not known";
oss << "keyword " << kw << " not known"; return oss.str();
return oss.str();
}
} }
assert(false && "should never get here in check_unparsed_keywords");
} }
return ""; return "";
} }

View File

@@ -765,6 +765,7 @@ LuaNumberConstant(math_pi, M_PI, "");
LuaNumberConstant(math_huge, HUGE_VAL, ""); LuaNumberConstant(math_huge, HUGE_VAL, "");
LuaNumberConstant(math_nan, NAN, ""); LuaNumberConstant(math_nan, NAN, "");
LuaNumberConstant(math_maxint, LuaCoreStack::MAXINT, ""); 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 // math.random and math.randomseed are in world-accessor.cpp, because
// generating random numbers must manipulate global state which is // generating random numbers must manipulate global state which is

View File

@@ -130,7 +130,7 @@ LuaDefine(tangible_animinit, "tan,config",
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false); Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state; AnimState state;
eng::string error = state.apply_lua(LS, config, true); eng::string error = state.from_lua(LS, config, true, false);
if (!error.empty()) { if (!error.empty()) {
luaL_error(L, "%s", error.c_str()); luaL_error(L, "%s", error.c_str());
} }
@@ -185,18 +185,25 @@ LuaDefine(tangible_animate, "tan,options,config",
if (kp.parse(option, "replace")) { if (kp.parse(option, "replace")) {
replace = LS.ckboolean(option); replace = LS.ckboolean(option);
} }
kp.final_check_throw();
} }
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false); Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state = tan->anim_queue_.get_final_persistent(); AnimState previous = tan->anim_queue_.get_final_persistent();
eng::string error = state.apply_lua(LS, steptab, false); 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()) { if (!error.empty()) {
luaL_error(L, "%s", error.c_str()); luaL_error(L, "%s", error.c_str());
} }
if (replace) { if (replace) {
tan->anim_queue_.replace(state); tan->anim_queue_.replace(merge);
} else { } else {
tan->anim_queue_.add(state); tan->anim_queue_.add(merge);
} }
tan->update_plane_item(); tan->update_plane_item();
return LS.result(); return LS.result();
@@ -289,7 +296,7 @@ LuaDefine(tangible_build, "config",
// Calculate the initial animation state. // Calculate the initial animation state.
AnimState state; AnimState state;
err = state.apply_lua(LS, animstate, true); err = state.from_lua(LS, animstate, true, false);
if (err != "") { if (err != "") {
luaL_error(L, "%s", err.c_str()); luaL_error(L, "%s", err.c_str());
} }

View File

@@ -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) { void World::get_encoded_animation_queues(uint32_t count, const int64_t *ids, util::SharedStdStringVec &into) {
util::SharedStdString empty = std::make_shared<std::string>("");
into.resize(count); into.resize(count);
for (int i = 0; i < int(count); i++) { for (int i = 0; i < int(count); i++) {
Tangible *tan = tangible_get(ids[i]); Tangible *tan = tangible_get(ids[i]);
if (tan == nullptr) { if (tan == nullptr) {
into[i] = empty; into[i] = AnimQueue::get_encoded_blank_queue();
} else { } else {
into[i] = tan->anim_queue_.get_encoded_queue(); into[i] = tan->anim_queue_.get_encoded_queue();
} }
@@ -1191,6 +1190,7 @@ void World::rollback() {
// //
void engine_initialization() { void engine_initialization() {
SourceDB::register_lua_builtins(); SourceDB::register_lua_builtins();
AnimQueue::initialize_module();
} }
static DrivenEngineInitializerReg eireg(engine_initialization); static DrivenEngineInitializerReg eireg(engine_initialization);

View File

@@ -33,6 +33,7 @@
enum class SimpleDynamicTag { enum class SimpleDynamicTag {
UNINITIALIZED, UNINITIALIZED,
AUTO,
STRING, STRING,
NUMBER, NUMBER,
BOOLEAN, BOOLEAN,
@@ -70,6 +71,10 @@ struct SimpleDynamic {
type=SimpleDynamicTag::UNINITIALIZED; s.clear(); x=y=z=0; 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) { void set_string(std::string_view is) {
type=SimpleDynamicTag::STRING; s=is; x=y=z=0; type=SimpleDynamicTag::STRING; s=is; x=y=z=0;
} }

View File

@@ -4,7 +4,7 @@ function login.initialize(actor, place)
dprint("login.initialize:", actor) dprint("login.initialize:", actor)
local x = math.random(1, 100) local x = math.random(1, 100)
local y = 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 end
function login.interface(actor, place) function login.interface(actor, place)