Mercurial > malikania
changeset 160:bbb506feb55f
Server: implement new stats for character, closes #761 @2h
Each character no longer have a global level integer. Instead, separate each
stat with a different level, factor and experience which define the current stat
level, the desired partition of experience (from 100%) for this stat and the
current experience respectively.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 05 Mar 2018 21:04:14 +0100 |
parents | 7362fba6ff11 |
children | be0d90706ded |
files | libdb-sqlite/malikania/server/db/sqlite_character_dao.cpp libdb-sqlite/malikania/server/db/sqlite_character_dao.hpp libdb-sqlite/malikania/server/db/sqlite_database.cpp libserver-test/malikania/server/db/broken_character_dao.cpp libserver-test/malikania/server/db/broken_character_dao.hpp libserver/malikania/server/db/account.hpp libserver/malikania/server/db/character.cpp libserver/malikania/server/db/character.hpp libserver/malikania/server/db/model.hpp libserver/malikania/server/db/spell.hpp tests/libserver/db/account/main.cpp |
diffstat | 11 files changed, 342 insertions(+), 55 deletions(-) [+] |
line wrap: on
line diff
--- a/libdb-sqlite/malikania/server/db/sqlite_character_dao.cpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libdb-sqlite/malikania/server/db/sqlite_character_dao.cpp Mon Mar 05 21:04:14 2018 +0100 @@ -16,8 +16,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <boost/format.hpp> + #include "sqlite_database.hpp" +using boost::format; +using boost::str; + namespace mlk { namespace server { @@ -26,21 +31,62 @@ { character ch(db_, to_string(stmt, 2), to_string(stmt, 3)); - ch.set_level(to_int(stmt, 4)); ch.set_id(to_int(stmt, 0)); + ch.get_levels()[character::hp] = to_int(stmt, 4); + ch.get_factors()[character::hp] = to_int(stmt, 5); + ch.get_experience()[character::hp] = to_int(stmt, 6); + ch.get_levels()[character::defense] = to_int(stmt, 7); + ch.get_factors()[character::defense] = to_int(stmt, 8); + ch.get_experience()[character::defense] = to_int(stmt, 9); + ch.get_levels()[character::agility] = to_int(stmt, 10); + ch.get_factors()[character::agility] = to_int(stmt, 11); + ch.get_experience()[character::agility] = to_int(stmt, 12); + ch.get_levels()[character:: dodge] = to_int(stmt, 13); + ch.get_factors()[character:: dodge] = to_int(stmt, 14); + ch.get_experience()[character:: dodge] = to_int(stmt, 15); + ch.get_levels()[character::luck] = to_int(stmt, 16); + ch.get_factors()[character::luck] = to_int(stmt, 17); + ch.get_experience()[character::luck] = to_int(stmt, 18); return ch; } -void sqlite_character_dao::set_level(const character& ch, std::uint16_t level) +template <typename Array> +void sqlite_character_dao::set_what(const character& ch, const Array& array, const std::string& key) { - const std::string sql( + const auto sql = str(format( "UPDATE character" - " SET level = ?" - " WHERE id = ?" + " SET hp_%1$ = ?" + " , defense_%1% = ?" + " , agility_%1$ = ?" + " , dodge_%1% = ?" + " , luck_%1% = ?" + " WHERE id = ?") % key ); - exec(db_.instance(), sql, {level, ch.get_id()}); + exec(db_.instance(), sql, { + array[character::hp], + array[character::defense], + array[character::agility], + array[character::dodge], + array[character::luck], + ch.get_id() + }); +} + +void sqlite_character_dao::set_levels(const character& ch, const std::array<uint8_t, 5>& levels) +{ + set_what(ch, levels, "level"); +} + +void sqlite_character_dao::set_factors(const character& ch, const std::array<uint8_t, 5>& factors) +{ + set_what(ch, factors, "factor"); +} + +void sqlite_character_dao::set_experience(const character& ch, const std::array<uint32_t, 5>& experience) +{ + set_what(ch, experience, "experience"); } void sqlite_character_dao::publish(character& ch, const account& parent) @@ -50,15 +96,43 @@ " account_id," " nickname," " classname," - " level" - ") VALUES (?, ?, ?, ?)" + " hp_level," + " hp_factor," + " hp_experience," + " defense_level," + " defense_factor," + " defense_experience," + " agility_level," + " agility_factor," + " agility_experience," + " dodge_level," + " dodge_factor," + " dodge_experience," + " luck_level," + " luck_factor," + " luck_experience" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ); exec(db_.instance(), sql, { parent.get_id(), ch.get_nickname(), ch.get_classname(), - ch.get_level() + ch.get_levels()[character::hp], + ch.get_factors()[character::hp], + ch.get_experience()[character::hp], + ch.get_levels()[character::defense], + ch.get_factors()[character::defense], + ch.get_experience()[character::defense], + ch.get_levels()[character::agility], + ch.get_factors()[character::agility], + ch.get_experience()[character::agility], + ch.get_levels()[character::dodge], + ch.get_factors()[character::dodge], + ch.get_experience()[character::dodge], + ch.get_levels()[character::luck], + ch.get_factors()[character::luck], + ch.get_experience()[character::luck] }); }
--- a/libdb-sqlite/malikania/server/db/sqlite_character_dao.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libdb-sqlite/malikania/server/db/sqlite_character_dao.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -44,6 +44,9 @@ character get(stmt_ptr&); + template <typename Array> + void set_what(const character&, const Array&, const std::string&); + public: /** * Constructor. @@ -64,9 +67,19 @@ std::vector<character> load(const account& parent); /** - * \copydoc character_dao::set_level + * \copydoc character_dao::set_levels + */ + void set_levels(const character& ch, const std::array<uint8_t, 5>& levels) override; + + /** + * \copydoc character_dao::set_factors */ - void set_level(const character& ch, std::uint16_t level) override; + void set_factors(const character& ch, const std::array<uint8_t, 5>& levels) override; + + /** + * \copydoc character_dao::set_experience + */ + void set_experience(const character& ch, const std::array<uint32_t, 5>& experience) override; /** * \copydoc character_dao::publish
--- a/libdb-sqlite/malikania/server/db/sqlite_database.cpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libdb-sqlite/malikania/server/db/sqlite_database.cpp Mon Mar 05 21:04:14 2018 +0100 @@ -43,7 +43,21 @@ " account_id INTEGER," " nickname TEXT," " classname TEXT," - " level INTEGER," + " hp_level INTEGER," + " hp_factor INTEGER," + " hp_experience INTEGER," + " defense_level INTEGER," + " defense_factor INTEGER," + " defense_experience INTEGER," + " agility_level INTEGER," + " agility_factor INTEGER," + " agility_experience INTEGER," + " dodge_level INTEGER," + " dodge_factor INTEGER," + " dodge_experience INTEGER," + " luck_level INTEGER," + " luck_factor INTEGER," + " luck_experience INTEGER," " FOREIGN KEY(account_id) REFERENCES account(id) ON DELETE CASCADE" ")" );
--- a/libserver-test/malikania/server/db/broken_character_dao.cpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver-test/malikania/server/db/broken_character_dao.cpp Mon Mar 05 21:04:14 2018 +0100 @@ -38,10 +38,22 @@ throw std::runtime_error("broken unpublish"); } -void broken_character_dao::set_level(const character&, std::uint16_t) +void broken_character_dao::set_levels(const character&, const std::array<uint8_t, 5>&) +{ + if (!bool(allow_ & allow_flags::set_levels)) + throw std::runtime_error("broken set_levels"); +} + +void broken_character_dao::set_factors(const character&, const std::array<uint8_t, 5>&) { - if (!bool(allow_ & allow_flags::set_level)) - throw std::runtime_error("broken set_level"); + if (!bool(allow_ & allow_flags::set_factors)) + throw std::runtime_error("broken set_factors"); +} + +void broken_character_dao::set_experience(const character&, const std::array<uint32_t, 5>&) +{ + if (!bool(allow_ & allow_flags::set_experience)) + throw std::runtime_error("broken set_experience"); } } // !server
--- a/libserver-test/malikania/server/db/broken_character_dao.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver-test/malikania/server/db/broken_character_dao.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -42,7 +42,9 @@ none = 0, //!< everything is broken publish = (1 << 0), //!< allow publish unpublish = (1 << 1), //!< allow unpublish - set_level = (1 << 2), //!< allow set_level + set_levels = (1 << 2), //!< allow set_levels + set_factors = (1 << 3), //!< allow set_factors + set_experience = (1 << 4), //!< allow set_experience }; private: @@ -60,9 +62,19 @@ } /** - * \copydoc character_dao::set_level + * \copydoc character_dao::set_levels + */ + void set_levels(const character& ch, const std::array<uint8_t, 5>& levels) override; + + /** + * \copydoc character_dao::set_factors */ - void set_level(const character& ch, std::uint16_t level) override; + void set_factors(const character& ch, const std::array<uint8_t, 5>& factors) override; + + /** + * \copydoc character_dao::set_experience + */ + void set_experience(const character& ch, const std::array<uint32_t, 5>& experience) override; /** * \copydoc character_dao::publish
--- a/libserver/malikania/server/db/account.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver/malikania/server/db/account.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -37,13 +37,13 @@ * \brief Database account object. */ class account : public model { -protected: - std::string login_; //!< unique login (not null) - std::string password_; //!< password stored as-is - std::string email_; //!< raw email - std::string firstname_; //!< optional first name - std::string lastname_; //!< optional last name - std::vector<character> characters_; //!< list of characters +private: + std::string login_; + std::string password_; + std::string email_; + std::string firstname_; + std::string lastname_; + std::vector<character> characters_; public: /**
--- a/libserver/malikania/server/db/character.cpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver/malikania/server/db/character.cpp Mon Mar 05 21:04:14 2018 +0100 @@ -16,6 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <numeric> + #include "character.hpp" #include "database.hpp" @@ -23,12 +25,30 @@ namespace server { -void character::set_level(std::uint16_t level) +void character::set_levels(std::array<std::uint8_t, 5> levels) +{ + if (is_published()) + db_.characters().set_levels(*this, levels); + + levels_ = std::move(levels); +} + +void character::set_factors(std::array<std::uint8_t, 5> factors) { - if (is_published() && level_ != level) - db_.characters().set_level(*this, level); + assert(std::accumulate(factors.begin(), factors.end(), 0U) == 100U); + + if (is_published()) + db_.characters().set_factors(*this, factors); - level_ = level; + factors_ = std::move(factors); +} + +void character::set_experience(std::array<std::uint32_t, 5> experience) +{ + if (is_published()) + db_.characters().set_experience(*this, experience); + + exp_ = std::move(experience); } void character::add(spell sp)
--- a/libserver/malikania/server/db/character.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver/malikania/server/db/character.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -24,6 +24,8 @@ * \brief Database character object. */ +#include <cstdint> +#include <array> #include <vector> #include "model.hpp" @@ -39,11 +41,28 @@ * \brief Database character object. */ class character : public model { -protected: - std::string nickname_; //!< nickname (non null) - std::string classname_; //!< class type to instanciate - std::uint16_t level_{1}; //!< character level - std::vector<spell> spells_; //!< list of spells +public: + /** + * \brief Stats index in arrays + * \see set_levels + * \see set_factors + * \see set_experience + */ + enum stat { + hp = 0, //!< hp index + defense, //!< defense index + agility, //!< agility index + dodge, //!< dodge index + luck, //!< luck index + }; + +private: + std::string nickname_; + std::string classname_; + std::vector<spell> spells_; + std::array<std::uint8_t, 5> levels_{1U, 1U, 1U, 1U, 1U}; + std::array<std::uint8_t, 5> factors_{20U, 20U, 20U, 20U, 20U}; + std::array<std::uint32_t, 5> exp_{0U, 0U, 0U, 0U, 0U}; public: /** @@ -106,21 +125,95 @@ #endif // !MALIKANIA_PRIVATE /** - * Get the account level. + * Get the list of levels. * - * \return the level + * \return the levels + */ + inline const std::array<std::uint8_t, 5>& get_levels() const noexcept + { + return levels_; + } + +#if defined(MALIKANIA_PRIVATE) + /** + * Get the list of levels. + * + * \return the levels + * \note only available if MALIKANIA_PRIVATE macro is defined */ - inline std::uint16_t get_level() const noexcept + inline std::array<std::uint8_t, 5>& get_levels() noexcept { - return level_; + return levels_; + } +#endif // !MALIKANIA_PRIVATE + + /** + * Set the new levels. + * + * \param levels the levels + */ + void set_levels(std::array<std::uint8_t, 5> levels); + + /** + * Get the list of factors. + * + * \return the factors + */ + inline const std::array<std::uint8_t, 5>& get_factors() const noexcept + { + return factors_; } +#if defined(MALIKANIA_PRIVATE) /** - * Set the account level. + * Get the list of factors. + * + * \return the factors + * \note only available if MALIKANIA_PRIVATE macro is defined + */ + inline std::array<std::uint8_t, 5>& get_factors() noexcept + { + return factors_; + } +#endif // !MALIKANIA_PRIVATE + + /** + * Set progression factors. + * + * \pre factors sum must be 100 + * \param factors the factors + */ + void set_factors(std::array<std::uint8_t, 5> factors); + + /** + * Get the stats experience. * - * \param level the level + * \return the experience */ - void set_level(std::uint16_t level); + inline const std::array<std::uint32_t, 5>& get_experience() const noexcept + { + return exp_; + } + +#if defined(MALIKANIA_PRIVATE) + /** + * Get the stats experience. + * + * \return the experience + * \note only available if MALIKANIA_PRIVATE macro is defined + */ + inline std::array<std::uint32_t, 5>& get_experience() noexcept + { + return exp_; + } +#endif // !MALIKANIA_PRIVATE + + /** + * Set stats experience. + * + * \param experience the experience + */ + void set_experience(std::array<std::uint32_t, 5> experience); /** * Add a spell. @@ -181,17 +274,34 @@ virtual void unpublish(const character& ch) = 0; /** - * Update the character level in database. + * Update the character levels in database. * - * Only called when the level needs to be changed, the implementation does - * not need to update level_ field. + * \param ch the character + * \param levels the new levels + * \note called from character::set_levels helper + * \throw std::exception if the operation could not succeed + */ + virtual void set_levels(const character& ch, const std::array<std::uint8_t, 5>& levels) = 0; + + /** + * Update the character progression factors in database. * * \param ch the character - * \param level the new level - * \note called from character::set_level helper + * \param factors the new factors + * \note called from character::set_levels helper * \throw std::exception if the operation could not succeed */ - virtual void set_level(const character& ch, std::uint16_t level) = 0; + virtual void set_factors(const character& ch, const std::array<std::uint8_t, 5>& levels) = 0; + + /** + * Update the current level experiences in database. + * + * \param ch the character + * \param experience the new experience + * \note called from character::set_levels helper + * \throw std::exception if the operation could not succeed + */ + virtual void set_experience(const character& ch, const std::array<std::uint32_t, 5>& levels) = 0; }; } // !server
--- a/libserver/malikania/server/db/model.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver/malikania/server/db/model.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -24,7 +24,6 @@ * \brief Abstract database object. */ -#include <cassert> #include <cstdint> namespace mlk {
--- a/libserver/malikania/server/db/spell.hpp Sat Mar 03 17:34:12 2018 +0100 +++ b/libserver/malikania/server/db/spell.hpp Mon Mar 05 21:04:14 2018 +0100 @@ -24,6 +24,7 @@ * \brief Database spell object. */ +#include <cassert> #include <string> #include "model.hpp" @@ -38,9 +39,9 @@ * \brief Describe a spell. */ class spell : public model { -protected: - std::string classname_; //!< class type to instanciate - std::uint8_t level_{1}; //!< spell level +private: + std::string classname_; + std::uint8_t level_{1}; public: /**
--- a/tests/libserver/db/account/main.cpp Sat Mar 03 17:34:12 2018 +0100 +++ b/tests/libserver/db/account/main.cpp Mon Mar 05 21:04:14 2018 +0100 @@ -153,14 +153,18 @@ { character ch(db, "erekin", "blackmage"); - ch.set_level(100); + ch.set_levels({10U, 7U, 12U, 9U, 8U}); + ch.set_factors({20U, 20U, 20U, 20U, 20U}); + ch.set_experience({1000U, 2000U, 3000U, 4000U, 5000U}); ac.add(std::move(ch)); } { character ch(db, "luna", "fairy"); - ch.set_level(45); + ch.set_levels({12U, 6U, 12U, 9U, 8U}); + ch.set_factors({20U, 20U, 20U, 20U, 20U}); + ch.set_experience({2000U, 3000U, 4000U, 5000U, 6000U}); ac.add(std::move(ch)); } } @@ -188,7 +192,21 @@ BOOST_TEST(erekin->get_nickname() == "erekin"); BOOST_TEST(erekin->get_classname() == "blackmage"); - BOOST_TEST(erekin->get_level() == 100U); + BOOST_TEST(erekin->get_levels()[character::hp] == 10U); + BOOST_TEST(erekin->get_levels()[character::defense] == 7U); + BOOST_TEST(erekin->get_levels()[character::agility] == 12U); + BOOST_TEST(erekin->get_levels()[character::dodge] == 9U); + BOOST_TEST(erekin->get_levels()[character::luck] == 8U); + BOOST_TEST(erekin->get_factors()[character::hp] == 20U); + BOOST_TEST(erekin->get_factors()[character::defense] == 20U); + BOOST_TEST(erekin->get_factors()[character::agility] == 20U); + BOOST_TEST(erekin->get_factors()[character::dodge] == 20U); + BOOST_TEST(erekin->get_factors()[character::luck] == 20U); + BOOST_TEST(erekin->get_experience()[character::hp] == 1000U); + BOOST_TEST(erekin->get_experience()[character::defense] == 2000U); + BOOST_TEST(erekin->get_experience()[character::agility] == 3000U); + BOOST_TEST(erekin->get_experience()[character::dodge] == 4000U); + BOOST_TEST(erekin->get_experience()[character::luck] == 5000U); const auto luna = std::find_if(characters.begin(), characters.end(), [] (const auto& c) { return c.get_nickname() == "luna"; @@ -196,7 +214,21 @@ BOOST_TEST(luna->get_nickname() == "luna"); BOOST_TEST(luna->get_classname() == "fairy"); - BOOST_TEST(luna->get_level() == 45U); + BOOST_TEST(luna->get_levels()[character::hp] == 12U); + BOOST_TEST(luna->get_levels()[character::defense] == 6U); + BOOST_TEST(luna->get_levels()[character::agility] == 12U); + BOOST_TEST(luna->get_levels()[character::dodge] == 9U); + BOOST_TEST(luna->get_levels()[character::luck] == 8U); + BOOST_TEST(luna->get_factors()[character::hp] == 20U); + BOOST_TEST(luna->get_factors()[character::defense] == 20U); + BOOST_TEST(luna->get_factors()[character::agility] == 20U); + BOOST_TEST(luna->get_factors()[character::dodge] == 20U); + BOOST_TEST(luna->get_factors()[character::luck] == 20U); + BOOST_TEST(luna->get_experience()[character::hp] == 2000U); + BOOST_TEST(luna->get_experience()[character::defense] == 3000U); + BOOST_TEST(luna->get_experience()[character::agility] == 4000U); + BOOST_TEST(luna->get_experience()[character::dodge] == 5000U); + BOOST_TEST(luna->get_experience()[character::luck] == 6000U); } BOOST_AUTO_TEST_CASE_TEMPLATE(set_password, Database, database_types)