#include #include "luastack.hpp" #include "animqueue.hpp" AnimStep::AnimStep() { clear(); } AnimStep::~AnimStep() {} void AnimStep::clear() { id_ = 0; bits_ = 0; action_ = ""; facing_ = 0; xyz_ = util::XYZ(0,0,0); graphic_ = ""; plane_ = ""; } void AnimStep::set_action(const std::string &act) { action_ = act; } void AnimStep::set_facing(float f) { bits_ |= HAS_FACING; facing_ = f; } void AnimStep::set_x(float f) { bits_ |= HAS_X; xyz_.x = f; } void AnimStep::set_y(float f) { bits_ |= HAS_Y; xyz_.y = f; } void AnimStep::set_z(float f) { bits_ |= HAS_Z; xyz_.z = f; } void AnimStep::set_xyz(const util::XYZ &xyz) { bits_ |= (HAS_X | HAS_Y | HAS_Z); xyz_ = xyz; } void AnimStep::set_graphic(const std::string &g) { bits_ |= HAS_GRAPHIC; graphic_ = g; } void AnimStep::set_plane(const std::string &p) { bits_ |= HAS_PLANE; plane_ = p; } AnimQueue::AnimQueue() { size_limit_ = 10; // Default size limit. clear_steps(); } void AnimStep::from_lua_store_string(lua_State *L, int idx, std::string *target, int16_t bits, const char *name) { if ((bits_ & bits)||(*target != "")) { luaL_error(L, "specified %s twice", name); } if (lua_type(L, idx) != LUA_TSTRING) { luaL_error(L, "Expected %s to be a string", name); } *target = lua_tostring(L, idx); bits_ |= bits; } void AnimStep::from_lua_store_number(lua_State *L, int idx, float *target, float offset, int16_t bits, const char *name) { if (bits_ & bits) { luaL_error(L, "specified %s twice", name); } if (lua_type(L, idx) != LUA_TNUMBER) { luaL_error(L, "Expected %s to be a number", name); } *target = lua_tonumber(L, idx) + offset; bits_ |= bits; } void AnimStep::from_lua(lua_State *L, int idx, const AnimStep &qback) { LuaSpecial tab(idx); LuaVar key, value; LuaStack LS(L, key, value); if (!LS.istable(tab)) { luaL_error(L, "animation spec must be a table"); } LS.set(key, LuaNil); while (LS.next(tab, key, value)) { if (!LS.isstring(key)) { luaL_error(L, "animation specs must be key/value where key is a string"); } std::string skey = LS.ckstring(key); if (skey == "action") { from_lua_store_string(L, value.index(), &action_, 0, "action"); } else if (skey == "graphic") { from_lua_store_string(L, value.index(), &graphic_, HAS_GRAPHIC, "graphic"); } else if (skey == "plane") { from_lua_store_string(L, value.index(), &plane_, HAS_PLANE, "plane"); } else if (skey == "x") { from_lua_store_number(L, value.index(), &xyz_.x, 0.0, HAS_X, "X coordinate"); } else if (skey == "y") { from_lua_store_number(L, value.index(), &xyz_.y, 0.0, HAS_Y, "Z coordinate"); } else if (skey == "z") { from_lua_store_number(L, value.index(), &xyz_.z, 0.0, HAS_Z, "Z coordinate"); } else if (skey == "dx") { from_lua_store_number(L, value.index(), &xyz_.x, qback.xyz().x, HAS_X, "X coordinate"); } else if (skey == "dy") { from_lua_store_number(L, value.index(), &xyz_.y, qback.xyz().y, HAS_Y, "Y coordinate"); } else if (skey == "dz") { from_lua_store_number(L, value.index(), &xyz_.z, qback.xyz().z, HAS_Z, "Z coordinate"); } else if (skey == "facing") { from_lua_store_number(L, value.index(), &facing_, 0.0, HAS_FACING, "facing"); } else { luaL_error(L, "Unrecognized animation spec: %s", skey.c_str()); } } } void AnimStep::echo(const AnimStep &prev) { if (!has_facing()) { facing_ = prev.facing_; } if (!has_x()) { xyz_.x = prev.xyz_.x; } if (!has_y()) { xyz_.y = prev.xyz_.y; } if (!has_z()) { xyz_.z = prev.xyz_.z; } if (!has_graphic()) { graphic_ = prev.graphic_; } if (!has_plane()) { plane_ = prev.plane_; } } bool AnimStep::echoes(const AnimStep &prev) const { if (!has_facing() && (facing_ != prev.facing_)) { return false; } if (!has_x() && (xyz_.x != prev.xyz_.x)) { return false; } if (!has_y() && (xyz_.y != prev.xyz_.y)) { return false; } if (!has_z() && (xyz_.z != prev.xyz_.z)) { return false; } if (!has_graphic() && (graphic_ != prev.graphic_)) { return false; } if (!has_plane() && (plane_ != prev.plane_)) { return false; } return true; } void AnimQueue::clear_steps() { steps_.clear(); steps_.emplace_back(); AnimStep &init = steps_.back(); init.bits_ = AnimStep::HAS_EVERYTHING; init.action_ = ""; init.graphic_ = ""; init.plane_ = ""; } void AnimQueue::set_size_limit(int n) { assert(n >= 2); size_limit_ = n; } void AnimQueue::keep_only(int n) { if (n < 1) n = 1; while (int(steps_.size()) > n) { steps_.pop_front(); } AnimStep &init = steps_.front(); init.id_ = 0; init.action_ = ""; init.bits_ = AnimStep::HAS_EVERYTHING; } void AnimQueue::add(int64_t id, const AnimStep &step) { AnimStep copy = step; copy.echo(steps_.back()); copy.id_ = id; steps_.push_back(copy); keep_only(size_limit_); } bool AnimQueue::valid() { // Animqueue must have at least one step. if (steps_.empty()) { return false; } // First action should be blank. if (steps_[0].action_ != "") { return false; } // First step should have all bits. if (steps_[0].bits_ != AnimStep::HAS_EVERYTHING) { return false; } // Any unset bit should correspond to a value copied from the previous step. for (size_t i = 1; i < steps_.size(); i++) { const AnimStep &prev = steps_[i - 1]; const AnimStep &curr = steps_[i]; if (!curr.echoes(prev)) return false; } return true; } void AnimQueue::serialize(StreamBuffer *sb) { assert(valid()); // can't serialize an invalid animqueue. sb->write_int32(size_limit_); sb->write_size(steps_.size()); for (const AnimStep &step : steps_) { sb->write_int64(step.id_); sb->write_int16(step.bits_); sb->write_string(step.action_); if (step.has_facing()) { sb->write_float(step.facing_); } if (step.has_x()) { sb->write_float(step.xyz_.x); } if (step.has_y()) { sb->write_float(step.xyz_.y); } if (step.has_z()) { sb->write_float(step.xyz_.z); } if (step.has_graphic()) { sb->write_string(step.graphic_); } if (step.has_plane()) { sb->write_string(step.plane_); } } } void AnimQueue::deserialize(StreamBuffer *sb) { size_limit_ = sb->read_int32(); size_t nsteps = sb->read_size(); steps_.resize(nsteps); for (size_t i = 0; i < nsteps; i++) { AnimStep &step = steps_[i]; if (i > 0) step = steps_[i - 1]; step.id_ = sb->read_int64(); step.bits_ = sb->read_int16(); step.action_ = sb->read_string(); if (step.has_facing()) { step.facing_ = sb->read_float(); } if (step.has_x()) { step.xyz_.x = sb->read_float(); } if (step.has_y()) { step.xyz_.y = sb->read_float(); } if (step.has_z()) { step.xyz_.z = sb->read_float(); } if (step.has_graphic()) { step.graphic_ = sb->read_string(); } if (step.has_plane()) { step.plane_ = sb->read_string(); } } } const AnimStep &AnimQueue::back() const { return steps_.back(); } LuaDefine(unittests_animqueue, "c") { // Check initial state. AnimStep stp; AnimQueue aq; StreamBuffer sb; AnimQueue aqds; aq.set_size_limit(3); LuaAssert(L, aq.valid()); LuaAssert(L, aq.size() == 1); const AnimStep *st = &aq.nth(0); LuaAssert(L, st->id() == 0); LuaAssert(L, st->action() == ""); LuaAssert(L, st->bits() == AnimStep::HAS_EVERYTHING); LuaAssert(L, st->facing() == 0.0); LuaAssert(L, st->xyz() == util::XYZ(0,0,0)); LuaAssert(L, st->graphic() == ""); LuaAssert(L, st->plane() == ""); // Test the step setters. stp.clear(); stp.set_facing(180); LuaAssert(L, stp.facing() == 180); LuaAssert(L, stp.bits() == AnimStep::HAS_FACING); stp.set_x(3); LuaAssert(L, stp.xyz() == util::XYZ(3, 0, 0)); LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X)); stp.set_y(4); LuaAssert(L, stp.xyz() == util::XYZ(3, 4, 0)); LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X | AnimStep::HAS_Y)); stp.set_z(5); LuaAssert(L, stp.xyz() == util::XYZ(3, 4, 5)); LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_X | AnimStep::HAS_Y | AnimStep::HAS_Z)); stp.set_plane("somewhere"); LuaAssert(L, stp.plane() == "somewhere"); LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE)); stp.set_graphic("something"); LuaAssert(L, stp.graphic() == "something"); LuaAssert(L, stp.bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE | AnimStep::HAS_GRAPHIC)); // Add a step. stp.clear(); stp.set_action("walk"); aq.add(12345, stp); LuaAssert(L, aq.valid()); LuaAssert(L, aq.size() == 2); st = &aq.nth(1); LuaAssert(L, st->id() == 12345); LuaAssert(L, st->action() == "walk"); LuaAssert(L, st->bits() == 0); LuaAssert(L, st->facing() == 0.0); LuaAssert(L, st->xyz() == util::XYZ(0,0,0)); LuaAssert(L, st->graphic() == ""); LuaAssert(L, st->plane() == ""); // Exceed the length limit, dropping first element. stp.clear(); stp.set_action("walk"); aq.add(12346, stp); aq.add(12347, stp); LuaAssert(L, aq.size() == 3); LuaAssert(L, aq.nth(0).id() == 0); LuaAssert(L, aq.nth(0).action() == ""); LuaAssert(L, aq.nth(0).bits() == AnimStep::HAS_EVERYTHING); LuaAssert(L, aq.nth(1).id() == 12346); LuaAssert(L, aq.nth(2).id() == 12347); LuaAssert(L, aq.valid()); // Test serialization and deserialization. aq.set_size_limit(5); aq.clear_steps(); stp.clear(); stp.set_action("walk"); stp.set_xyz(util::XYZ(3,4,5)); aq.add(12345, stp); stp.clear(); stp.set_action("setgraphic"); stp.set_graphic("banana"); aq.add(12346, stp); stp.clear(); stp.set_action("setfacing"); stp.set_facing(301.0); aq.add(12347, stp); aq.serialize(&sb); aqds.deserialize(&sb); LuaAssert(L, aqds.size_limit() == 5); LuaAssert(L, aqds.size() == 4); LuaAssert(L, aqds.nth(0).id() == 0); LuaAssert(L, aqds.nth(0).bits() == AnimStep::HAS_EVERYTHING); LuaAssert(L, aqds.nth(0).action() == ""); LuaAssert(L, aqds.nth(0).facing() == 0.0); LuaAssert(L, aqds.nth(0).xyz() == util::XYZ(0,0,0)); LuaAssert(L, aqds.nth(0).graphic() == ""); LuaAssert(L, aqds.nth(0).plane() == ""); LuaAssert(L, aqds.nth(1).id() == 12345); LuaAssert(L, aqds.nth(1).bits() == AnimStep::HAS_XYZ); LuaAssert(L, aqds.nth(1).action() == "walk"); LuaAssert(L, aqds.nth(1).facing() == 0.0); LuaAssert(L, aqds.nth(1).xyz() == util::XYZ(3,4,5)); LuaAssert(L, aqds.nth(1).graphic() == ""); LuaAssert(L, aqds.nth(1).plane() == ""); LuaAssert(L, aqds.nth(2).id() == 12346); LuaAssert(L, aqds.nth(2).bits() == AnimStep::HAS_GRAPHIC); LuaAssert(L, aqds.nth(2).action() == "setgraphic"); LuaAssert(L, aqds.nth(2).facing() == 0.0); LuaAssert(L, aqds.nth(2).xyz() == util::XYZ(3,4,5)); LuaAssert(L, aqds.nth(2).graphic() == "banana"); LuaAssert(L, aqds.nth(2).plane() == ""); LuaAssert(L, aqds.nth(3).id() == 12347); LuaAssert(L, aqds.nth(3).bits() == AnimStep::HAS_FACING); LuaAssert(L, aqds.nth(3).action() == "setfacing"); LuaAssert(L, aqds.nth(3).facing() == 301.0); LuaAssert(L, aqds.nth(3).xyz() == util::XYZ(3,4,5)); LuaAssert(L, aqds.nth(3).graphic() == "banana"); LuaAssert(L, aqds.nth(3).plane() == ""); return 0; }