576 lines
19 KiB
C++
576 lines
19 KiB
C++
#include "TeamIndex.hh"
|
|
|
|
#include <filesystem>
|
|
#include <phosg/Filesystem.hh>
|
|
#include <phosg/Image.hh>
|
|
#include <phosg/Random.hh>
|
|
|
|
#include "BattleParamsIndex.hh"
|
|
#include "ImageEncoder.hh"
|
|
#include "ItemData.hh"
|
|
#include "Loggers.hh"
|
|
#include "StaticGameData.hh"
|
|
|
|
TeamIndex::Team::Member::Member(const phosg::JSON& json)
|
|
: flags(json.get_int("Flags", 0)), points(json.get_int("Points", 0)), name(json.get_string("Name", "")) {
|
|
try {
|
|
this->account_id = json.get_int("AccountID");
|
|
} catch (const std::out_of_range&) {
|
|
// Old format
|
|
this->account_id = json.get_int("SerialNumber");
|
|
}
|
|
}
|
|
|
|
phosg::JSON TeamIndex::Team::Member::json() const {
|
|
return phosg::JSON::dict(
|
|
{{"AccountID", this->account_id}, {"Flags", this->flags}, {"Points", this->points}, {"Name", this->name}});
|
|
}
|
|
|
|
uint32_t TeamIndex::Team::Member::privilege_level() const {
|
|
if (this->check_flag(Member::Flag::IS_MASTER)) {
|
|
return 0x40;
|
|
} else if (this->check_flag(Member::Flag::IS_LEADER)) {
|
|
return 0x30;
|
|
} else {
|
|
return 0x00;
|
|
}
|
|
}
|
|
|
|
TeamIndex::Team::Team(uint32_t team_id) : Team() {
|
|
this->team_id = team_id;
|
|
}
|
|
|
|
std::string TeamIndex::Team::json_filename() const {
|
|
return std::format("system/teams/{:08X}.json", this->team_id);
|
|
}
|
|
|
|
std::string TeamIndex::Team::flag_filename() const {
|
|
return std::format("system/teams/{:08X}.bmp", this->team_id);
|
|
}
|
|
|
|
void TeamIndex::Team::load_config() {
|
|
auto json = phosg::JSON::parse(phosg::load_file(this->json_filename()));
|
|
this->name = json.get_string("Name");
|
|
this->spent_points = json.get_int("SpentPoints");
|
|
this->points = 0;
|
|
for (const auto& member_it : json.get_list("Members")) {
|
|
Member m(*member_it);
|
|
this->points += m.points;
|
|
uint32_t account_id = m.account_id;
|
|
if (m.check_flag(Member::Flag::IS_MASTER)) {
|
|
this->master_account_id = account_id;
|
|
}
|
|
this->members.emplace(account_id, std::move(m));
|
|
}
|
|
try {
|
|
for (const auto& it : json.get_list("RewardKeys")) {
|
|
this->reward_keys.emplace(it->as_string());
|
|
}
|
|
} catch (const std::out_of_range&) {
|
|
}
|
|
this->reward_flags = json.get_int("RewardFlags");
|
|
}
|
|
|
|
phosg::JSON TeamIndex::Team::json() const {
|
|
phosg::JSON members_json = phosg::JSON::list();
|
|
for (const auto& it : this->members) {
|
|
members_json.emplace_back(it.second.json());
|
|
}
|
|
phosg::JSON reward_keys_json = phosg::JSON::list();
|
|
for (const auto& it : this->reward_keys) {
|
|
reward_keys_json.emplace_back(it);
|
|
}
|
|
return phosg::JSON::dict({
|
|
{"Name", this->name},
|
|
{"SpentPoints", this->spent_points},
|
|
{"Members", std::move(members_json)},
|
|
{"RewardKeys", std::move(reward_keys_json)},
|
|
{"RewardFlags", this->reward_flags},
|
|
});
|
|
}
|
|
|
|
void TeamIndex::Team::save_config() const {
|
|
phosg::save_file(this->json_filename(), this->json().serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
|
}
|
|
|
|
void TeamIndex::Team::load_flag() {
|
|
auto img = phosg::ImageRGBA8888N::from_file_data(phosg::load_file(this->flag_filename()));
|
|
if (img.get_width() != 32 || img.get_height() != 32) {
|
|
throw std::runtime_error("incorrect flag image dimensions");
|
|
}
|
|
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
|
|
for (size_t y = 0; y < 32; y++) {
|
|
for (size_t x = 0; x < 32; x++) {
|
|
this->flag_data->at(y * 0x20 + x) = phosg::argb1555_for_rgba8888(img.read(x, y));
|
|
}
|
|
}
|
|
}
|
|
|
|
phosg::ImageRGBA8888N TeamIndex::Team::decode_flag_data() const {
|
|
phosg::ImageRGBA8888N img(32, 32);
|
|
for (size_t y = 0; y < 32; y++) {
|
|
for (size_t x = 0; x < 32; x++) {
|
|
img.write(x, y, phosg::rgba8888_for_argb1555(this->flag_data->at(y * 0x20 + x)));
|
|
}
|
|
}
|
|
return img;
|
|
}
|
|
|
|
void TeamIndex::Team::save_flag() const {
|
|
if (!this->flag_data) {
|
|
return;
|
|
}
|
|
auto img = this->decode_flag_data();
|
|
phosg::save_file(this->flag_filename(), img.serialize(phosg::ImageFormat::WINDOWS_BITMAP));
|
|
}
|
|
|
|
void TeamIndex::Team::delete_files() const {
|
|
std::filesystem::remove(this->json_filename());
|
|
std::filesystem::remove(this->flag_filename());
|
|
}
|
|
|
|
PSOBBBaseTeamMembership TeamIndex::Team::base_membership_for_member(uint32_t account_id) const {
|
|
const auto& m = this->members.at(account_id);
|
|
|
|
PSOBBBaseTeamMembership ret;
|
|
ret.team_master_guild_card_number = this->master_account_id;
|
|
ret.team_id = this->team_id;
|
|
ret.unknown_a5 = 0;
|
|
ret.unknown_a6 = 0;
|
|
ret.privilege_level = m.privilege_level();
|
|
ret.team_member_count = this->members.size();
|
|
ret.unknown_a8 = 0;
|
|
ret.unknown_a9 = 0;
|
|
ret.team_name.encode(this->name);
|
|
return ret;
|
|
}
|
|
|
|
PSOBBFullTeamMembership TeamIndex::Team::full_membership_for_member(uint32_t account_id) const {
|
|
PSOBBFullTeamMembership ret;
|
|
ret.base = base_membership_for_member(account_id);
|
|
if (this->flag_data) {
|
|
ret.flag_data = *this->flag_data;
|
|
} else {
|
|
ret.flag_data.clear();
|
|
}
|
|
ret.reward_flags = this->reward_flags;
|
|
return ret;
|
|
}
|
|
|
|
bool TeamIndex::Team::has_reward(const std::string& key) const {
|
|
return this->reward_keys.count(key);
|
|
}
|
|
|
|
size_t TeamIndex::Team::num_members() const {
|
|
return this->members.size();
|
|
}
|
|
|
|
size_t TeamIndex::Team::num_leaders() const {
|
|
size_t count = 0;
|
|
for (const auto& it : this->members) {
|
|
if (it.second.check_flag(Member::Flag::IS_LEADER)) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
size_t TeamIndex::Team::max_members() const {
|
|
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
|
return 100;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
|
return 70;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
|
return 40;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
|
return 20;
|
|
} else {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
size_t TeamIndex::Team::max_leaders() const {
|
|
if (this->check_reward_flag(RewardFlag::MEMBERS_100_LEADERS_10)) {
|
|
return 10;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_70_LEADERS_8)) {
|
|
return 8;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_40_LEADERS_5)) {
|
|
return 5;
|
|
} else if (this->check_reward_flag(RewardFlag::MEMBERS_20_LEADERS_3)) {
|
|
return 3;
|
|
} else {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
bool TeamIndex::Team::can_add_member() const {
|
|
return this->num_members() < this->max_members();
|
|
}
|
|
|
|
bool TeamIndex::Team::can_promote_leader() const {
|
|
return this->num_leaders() < this->max_leaders();
|
|
}
|
|
|
|
TeamIndex::Reward::Reward(uint32_t menu_item_id, const phosg::JSON& def_json)
|
|
: menu_item_id(menu_item_id),
|
|
key(def_json.get_string("Key")),
|
|
name(def_json.get_string("Name")),
|
|
description(def_json.get_string("Description")),
|
|
is_unique(def_json.get_bool("IsUnique", true)),
|
|
team_points(def_json.get_int("Points")) {
|
|
try {
|
|
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
|
|
this->prerequisite_keys.emplace(it->as_string());
|
|
}
|
|
} catch (const std::out_of_range&) {
|
|
}
|
|
try {
|
|
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
|
|
} catch (const std::out_of_range&) {
|
|
}
|
|
try {
|
|
this->reward_item = ItemData::from_data(phosg::parse_data_string(def_json.get_string("RewardItem")));
|
|
} catch (const std::out_of_range&) {
|
|
}
|
|
}
|
|
|
|
TeamIndex::TeamIndex(const std::string& directory, const phosg::JSON& reward_defs_json)
|
|
: directory(directory), next_team_id(1) {
|
|
uint32_t reward_menu_item_id = 0;
|
|
for (const auto& it : reward_defs_json.as_list()) {
|
|
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
|
|
}
|
|
|
|
if (!std::filesystem::is_directory(this->directory)) {
|
|
std::filesystem::create_directories(this->directory.c_str());
|
|
return;
|
|
}
|
|
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
|
std::string filename = item.path().filename().string();
|
|
std::string file_path = this->directory + "/" + filename;
|
|
if (filename == "base.json") {
|
|
auto json = phosg::JSON::parse(phosg::load_file(file_path));
|
|
this->next_team_id = json.get_int("NextTeamID");
|
|
} else if (filename.ends_with(".json")) {
|
|
try {
|
|
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
|
|
auto team = std::make_shared<Team>(team_id);
|
|
team->load_config();
|
|
try {
|
|
team->load_flag();
|
|
} catch (const std::exception& e) {
|
|
static_game_data_log.warning_f("Failed to load flag for team {:08X}: {}", team_id, e.what());
|
|
}
|
|
this->add_to_indexes(team);
|
|
static_game_data_log.info_f("Indexed team {:08X} ({}) ({} members)", team_id, team->name, team->num_members());
|
|
} catch (const std::exception& e) {
|
|
static_game_data_log.warning_f("Failed to index team from {}: {}", filename, e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t TeamIndex::count() const {
|
|
return this->id_to_team.size();
|
|
}
|
|
|
|
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
|
try {
|
|
return this->id_to_team.at(team_id);
|
|
} catch (const std::out_of_range&) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const std::string& name) const {
|
|
try {
|
|
return this->name_to_team.at(name);
|
|
} catch (const std::out_of_range&) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
|
|
try {
|
|
return this->account_id_to_team.at(account_id);
|
|
} catch (const std::out_of_range&) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::vector<std::shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
|
std::vector<std::shared_ptr<const Team>> ret;
|
|
for (const auto& it : this->id_to_team) {
|
|
ret.emplace_back(it.second);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::shared_ptr<const TeamIndex::Team> TeamIndex::create(
|
|
const std::string& name, uint32_t master_account_id, const std::string& master_name) {
|
|
auto team = std::make_shared<Team>(this->next_team_id++);
|
|
phosg::save_file(this->directory + "/base.json", phosg::JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
|
|
|
|
Team::Member m;
|
|
m.account_id = master_account_id;
|
|
m.flags = 0;
|
|
m.points = 0;
|
|
m.name = master_name;
|
|
m.set_flag(Team::Member::Flag::IS_MASTER);
|
|
team->members.emplace(master_account_id, std::move(m));
|
|
team->name = name;
|
|
|
|
team->save_config();
|
|
this->add_to_indexes(team);
|
|
return team;
|
|
}
|
|
|
|
void TeamIndex::disband(uint32_t team_id) {
|
|
auto team = this->id_to_team.at(team_id);
|
|
this->remove_from_indexes(team);
|
|
team->delete_files();
|
|
}
|
|
|
|
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
|
|
auto team = this->id_to_team.at(team_id);
|
|
if (!this->name_to_team.emplace(new_team_name, team).second) {
|
|
throw std::runtime_error("team name is already in use");
|
|
}
|
|
this->name_to_team.erase(team->name);
|
|
team->name = new_team_name;
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const std::string& name) {
|
|
auto team = this->id_to_team.at(team_id);
|
|
if (!this->account_id_to_team.emplace(account_id, team).second) {
|
|
throw std::runtime_error("user is already in a different team");
|
|
}
|
|
|
|
Team::Member m;
|
|
m.account_id = account_id;
|
|
m.flags = 0;
|
|
m.points = 0;
|
|
m.name = name;
|
|
team->members.emplace(account_id, std::move(m));
|
|
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::remove_member(uint32_t account_id) {
|
|
auto team_it = this->account_id_to_team.find(account_id);
|
|
if (team_it == this->account_id_to_team.end()) {
|
|
throw std::runtime_error("client is not in any team");
|
|
}
|
|
auto team = std::move(team_it->second);
|
|
this->account_id_to_team.erase(team_it);
|
|
team->members.erase(account_id);
|
|
if (team->members.empty()) {
|
|
this->disband(team->team_id);
|
|
} else {
|
|
team->save_config();
|
|
}
|
|
}
|
|
|
|
void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) {
|
|
auto team = this->account_id_to_team.at(account_id);
|
|
auto& m = team->members.at(account_id);
|
|
m.name = name;
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) {
|
|
auto team = this->account_id_to_team.at(account_id);
|
|
auto& m = team->members.at(account_id);
|
|
m.points += points;
|
|
team->points += points;
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
|
|
auto team = this->id_to_team.at(team_id);
|
|
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
|
|
team->save_flag();
|
|
}
|
|
|
|
bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
|
auto team = this->account_id_to_team.at(master_account_id);
|
|
auto& master_m = team->members.at(master_account_id);
|
|
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
|
throw std::runtime_error("incorrect master account ID");
|
|
}
|
|
auto& other_m = team->members.at(leader_account_id);
|
|
|
|
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
|
|
return false;
|
|
}
|
|
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
|
team->save_config();
|
|
return true;
|
|
}
|
|
|
|
bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) {
|
|
auto team = this->account_id_to_team.at(master_account_id);
|
|
auto& master_m = team->members.at(master_account_id);
|
|
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
|
throw std::runtime_error("incorrect master account ID");
|
|
}
|
|
auto& other_m = team->members.at(leader_account_id);
|
|
|
|
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
|
|
return false;
|
|
}
|
|
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
|
team->save_config();
|
|
return true;
|
|
}
|
|
|
|
void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) {
|
|
auto team = this->account_id_to_team.at(master_account_id);
|
|
auto& master_m = team->members.at(master_account_id);
|
|
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
|
throw std::runtime_error("incorrect master account ID");
|
|
}
|
|
auto& new_master_m = team->members.at(new_master_account_id);
|
|
|
|
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
|
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
|
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
|
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
|
team->master_account_id = new_master_account_id;
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
|
auto team = this->id_to_team.at(team_id);
|
|
if (team->spent_points + points > team->points) {
|
|
throw std::runtime_error("not enough points available");
|
|
}
|
|
team->reward_keys.emplace(key);
|
|
team->spent_points += points;
|
|
if (reward_flag != Team::RewardFlag::NONE) {
|
|
team->set_reward_flag(reward_flag);
|
|
}
|
|
team->save_config();
|
|
}
|
|
|
|
void TeamIndex::replace_all_from_authority(const phosg::JSON& canonical_team_state) {
|
|
uint32_t new_next_team_id = canonical_team_state.get_int("next_team_id", 1);
|
|
if (new_next_team_id < 1) {
|
|
new_next_team_id = 1;
|
|
}
|
|
|
|
std::unordered_map<uint32_t, std::shared_ptr<Team>> new_id_to_team;
|
|
std::unordered_map<std::string, std::shared_ptr<Team>> new_name_to_team;
|
|
std::unordered_map<uint32_t, std::shared_ptr<Team>> new_account_id_to_team;
|
|
|
|
const auto& teams_json = canonical_team_state.get("teams", phosg::JSON::list()).as_list();
|
|
for (const auto& team_json_p : teams_json) {
|
|
const auto& team_json = *team_json_p;
|
|
uint32_t team_id = team_json.get_int("team_id");
|
|
if (team_id == 0) {
|
|
throw std::runtime_error("authority team has invalid team_id");
|
|
}
|
|
|
|
auto team = std::make_shared<Team>(team_id);
|
|
team->name = team_json.get_string("name", "");
|
|
if (team->name.empty()) {
|
|
throw std::runtime_error("authority team has empty name");
|
|
}
|
|
|
|
team->reward_flags = team_json.get_int("reward_flags", 0);
|
|
team->spent_points = team_json.get_int("spent_points", 0);
|
|
team->points = 0;
|
|
|
|
team->reward_keys.clear();
|
|
for (const auto& key_json_p : team_json.get("reward_keys", phosg::JSON::list()).as_list()) {
|
|
team->reward_keys.emplace(key_json_p->as_string());
|
|
}
|
|
|
|
const auto& members_json = team_json.get("members", phosg::JSON::list()).as_list();
|
|
for (const auto& member_json_p : members_json) {
|
|
const auto& member_json = *member_json_p;
|
|
Team::Member m;
|
|
m.account_id = member_json.get_int("account_id", member_json.get_int("AccountID", 0));
|
|
m.flags = member_json.get_int("flags", member_json.get_int("Flags", 0));
|
|
m.points = member_json.get_int("points", member_json.get_int("Points", 0));
|
|
m.name = member_json.get_string("name", member_json.get_string("Name", ""));
|
|
|
|
if (m.account_id == 0) {
|
|
throw std::runtime_error("authority team member has invalid account_id");
|
|
}
|
|
if (m.check_flag(Team::Member::Flag::IS_MASTER)) {
|
|
team->master_account_id = m.account_id;
|
|
}
|
|
team->points += m.points;
|
|
team->members.emplace(m.account_id, std::move(m));
|
|
}
|
|
|
|
if (team->members.empty()) {
|
|
throw std::runtime_error("authority team has no members");
|
|
}
|
|
|
|
if (!new_id_to_team.emplace(team->team_id, team).second) {
|
|
throw std::runtime_error("authority state has duplicate team_id");
|
|
}
|
|
if (!new_name_to_team.emplace(team->name, team).second) {
|
|
throw std::runtime_error("authority state has duplicate team name");
|
|
}
|
|
for (const auto& [account_id, member] : team->members) {
|
|
if (!new_account_id_to_team.emplace(account_id, team).second) {
|
|
throw std::runtime_error("authority state has account in multiple teams");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::filesystem::create_directories(this->directory.c_str());
|
|
|
|
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
|
const std::string filename = item.path().filename().string();
|
|
if ((filename != "base.json") && (filename.ends_with(".json") || filename.ends_with(".bmp"))) {
|
|
std::filesystem::remove(item.path());
|
|
}
|
|
}
|
|
|
|
phosg::save_file(
|
|
this->directory + "/base.json",
|
|
phosg::JSON::dict({{"NextTeamID", new_next_team_id}}).serialize(
|
|
phosg::JSON::SerializeOption::FORMAT |
|
|
phosg::JSON::SerializeOption::HEX_INTEGERS |
|
|
phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
|
|
|
this->next_team_id = new_next_team_id;
|
|
this->id_to_team.swap(new_id_to_team);
|
|
this->name_to_team.swap(new_name_to_team);
|
|
this->account_id_to_team.swap(new_account_id_to_team);
|
|
|
|
for (const auto& [team_id, team] : this->id_to_team) {
|
|
team->save_config();
|
|
}
|
|
}
|
|
|
|
|
|
void TeamIndex::add_to_indexes(std::shared_ptr<Team> team) {
|
|
if (!this->id_to_team.emplace(team->team_id, team).second) {
|
|
throw std::runtime_error("team ID is already in use");
|
|
}
|
|
if (!this->name_to_team.emplace(team->name, team).second) {
|
|
this->id_to_team.erase(team->team_id);
|
|
throw std::runtime_error("team name is already in use");
|
|
}
|
|
for (const auto& [_, member] : team->members) {
|
|
if (!this->account_id_to_team.emplace(member.account_id, team).second) {
|
|
static_game_data_log.warning_f("Serial number {:08X} ({:010}) exists in multiple teams",
|
|
member.account_id, member.account_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TeamIndex::remove_from_indexes(std::shared_ptr<Team> team) {
|
|
this->id_to_team.erase(team->team_id);
|
|
this->name_to_team.erase(team->name);
|
|
for (const auto& it : team->members) {
|
|
this->account_id_to_team.erase(it.second.account_id);
|
|
}
|
|
}
|