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>
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) {

View File

@@ -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.
//
eng::string apply_lua(LuaCoreStack &LS0, LuaSlot tab, bool setpersist);
// 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.
//
// 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

View File

@@ -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<std::string>("");
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_);

View File

@@ -572,7 +572,6 @@ 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.
@@ -586,8 +585,6 @@ eng::string LuaKeywordParser::final_check() {
return oss.str();
}
}
assert(false && "should never get here in check_unparsed_keywords");
}
return "";
}

View File

@@ -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

View File

@@ -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());
}

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) {
util::SharedStdString empty = std::make_shared<std::string>("");
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);

View File

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

View File

@@ -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)