From 530b969d728d057c83519b91ddad841ef6600752 Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Thu, 3 Apr 2025 16:39:30 +0300 Subject: [PATCH 1/6] Account refactoring - remove player list from account file (because this information is stored in player profile) - build lis of players in account on the fly --- src/administration/accounts.cpp | 74 ++++++++--------------------- src/administration/accounts.h | 9 ++-- src/engine/db/db.cpp | 1 - src/engine/entities/char_player.cpp | 1 - src/engine/ui/interpreter.cpp | 2 - 5 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index b45627cb3..d9a07a3a0 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -59,21 +59,26 @@ void Account::complete_quest(int id) { this->dquests.push_back(dq_tmp); } -void Account::purge_erased() { - std::string uid; - for (size_t i = 0; i < this->players_list.size(); i++) { - uid = GetNameByUnique(this->players_list[i]); - if (uid.empty()) { - this->players_list.erase(this->players_list.begin() + i); +std::vector Account::all_chars_in_account() const +{ + std::vector result_list; + + const auto &player_list = GlobalObjects::player_table(); + std::copy_if(player_list.begin(), player_list.end(), std::back_inserter(result_list), + [this](const PlayerIndexElement &pie) { + if (!pie.mail) { + return false; } - } - save_to_file(); + + return email.compare(pie.mail) == 0; + }); + + return result_list; } void Account::show_players(CharData *ch) { int count = 1; std::stringstream ss; - purge_erased(); ss << "Данные аккаунта: " << this->email << "\r\n"; for (auto &x : this->players_list) { std::string name = GetNameByUnique(x); @@ -87,11 +92,14 @@ void Account::show_players(CharData *ch) { void Account::list_players(DescriptorData *d) { int count = 1; std::stringstream ss; - purge_erased(); + const auto &chars_in_account = all_chars_in_account(); ss << "Данные аккаунта: " << this->email << "\r\n"; iosystem::write_to_output(ss.str().c_str(), d); - for (auto &x : this->players_list) { - std::string name = GetNameByUnique(x); + for (auto &x : chars_in_account) { + if (!x.name()) { + continue; + } + std::string name(x.name()); iosystem::write_to_output((std::to_string(count) + ") ").c_str(), d); name[0] = UPPER(name[0]); iosystem::write_to_output(name.c_str(), d); @@ -135,26 +143,6 @@ void Account::read_from_file() { tmp_quest.time = atoi(tmp[3].c_str()); this->dquests.push_back(tmp_quest); } -/* if (line.starts_with("hl: ")) { - utils::Split(tmp, line); - login_index tmp_li; - tmp_li.count = atoi(tmp[2].c_str()); - tmp_li.last_login = atoi(tmp[3].c_str()); - this->history_logins.insert(std::pair(tmp[1].c_str(), tmp_li)); - } - if (line.starts_with("p: ")) { - utils::Split(tmp, line); - this->add_player(atoi(tmp[1].c_str())); - } - if (line.starts_with("Pwd: ")) { - utils::Split(tmp, line); - this->hash_password = tmp[1]; - } - if (line.starts_with("ll: ")) { - utils::Split(tmp, line); - this->last_login = atoi(tmp[1].c_str()); - } -*/ } in.close(); } @@ -163,26 +151,6 @@ void Account::read_from_file() { std::string Account::get_email() { return this->email; } -\ -void Account::add_player(long uid) { - // если уже есть, то не добавляем - for (auto &x : this->players_list) { - if (x == uid) { - return; - } - } - this->players_list.push_back(uid); -} - -void Account::remove_player(long uid) { - for (size_t i = 0; i < this->players_list.size(); i++) { - if (this->players_list[i] == uid) { - this->players_list.erase(this->players_list.begin() + i); - return; - } - } - //mudlog("Функция Account::remove_player, uid %d не был найден", uid); -} time_t Account::get_last_login() const { return this->last_login; @@ -240,4 +208,4 @@ bool Account::quest_is_available(int id) { } } return true; -} \ No newline at end of file +} diff --git a/src/administration/accounts.h b/src/administration/accounts.h index 7ab598d22..13fa6ce87 100644 --- a/src/administration/accounts.h +++ b/src/administration/accounts.h @@ -9,6 +9,7 @@ #include "engine/structs/structs.h" #include "engine/entities/char_data.h" #include "engine/network/descriptor_data.h" +#include "engine/db/global_objects.h" #include #include @@ -45,7 +46,6 @@ class Account { std::unordered_map history_logins; public: - void purge_erased(); Account(const std::string &name); void save_to_file(); void read_from_file(); @@ -56,18 +56,19 @@ class Account { static std::shared_ptr get_account(const std::string &email); void show_players(CharData *ch); void list_players(DescriptorData *d); - void add_player(long uid); - void remove_player(long uid); time_t get_last_login() const; void set_last_login(); void set_password(const std::string &password); bool compare_password(const std::string &password); void show_history_logins(CharData *ch); void add_login(const std::string &ip_addr); + +private: + std::vector all_chars_in_account() const; }; extern std::unordered_map> accounts; #endif //__ACCOUNTS_HPP__ -// vim: ts=4 sw=4 tw=0 noet syntax=cpp : \ No newline at end of file +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/db.cpp b/src/engine/db/db.cpp index 6f5173652..91bb294da 100644 --- a/src/engine/db/db.cpp +++ b/src/engine/db/db.cpp @@ -3200,7 +3200,6 @@ void ActualizePlayersIndex(char *name) { log("Delete %s from account email: %s", GET_NAME(short_ch), short_ch->get_account()->get_email().c_str()); - short_ch->get_account()->remove_player(short_ch->get_uid()); } } else { log("SYSERR: Failed to load player %s.", name); diff --git a/src/engine/entities/char_player.cpp b/src/engine/entities/char_player.cpp index ab585ce2d..d8659aa32 100644 --- a/src/engine/entities/char_player.cpp +++ b/src/engine/entities/char_player.cpp @@ -1096,7 +1096,6 @@ int Player::load_char_ascii(const char *name, const int load_flags) { this->account = temp_account; } // log("Add account %s player id %d name %s", GET_EMAIL(this), this->get_uid(), name); - this->account->add_player(this->get_uid()); if (load_flags & ELoadCharFlags::kReboot) { fbclose(fl); diff --git a/src/engine/ui/interpreter.cpp b/src/engine/ui/interpreter.cpp index 06bb30e80..e1b050dc4 100644 --- a/src/engine/ui/interpreter.cpp +++ b/src/engine/ui/interpreter.cpp @@ -2594,7 +2594,6 @@ void DoAfterEmailConfirm(DescriptorData *d) { } d->character->save_char(); d->character->get_account()->set_last_login(); - d->character->get_account()->add_player(d->character->get_uid()); // добавляем в список ждущих одобрения if (!(int) NAME_FINE(d->character)) { @@ -3592,7 +3591,6 @@ void nanny(DescriptorData *d, char *argument) { iosystem::write_to_output(buffer, d); sprintf(buffer, "%s (lev %d) has self-deleted.", GET_NAME(d->character), GetRealLevel(d->character)); mudlog(buffer, NRM, kLvlGod, SYSLOG, true); - d->character->get_account()->remove_player(d->character->get_uid()); STATE(d) = CON_CLOSE; return; } else { From 9e4bb93eb04704c42b67c7352b10f66a30a36a7b Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Thu, 3 Apr 2025 17:04:42 +0300 Subject: [PATCH 2/6] Account refactoring - removed unnecessary method Account::get_email() --- src/administration/accounts.cpp | 7 +++---- src/administration/accounts.h | 1 - src/engine/db/db.cpp | 4 ---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index d9a07a3a0..f296834aa 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -20,6 +20,9 @@ std::shared_ptr Account::get_account(const std::string &email) { } return nullptr; } + +// TODO логика никак не относится к аккаунту +// убрать процессинг к валютам, когда они будут готовы int Account::zero_hryvn(CharData *ch, int val) { const int zone_lvl = zone_table[world[ch->in_room]->zone_rn].mob_level; for (auto &plr : this->players_list) { @@ -148,10 +151,6 @@ void Account::read_from_file() { } } -std::string Account::get_email() { - return this->email; -} - time_t Account::get_last_login() const { return this->last_login; } diff --git a/src/administration/accounts.h b/src/administration/accounts.h index 13fa6ce87..24038692d 100644 --- a/src/administration/accounts.h +++ b/src/administration/accounts.h @@ -49,7 +49,6 @@ class Account { Account(const std::string &name); void save_to_file(); void read_from_file(); - std::string get_email(); bool quest_is_available(int id); int zero_hryvn(CharData *ch, int val); void complete_quest(int id); diff --git a/src/engine/db/db.cpp b/src/engine/db/db.cpp index 91bb294da..1c7c1a7f8 100644 --- a/src/engine/db/db.cpp +++ b/src/engine/db/db.cpp @@ -3196,10 +3196,6 @@ void ActualizePlayersIndex(char *name) { log("Adding new player %s", element.name()); player_table.Append(element); - } else { - log("Delete %s from account email: %s", - GET_NAME(short_ch), - short_ch->get_account()->get_email().c_str()); } } else { log("SYSERR: Failed to load player %s.", name); From fed786a3088c1b5c97a631c7e5aaa2842a814da7 Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Thu, 3 Apr 2025 18:01:07 +0300 Subject: [PATCH 3/6] Account refactoring - removed unnecessary fields from Account class --- src/administration/accounts.cpp | 30 +++++++++++++++--------------- src/administration/accounts.h | 2 -- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index f296834aa..98df2eda6 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -25,21 +25,20 @@ std::shared_ptr Account::get_account(const std::string &email) { // убрать процессинг к валютам, когда они будут готовы int Account::zero_hryvn(CharData *ch, int val) { const int zone_lvl = zone_table[world[ch->in_room]->zone_rn].mob_level; - for (auto &plr : this->players_list) { - std::string name = GetNameByUnique(plr); - if (name.empty()) { + const auto &chars_in_account = all_chars_in_account(); + for (auto &plr : chars_in_account) { + if (!plr.name()) { continue; } - const auto &player = player_table[GetPtableByUnique(plr)]; - if (zone_lvl <= 12 && (player.level + player.remorts / 5 >= 20)) { + if (zone_lvl <= 12 && (plr.level + plr.remorts / 5 >= 20)) { if (ch->IsFlagged(EPrf::kTester)) { SendMsgToChar(ch, "У чара %s в расчете %d гривен, тут будет 0, левел %d морты %d обнуляем!!!\r\n", - player.name(), + plr.name(), val, - player.level, - player.remorts); + plr.level, + plr.remorts); } val = 0; } @@ -66,8 +65,8 @@ std::vector Account::all_chars_in_account() const { std::vector result_list; - const auto &player_list = GlobalObjects::player_table(); - std::copy_if(player_list.begin(), player_list.end(), std::back_inserter(result_list), + const auto &player_table = GlobalObjects::player_table(); + std::copy_if(player_table.begin(), player_table.end(), std::back_inserter(result_list), [this](const PlayerIndexElement &pie) { if (!pie.mail) { return false; @@ -82,9 +81,13 @@ std::vector Account::all_chars_in_account() const void Account::show_players(CharData *ch) { int count = 1; std::stringstream ss; + const auto &chars_in_account = all_chars_in_account(); ss << "Данные аккаунта: " << this->email << "\r\n"; - for (auto &x : this->players_list) { - std::string name = GetNameByUnique(x); + for (auto &x : chars_in_account) { + if (!x.name()) { + continue; + } + std::string name(x.name()); name[0] = UPPER(name[0]); ss << count << ") " << name << "\r\n"; count++; @@ -122,9 +125,6 @@ void Account::save_to_file() { for (const auto &x : this->history_logins) { out << "hl: " << x.first << " " << x.second.count << " " << x.second.last_login << "\n"; } - for (const auto &x : this->players_list) { - out << "p: " << x << "\n"; - } out << "Pwd: " << this->hash_password << "\n"; out << "ll: " << this->last_login << "\n"; } diff --git a/src/administration/accounts.h b/src/administration/accounts.h index 24038692d..68442844a 100644 --- a/src/administration/accounts.h +++ b/src/administration/accounts.h @@ -36,8 +36,6 @@ class Account { std::vector characters; std::vector dquests; std::string karma; - // список чаров на мыле (только уиды) - std::vector players_list; // пароль (а точнее его хеш) аккаунта std::string hash_password; // дата последнего входа в аккаунт From 651c322f9b70c813906e025ee1d5ab1c63777d6a Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Thu, 3 Apr 2025 20:59:47 +0300 Subject: [PATCH 4/6] Accout refactoring - refactored accounts code to modern style --- src/administration/accounts.cpp | 122 +++++++++++++++++++++----------- src/administration/accounts.h | 45 ++++++++---- 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index 98df2eda6..633c89f21 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -14,6 +14,33 @@ std::unordered_map> accounts; #define CRYPT(a, b) ((char *) crypt((a),(b))) #endif +DQuest::DQuest() + : id(0) + , count(0) + , time(0) +{ +} + + +DQuest::DQuest(int id, int count, time_t time) + : id(id) + , count(count) + , time(time) +{ +} + +LoginIndex::LoginIndex() + : count(0) + , last_login(time(0)) +{ +} + +LoginIndex::LoginIndex(int count, time_t last_login) + : count(count) + , last_login(last_login) +{ +} + std::shared_ptr Account::get_account(const std::string &email) { if (accounts.contains(email)) { return accounts[email]; @@ -21,6 +48,18 @@ std::shared_ptr Account::get_account(const std::string &email) { return nullptr; } +const std::string Account::config_parameter_daily_quest_ = "DaiQ: "; +const std::string Account::config_parameter_password_ = "Pwd: "; +const std::string Account::config_parameter_last_login_ = "ll: "; +const std::string Account::config_parameter_history_login_ = "hl: "; + +Account::Account(const std::string &email) + : email_(email) + , last_login_(0) +{ + read_from_file(); +} + // TODO логика никак не относится к аккаунту // убрать процессинг к валютам, когда они будут готовы int Account::zero_hryvn(CharData *ch, int val) { @@ -47,7 +86,7 @@ int Account::zero_hryvn(CharData *ch, int val) { } void Account::complete_quest(int id) { - for (auto &x : this->dquests) { + for (auto &x : dquests_) { if (x.id == id) { x.count += 1; x.time = time(nullptr); @@ -58,7 +97,7 @@ void Account::complete_quest(int id) { dq_tmp.id = id; dq_tmp.count = 1; dq_tmp.time = time(nullptr); - this->dquests.push_back(dq_tmp); + dquests_.push_back(dq_tmp); } std::vector Account::all_chars_in_account() const @@ -72,7 +111,7 @@ std::vector Account::all_chars_in_account() const return false; } - return email.compare(pie.mail) == 0; + return email_.compare(pie.mail) == 0; }); return result_list; @@ -82,7 +121,7 @@ void Account::show_players(CharData *ch) { int count = 1; std::stringstream ss; const auto &chars_in_account = all_chars_in_account(); - ss << "Данные аккаунта: " << this->email << "\r\n"; + ss << "Данные аккаунта: " << email_ << "\r\n"; for (auto &x : chars_in_account) { if (!x.name()) { continue; @@ -99,7 +138,7 @@ void Account::list_players(DescriptorData *d) { int count = 1; std::stringstream ss; const auto &chars_in_account = all_chars_in_account(); - ss << "Данные аккаунта: " << this->email << "\r\n"; + ss << "Данные аккаунта: " << email_ << "\r\n"; iosystem::write_to_output(ss.str().c_str(), d); for (auto &x : chars_in_account) { if (!x.name()) { @@ -117,34 +156,40 @@ void Account::list_players(DescriptorData *d) { void Account::save_to_file() { std::ofstream out; - out.open(LIB_ACCOUNTS + this->email); + out.open(LIB_ACCOUNTS + email_); if (out.is_open()) { - for (const auto &x : this->dquests) { - out << "DaiQ: " << x.id << " " << x.count << " " << x.time << "\n"; + for (const auto &x : dquests_) { + out << config_parameter_daily_quest_ << x.id << " " << x.count << " " << x.time << "\n"; } - for (const auto &x : this->history_logins) { - out << "hl: " << x.first << " " << x.second.count << " " << x.second.last_login << "\n"; + for (const auto &x : history_logins_) { + out << config_parameter_history_login_ << x.first << " " << x.second.count << " " << x.second.last_login << "\n"; } - out << "Pwd: " << this->hash_password << "\n"; - out << "ll: " << this->last_login << "\n"; + if (hash_password_.has_value()) { + // пароля на аккаунте может и не быть, поэтому проверяем перед сохранением + out << config_parameter_password_ << hash_password_.value() << "\n"; + } + out << config_parameter_last_login_ << last_login_ << "\n"; } out.close(); } void Account::read_from_file() { std::string line; - std::ifstream in(LIB_ACCOUNTS + this->email); - std::vector tmp; + std::ifstream in(LIB_ACCOUNTS + email_); if (in.is_open()) { while (getline(in, line)) { - tmp = utils::Split(line); - if (line.starts_with("DaiQ: ")) { - DQuest tmp_quest{}; - tmp_quest.id = atoi(tmp[1].c_str()); - tmp_quest.count = atoi(tmp[2].c_str()); - tmp_quest.time = atoi(tmp[3].c_str()); - this->dquests.push_back(tmp_quest); + const std::vector splitted_line = utils::Split(line); + if (line.starts_with(config_parameter_daily_quest_)) { + const auto read_id = atoi(splitted_line[1].c_str()); + const auto read_count = atoi(splitted_line[2].c_str()); + const auto read_time = atoi(splitted_line[3].c_str()); + dquests_.emplace_back(read_id, read_count, read_time); + } else if (line.starts_with(config_parameter_password_)) { + hash_password_ = line.substr(config_parameter_password_.length()); + if (hash_password_->empty()) { + hash_password_ = std::nullopt; + } } } in.close(); @@ -152,35 +197,30 @@ void Account::read_from_file() { } time_t Account::get_last_login() const { - return this->last_login; + return last_login_; } void Account::set_last_login() { - this->last_login = time(nullptr); -} - -Account::Account(const std::string &email) { - this->email = email; - this->read_from_file(); - this->hash_password = ""; - this->last_login = 0; + last_login_ = time(nullptr); } void Account::add_login(const std::string &ip_addr) { - if (this->history_logins.count(ip_addr)) { - this->history_logins[ip_addr].last_login = time(nullptr); - this->history_logins[ip_addr].count += 1; + if (history_logins_.count(ip_addr)) { + history_logins_[ip_addr].last_login = time(nullptr); + history_logins_[ip_addr].count += 1; return; } - login_index tmp{}; - tmp.last_login = time(nullptr); - tmp.count = 1; - this->history_logins.insert(std::pair(ip_addr, tmp)); + + const auto current_last_login = time(nullptr); + const int current_count = 1; + const LoginIndex ll{current_count, current_last_login}; + history_logins_.insert(std::pair(ip_addr, ll)); } + /* Показ хистори логинов */ void Account::show_history_logins(CharData *ch) { std::stringstream ss; - for (auto &x : this->history_logins) { + for (auto &x : history_logins_) { ss << "IP: " << x.first << " count: " << x.second.count << " time: " << rustime(localtime(&x.second.last_login)) @@ -190,15 +230,15 @@ void Account::show_history_logins(CharData *ch) { } void Account::set_password(const std::string &password) { - this->hash_password = Password::generate_md5_hash(password); + hash_password_ = Password::generate_md5_hash(password); } bool Account::compare_password(const std::string &password) { - return CompareParam(this->hash_password, CRYPT(password.c_str(), this->hash_password.c_str()), true); + return hash_password_.has_value() ? (hash_password_.value(), CRYPT(password.c_str(), hash_password_->c_str()), true) : false; } bool Account::quest_is_available(int id) { - for (const auto &x : this->dquests) { + for (const auto &x : dquests_) { if (x.id == id) { if (x.time != 0 && (time(0) - x.time) < 82800) { return false; diff --git a/src/administration/accounts.h b/src/administration/accounts.h index 68442844a..24bc1b703 100644 --- a/src/administration/accounts.h +++ b/src/administration/accounts.h @@ -17,13 +17,25 @@ #include #include -struct DQuest { +class DQuest { +public: + DQuest(); + DQuest(int id, int count, time_t time); + virtual ~DQuest() = default; + +public: int id; int count; time_t time; }; -struct login_index { +class LoginIndex { +public: + LoginIndex(); + LoginIndex(int count, time_t last_login); + virtual ~LoginIndex() = default; + +public: // количество заходов int count; // дата последнего захода с данного ip @@ -31,26 +43,27 @@ struct login_index { }; class Account { - private: - std::string email; - std::vector characters; - std::vector dquests; - std::string karma; +private: + const std::string email_; + std::vector dquests_; // пароль (а точнее его хеш) аккаунта - std::string hash_password; + std::optional hash_password_; // дата последнего входа в аккаунт - time_t last_login; + time_t last_login_; // История логинов, ключ - айпи, в структуре количество раз, с которых был произведен заход с данного айпи-адреса + дата, когда последний раз выходили с данного айпишника - std::unordered_map history_logins; + std::unordered_map history_logins_; - public: - Account(const std::string &name); +public: + static std::shared_ptr get_account(const std::string &email); + Account(const std::string &email); + virtual ~Account() = default; + +public: void save_to_file(); void read_from_file(); bool quest_is_available(int id); int zero_hryvn(CharData *ch, int val); void complete_quest(int id); - static std::shared_ptr get_account(const std::string &email); void show_players(CharData *ch); void list_players(DescriptorData *d); time_t get_last_login() const; @@ -62,6 +75,12 @@ class Account { private: std::vector all_chars_in_account() const; + +private: + static const std::string config_parameter_daily_quest_; + static const std::string config_parameter_password_; + static const std::string config_parameter_last_login_; + static const std::string config_parameter_history_login_; }; extern std::unordered_map> accounts; From efa8921b399929bdf7445bae5e38481c9f04e3fa Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Sat, 12 Apr 2025 20:46:19 +0300 Subject: [PATCH 5/6] Account refactoring - storage for alternative currencies --- CMakeLists.txt | 2 + src/gameplay/economics/currencies.h | 3 + src/gameplay/economics/currency_container.cpp | 131 ++++++++++++++++++ src/gameplay/economics/currency_container.h | 36 +++++ 4 files changed, 172 insertions(+) create mode 100644 src/gameplay/economics/currency_container.cpp create mode 100644 src/gameplay/economics/currency_container.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ad3e94fe2..dfac8ce5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ set(SOURCES src/engine/ui/cmd/do_spells.cpp src/engine/ui/cmd/do_skills.cpp src/gameplay/economics/currencies.cpp + src/gameplay/economics/currency_container.cpp src/gameplay/skills/spell_capable.cpp src/gameplay/skills/lightwalk.cpp src/gameplay/skills/death_rage.cpp @@ -633,6 +634,7 @@ set(HEADERS src/engine/ui/cmd/do_spells.h src/engine/ui/cmd/do_skills.h src/gameplay/economics/currencies.h + src/gameplay/economics/currency_container.h src/gameplay/skills/spell_capable.h src/gameplay/skills/lightwalk.h src/gameplay/skills/death_rage.h diff --git a/src/gameplay/economics/currencies.h b/src/gameplay/economics/currencies.h index e739d0b8e..190edf42a 100644 --- a/src/gameplay/economics/currencies.h +++ b/src/gameplay/economics/currencies.h @@ -13,6 +13,9 @@ #include "engine/structs/info_container.h" #include "utils/grammar/cases.h" +#include +#include + // Старый неймспейс со старыми идами валют // Его необходимо удалить после доделывания системы валют namespace currency { diff --git a/src/gameplay/economics/currency_container.cpp b/src/gameplay/economics/currency_container.cpp new file mode 100644 index 000000000..f003b4b78 --- /dev/null +++ b/src/gameplay/economics/currency_container.cpp @@ -0,0 +1,131 @@ +#include "currency_container.h" + +#include "engine/db/global_objects.h" +#include "gameplay/economics/currencies.h" +#include "utils/logger.h" +#include "utils/utils_string.h" + +#include + +namespace currencies { + +CurrencyContainer::CurrencyContainer(bool account_shared) + : account_shared_(account_shared) +{ +} + +void CurrencyContainer::Validate() +{ + std::erase_if(currency_storage_, [this](const auto &element) { + const bool wrong_amount = element.second <= 0; + const auto currency = FindCurrencyByVnum(element.first); + const bool wrong_account_shared = currency.has_value() ? currency->get().IsAccountShared() != account_shared_ : true; + + const bool result = wrong_amount || wrong_account_shared; + if (result) { + err_log("CurrencyContainer::Validate. Currency text_id: %s, Wrong amount: %d, Wrong account shared: %d", + currency->get().GetTextId().c_str(), wrong_amount, wrong_account_shared); + } + + return result; + }); +} + +std::optional CurrencyContainer::serialize() +{ + Validate(); + + // формат: + // currency_text_id=value currency_text_id_2=value ... + if (currency_storage_.empty()) { + return std::nullopt; + } + + std::ostringstream ss; + + for (const auto & [currency_vnum, currency_amount] : currency_storage_) { + const auto currency = FindCurrencyByVnum(currency_vnum); + if (!currency.has_value() || currency->get().GetId() == info_container::kUndefinedVnum) { + err_log("CurrencyContainer::serialize: No text_id for currency vnum: %d", currency_vnum); + continue; + } + + if (!ss.str().empty()) { + ss << " "; + } + + ss << currency->get().GetTextId() << "=" << currency_amount; + } + + return ss.str(); +} + +void CurrencyContainer::deserealize(const std::string& data) +{ + if (data.empty()) { + return; + } + + for (const auto &data_list : utils::Split(data, ' ')) { + const auto currency_data = utils::Split(data_list, '='); + if (currency_data.size() != 2) { + err_log("CurrencyContainer::deserealize: Wrong input data: %s", data_list.c_str()); + continue; + } + const std::string &in_currency_text_id = currency_data[0]; + const std::string &in_currency_amount = currency_data[1]; + + const auto currency = FindCurrencyByTextId(in_currency_text_id); + if (!currency.has_value() || currency->get().GetId() == info_container::kUndefinedVnum) { + err_log("CurrencyContainer::deserealize: No such currency: %s", data_list.c_str()); + continue; + } + + int currency_amount = 0; + try { + currency_amount = std::stoi(in_currency_amount); + } catch (const std::invalid_argument& e) { + err_log("CurrencyContainer::deserealize: exception: %s", e.what()); + currency_amount = 0; + } catch (const std::out_of_range& e) { + err_log("CurrencyContainer::deserealize: exception: %s", e.what()); + currency_amount = 0; + } + if (currency_amount == 0) { + err_log("CurrencyContainer::deserealize: Wrong currency amount: %s", data_list.c_str()); + continue; + } + + currency_storage_[currency->get().GetId()] = currency_amount; + } + + Validate(); +} + +std::optional> CurrencyContainer::FindCurrencyByVnum(Vnum vnum) +{ + if (vnum == info_container::kUndefinedVnum) { + return std::nullopt; + } + + for (const auto ¤cy : MUD::Currencies()) { + if (currency.GetId() == vnum) { + return std::cref(currency); + } + } + + return std::nullopt; +} + +std::optional> FindCurrencyByTextId(const std::string &text_id) +{ + for (const auto ¤cy : MUD::Currencies()) { + if (currency.GetTextId() == text_id) { + return std::cref(currency); + } + } + + return std::nullopt; +} + +} // namespace currencies diff --git a/src/gameplay/economics/currency_container.h b/src/gameplay/economics/currency_container.h new file mode 100644 index 000000000..f90d2fc6d --- /dev/null +++ b/src/gameplay/economics/currency_container.h @@ -0,0 +1,36 @@ +#pragma once + +#include "engine/structs/structs.h" + +#include +#include +#include + +namespace currencies { + +class CurrencyInfo; + +// класс для хранения альтернативных валют в аккаунте или инвентаре персонажа +class CurrencyContainer +{ +public: + // account_shared==true - контейнер для аккаунта + // account_shared==false - контейнер для персонажа + CurrencyContainer(bool account_shared); + +public: + std::optional serialize(); + void deserealize(const std::string &data); + +private: + void Validate(); + static std::optional> FindCurrencyByVnum(Vnum vnum); + static std::optional> FindCurrencyByTextId(const std::string &text_id); + +private: + const bool account_shared_; + // ключ - vnum валюты, значение - количество + std::map currency_storage_; +}; + +} //namespace currencies From eaa4845c719ada9f10f9dec8311e6f5b5fda6ffc Mon Sep 17 00:00:00 2001 From: vkhanayev Date: Sun, 13 Apr 2025 11:32:02 +0300 Subject: [PATCH 6/6] Account refactoring - add currency storage to accounts --- lib/cfg/economics/currencies.xml | 12 ++++---- src/administration/accounts.cpp | 25 +++++++++++++++++ src/administration/accounts.h | 5 ++++ src/engine/ui/cmd_god/do_show.cpp | 1 + src/gameplay/economics/currencies.cpp | 1 + src/gameplay/economics/currency_container.cpp | 28 ++++++++++++++++++- src/gameplay/economics/currency_container.h | 23 ++++++++++++++- 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/lib/cfg/economics/currencies.xml b/lib/cfg/economics/currencies.xml index 1f617bb85..b970f90fd 100644 --- a/lib/cfg/economics/currencies.xml +++ b/lib/cfg/economics/currencies.xml @@ -27,7 +27,7 @@ flags - флаги --> - + @@ -35,7 +35,7 @@ flags - флаги - + @@ -48,10 +48,10 @@ flags - флаги - - + + - + @@ -59,7 +59,7 @@ flags - флаги - + diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index 633c89f21..21907620e 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -52,10 +52,12 @@ const std::string Account::config_parameter_daily_quest_ = "DaiQ: "; const std::string Account::config_parameter_password_ = "Pwd: "; const std::string Account::config_parameter_last_login_ = "ll: "; const std::string Account::config_parameter_history_login_ = "hl: "; +const std::string Account::config_parameter_alt_currencies_ = "AltCurr: "; Account::Account(const std::string &email) : email_(email) , last_login_(0) + , currency_container_(true) { read_from_file(); } @@ -134,6 +136,22 @@ void Account::show_players(CharData *ch) { SendMsgToChar(ss.str(), ch); } +void Account::show_currencies(CharData *ch) { + std::stringstream ss; + ss << "Валюты на аккаунте: " << email_ << "\r\n"; + if (currency_container_.empty()) { + ss << " * Не обнаружено\r\n"; + } else { + int count = 1; + for (const auto &[currency_vnum, currrency_amount] : currency_container_) { + ss << " " << count << ") Vnum: " << currency_vnum << ", количество: " << currrency_amount << "\r\n"; + ++count; + } + } + + SendMsgToChar(ss.str(), ch); +} + void Account::list_players(DescriptorData *d) { int count = 1; std::stringstream ss; @@ -169,6 +187,11 @@ void Account::save_to_file() { out << config_parameter_password_ << hash_password_.value() << "\n"; } out << config_parameter_last_login_ << last_login_ << "\n"; + + const auto serialized_currencies = currency_container_.serialize(); + if (serialized_currencies.has_value()) { + out << config_parameter_alt_currencies_ << serialized_currencies.value(); + } } out.close(); } @@ -190,6 +213,8 @@ void Account::read_from_file() { if (hash_password_->empty()) { hash_password_ = std::nullopt; } + } else if (line.starts_with(config_parameter_alt_currencies_)) { + currency_container_.deserealize(line.substr(config_parameter_alt_currencies_.length())); } } in.close(); diff --git a/src/administration/accounts.h b/src/administration/accounts.h index 24bc1b703..602cdb1a1 100644 --- a/src/administration/accounts.h +++ b/src/administration/accounts.h @@ -10,6 +10,7 @@ #include "engine/entities/char_data.h" #include "engine/network/descriptor_data.h" #include "engine/db/global_objects.h" +#include "gameplay/economics/currency_container.h" #include #include @@ -52,6 +53,8 @@ class Account { time_t last_login_; // История логинов, ключ - айпи, в структуре количество раз, с которых был произведен заход с данного айпи-адреса + дата, когда последний раз выходили с данного айпишника std::unordered_map history_logins_; + // хранилище альтернативных валют на аккаунте + currencies::CurrencyContainer currency_container_; public: static std::shared_ptr get_account(const std::string &email); @@ -65,6 +68,7 @@ class Account { int zero_hryvn(CharData *ch, int val); void complete_quest(int id); void show_players(CharData *ch); + void show_currencies(CharData *ch); void list_players(DescriptorData *d); time_t get_last_login() const; void set_last_login(); @@ -81,6 +85,7 @@ class Account { static const std::string config_parameter_password_; static const std::string config_parameter_last_login_; static const std::string config_parameter_history_login_; + static const std::string config_parameter_alt_currencies_; }; extern std::unordered_map> accounts; diff --git a/src/engine/ui/cmd_god/do_show.cpp b/src/engine/ui/cmd_god/do_show.cpp index beb5e14a1..1ffbbb2ca 100644 --- a/src/engine/ui/cmd_god/do_show.cpp +++ b/src/engine/ui/cmd_god/do_show.cpp @@ -895,6 +895,7 @@ void do_show(CharData *ch, char *argument, int/* cmd*/, int/* subcmd*/) { } chdata->get_account()->show_players(ch); chdata->get_account()->show_history_logins(ch); + chdata->get_account()->show_currencies(ch); break; } case 36: // shops diff --git a/src/gameplay/economics/currencies.cpp b/src/gameplay/economics/currencies.cpp index 0acd8a520..1eab0050c 100644 --- a/src/gameplay/economics/currencies.cpp +++ b/src/gameplay/economics/currencies.cpp @@ -93,6 +93,7 @@ void CurrencyInfo::Print(CharData */*ch*/, std::ostringstream &buffer) const { << " Can be stored in bank: " << kColorGrn << (bankable_ ? "Y" : "N") << kColorNrm << "\r\n" << " Can be transfered: " << kColorGrn << (transferable_ ? "Y" : "N") << kColorNrm << "\r\n" << " Can be transfered to other account: " << kColorGrn << (transferable_to_other_ ? "Y" : "N") << kColorNrm << "\r\n" + << " Account shared: " << kColorGrn << (account_shared_ ? "Y" : "N") << kColorNrm << "\r\n" << " Transfer tax: " << kColorGrn << transfer_tax_ << kColorNrm << "\r\n" << " Drop on death: " << kColorGrn << drop_on_death_ << kColorNrm << "\r\n" << " Max clan tax: " << kColorGrn << max_clan_tax_ << kColorNrm << "\r\n" diff --git a/src/gameplay/economics/currency_container.cpp b/src/gameplay/economics/currency_container.cpp index f003b4b78..c430a010d 100644 --- a/src/gameplay/economics/currency_container.cpp +++ b/src/gameplay/economics/currency_container.cpp @@ -14,6 +14,32 @@ CurrencyContainer::CurrencyContainer(bool account_shared) { } +void CurrencyContainer::SetCurrencyAmount(Vnum currency_vnum, int value) +{ + currency_storage_[currency_vnum] = std::clamp(value, 0, currency_amount_cap_); + Validate(); +} + +void CurrencyContainer::ModifyCurrencyAmount(Vnum currency_vnum, int value) +{ + if (!currency_storage_.contains(currency_vnum)) { + currency_storage_[currency_vnum] = value; + } else { + currency_storage_[currency_vnum] += value; + } + currency_storage_[currency_vnum] = std::clamp(currency_storage_[currency_vnum], 0, currency_amount_cap_); + Validate(); +} + +std::optional CurrencyContainer::GetCurrencyAmount(Vnum currency_vnum) const +{ + if (!currency_storage_.contains(currency_vnum)) { + return std::nullopt; + } + + return currency_storage_.at(currency_vnum); +} + void CurrencyContainer::Validate() { std::erase_if(currency_storage_, [this](const auto &element) { @@ -117,7 +143,7 @@ std::optional> CurrencyContainer::Fin return std::nullopt; } -std::optional> FindCurrencyByTextId(const std::string &text_id) +std::optional> CurrencyContainer::FindCurrencyByTextId(const std::string &text_id) { for (const auto ¤cy : MUD::Currencies()) { if (currency.GetTextId() == text_id) { diff --git a/src/gameplay/economics/currency_container.h b/src/gameplay/economics/currency_container.h index f90d2fc6d..a3ca44dbb 100644 --- a/src/gameplay/economics/currency_container.h +++ b/src/gameplay/economics/currency_container.h @@ -18,7 +18,26 @@ class CurrencyContainer // account_shared==false - контейнер для персонажа CurrencyContainer(bool account_shared); + using StorageType = std::map; + /* + * begin()/end() возвращают константные итераторы + * для исключения возможности модификации хранилища в обход сеттеров + */ + StorageType::const_iterator begin() const {return currency_storage_.cbegin(); } + StorageType::const_iterator end() const { return currency_storage_.cend(); } + bool empty() const { return currency_storage_.empty(); } + public: + // при установке значения в 0 или меньше - валюта удалится + void SetCurrencyAmount(Vnum currency_vnum, int value); + // value может быть отрицательным, если результат будет 0 или меньше - валюта удалится + void ModifyCurrencyAmount(Vnum currency_vnum, int value); + std::optional GetCurrencyAmount(Vnum currency_vnum) const; + + /* + * Сохранение/чтение в/из строки для записи в файл. + * Для надежности используется text_id валюты вместо внума. + */ std::optional serialize(); void deserealize(const std::string &data); @@ -30,7 +49,9 @@ class CurrencyContainer private: const bool account_shared_; // ключ - vnum валюты, значение - количество - std::map currency_storage_; + StorageType currency_storage_; + // кап потом вынести в конфиг каждой валюты. пока его нет, установить хоть какой-то тут + const int currency_amount_cap_ = 1000000; }; } //namespace currencies