#include #include "luastack.hpp" #include "animqueue.hpp" AnimStep::AnimStep() {} AnimStep::~AnimStep() {} AnimQueue::AnimQueue() { id_ = 0; size_limit_ = 10; // Default size limit. clear_steps(); } void AnimQueue::clear_steps() { steps_.clear(); steps_.emplace_back(); AnimStep &init = steps_.back(); init.id_ = 0; init.bits_ = AnimStep::HAS_EVERYTHING; init.facing_ = 0; init.xyz_ = util::XYZ(0,0,0); init.graphic_ = "nothing"; init.plane_ = "nowhere"; } void AnimQueue::set_size_limit(int n) { assert(n >= 2); size_limit_ = n; } void AnimQueue::add(int64_t id, lua_State *L, int idx) { LuaSpecial tab(idx); LuaVar value; LuaStack LS(L, value); if (!LS.istable(tab)) { luaL_error(L, "animation spec must be a table"); } AnimStep step = steps_.back(); step.id_ = id; step.bits_ = 0; step.action_ = ""; LS.rawget(value, tab, "action"); if (!LS.isstring(value)) { luaL_error(L, "animation action is not optional and must be a string"); } step.action_ = LS.ckstring(value); LS.rawget(value, tab, "facing"); if (LS.isnumber(value)) { step.facing_ = LS.cknumber(value); step.bits_ |= AnimStep::HAS_FACING; } else if (!LS.isnil(value)) { luaL_error(L, "animation facing must be a number"); } LS.rawget(value, tab, "x"); if (LS.isnumber(value)) { step.xyz_.x = LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation X coordinate must be a number"); } LS.rawget(value, tab, "y"); if (LS.isnumber(value)) { step.xyz_.y = LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation Y coordinate must be a number"); } LS.rawget(value, tab, "z"); if (LS.isnumber(value)) { step.xyz_.z = LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation Z coordinate must be a number"); } LS.rawget(value, tab, "dx"); if (LS.isnumber(value)) { step.xyz_.x += LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation DX offset must be a number"); } LS.rawget(value, tab, "dy"); if (LS.isnumber(value)) { step.xyz_.y += LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation DY offset must be a number"); } LS.rawget(value, tab, "dz"); if (LS.isnumber(value)) { step.xyz_.z += LS.cknumber(value); step.bits_ |= AnimStep::HAS_XYZ; } else if (!LS.isnil(value)) { luaL_error(L, "animation DZ offset must be a number"); } LS.rawget(value, tab, "graphic"); if (LS.isstring(value)) { step.graphic_ = LS.ckstring(value); step.bits_ |= AnimStep::HAS_GRAPHIC; } else if (!LS.isnil(value)) { luaL_error(L, "animation graphic must be a string"); } LS.rawget(value, tab, "plane"); if (LS.isstring(value)) { step.plane_ = LS.ckstring(value); step.bits_ |= AnimStep::HAS_PLANE; } else if (!LS.isnil(value)) { luaL_error(L, "animation plane must be a string"); } steps_.push_back(step); while (int(steps_.size()) > size_limit_) { steps_.pop_front(); } AnimStep &init = steps_.front(); init.id_ = 0; init.action_ = ""; init.bits_ = AnimStep::HAS_EVERYTHING; } void AnimQueue::add(int64_t id, const std::string &action) { AnimStep step = steps_.back(); step.id_ = id; step.action_ = action; step.bits_ = 0; steps_.push_back(step); while (int(steps_.size()) > size_limit_) { steps_.pop_front(); } AnimStep &init = steps_.front(); init.id_ = 0; init.action_ = ""; init.bits_ = AnimStep::HAS_EVERYTHING; } void AnimQueue::set_facing(float f) { AnimStep &last = steps_.back(); last.bits_ |= AnimStep::HAS_FACING; last.facing_ = f; } void AnimQueue::set_xyz(util::XYZ xyz) { AnimStep &last = steps_.back(); last.bits_ |= AnimStep::HAS_XYZ; last.xyz_ = xyz; } void AnimQueue::set_graphic(const std::string &g) { AnimStep &last = steps_.back(); last.bits_ |= AnimStep::HAS_GRAPHIC; last.graphic_ = g; } void AnimQueue::set_plane(const std::string &p) { AnimStep &last = steps_.back(); last.bits_ |= AnimStep::HAS_PLANE; last.plane_ = p; } 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.has_facing() && (curr.facing_ != prev.facing_)) { return false; } if (!curr.has_xyz() && (curr.xyz_ != prev.xyz_)) { return false; } if (!curr.has_graphic() && (curr.graphic_ != prev.graphic_)) { return false; } if (!curr.has_plane() && (curr.plane_ != prev.plane_)) { return false; } } return true; } void AnimQueue::serialize(StreamBuffer *sb) { assert(valid()); // can't serialize an invalid animqueue. sb->write_int64(id_); 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_xyz()) { sb->write_float(step.xyz_.x); sb->write_float(step.xyz_.y); 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) { id_ = sb->read_int64(); size_limit_ = sb->read_int32(); size_t nsteps = sb->read_size(); steps_.clear(); 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_xyz()) { step.xyz_.x = sb->read_float(); step.xyz_.y = sb->read_float(); 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 std::string &AnimQueue::get_graphic() const { const AnimStep &last = steps_.back(); return last.graphic_; } const std::string &AnimQueue::get_plane() const { const AnimStep &last = steps_.back(); return last.plane_; } const util::XYZ &AnimQueue::get_xyz() const { const AnimStep &last = steps_.back(); return last.xyz_; } LuaDefine(unittests_animqueue, "c") { // Check initial state. 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() == "nothing"); LuaAssert(L, st->plane() == "nowhere"); // Add a step. aq.add(12345, "walk"); 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() == "nothing"); LuaAssert(L, st->plane() == "nowhere"); // Test the setters. aq.set_facing(180); LuaAssert(L, st->facing() == 180); LuaAssert(L, st->bits() == AnimStep::HAS_FACING); aq.set_xyz(util::XYZ(3,4,5)); LuaAssert(L, st->xyz() == util::XYZ(3, 4, 5)); LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ)); aq.set_plane("somewhere"); LuaAssert(L, st->plane() == "somewhere"); LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE)); aq.set_graphic("something"); LuaAssert(L, st->graphic() == "something"); LuaAssert(L, st->bits() == (AnimStep::HAS_FACING | AnimStep::HAS_XYZ | AnimStep::HAS_PLANE | AnimStep::HAS_GRAPHIC)); LuaAssert(L, aq.valid()); // Exceed the length limit, dropping first element. aq.add(12346, "walk"); aq.add(12347, "walk"); 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_id(123); aq.set_size_limit(5); aq.clear_steps(); aq.add(12345, "walk"); aq.set_xyz(util::XYZ(3,4,5)); aq.add(12346, "setgraphic"); aq.set_graphic("banana"); aq.add(12347, "setfacing"); aq.set_facing(301.0); aq.serialize(&sb); aqds.deserialize(&sb); LuaAssert(L, aqds.get_id() == 123); 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() == "nothing"); LuaAssert(L, aqds.nth(0).plane() == "nowhere"); 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() == "nothing"); LuaAssert(L, aqds.nth(1).plane() == "nowhere"); 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() == "nowhere"); 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() == "nowhere"); return 0; }