Merge pull request #36 from incentivebeats/feature/team-sync-phase3-team-management
Add TeamSync team management events
This commit is contained in:
+73
-14
@@ -6262,12 +6262,17 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
|
||||
case 0x0FEA: { // Set team flag
|
||||
auto team = c->team();
|
||||
if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
const auto& cmd = check_size_t<C_SetTeamFlag_BB_0FEA>(msg.data);
|
||||
if (TeamSync::relay_team_actions_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Do not mutate local-only team
|
||||
// metadata that would be erased by the next authority apply.
|
||||
if (TeamSync::enqueue_team_flag_update(team->team_id, &cmd.flag_data, sizeof(cmd.flag_data)) &&
|
||||
co_await TeamSync::exchange_once_now()) {
|
||||
auto refreshed_team = s->team_index->get_by_id(team->team_id);
|
||||
if (refreshed_team) {
|
||||
send_team_metadata_change_notifications(s, refreshed_team, 0, TeamMetadataChange::FLAG_DATA);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
const auto& cmd = check_size_t<C_SetTeamFlag_BB_0FEA>(msg.data);
|
||||
s->team_index->set_flag_data(team->team_id, cmd.flag_data);
|
||||
send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::FLAG_DATA);
|
||||
}
|
||||
@@ -6277,9 +6282,12 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
|
||||
auto team = c->team();
|
||||
if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
if (TeamSync::relay_team_actions_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Disband is not yet routed
|
||||
// through the authority.
|
||||
send_command(c, 0x10EA, 0x00000001);
|
||||
if (TeamSync::enqueue_team_disband(team->team_id) && co_await TeamSync::exchange_once_now()) {
|
||||
send_command(c, 0x10EA, 0x00000000);
|
||||
send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::TEAM_DISBANDED);
|
||||
} else {
|
||||
send_command(c, 0x10EA, 0x00000001);
|
||||
}
|
||||
break;
|
||||
}
|
||||
s->team_index->disband(team->team_id);
|
||||
@@ -6297,9 +6305,39 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
|
||||
if (TeamSync::relay_team_actions_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Privilege/master changes are
|
||||
// not yet routed through the authority.
|
||||
send_command(c, 0x11EA, 0x00000005);
|
||||
if (!team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
send_command(c, 0x11EA, 0x00000005);
|
||||
break;
|
||||
}
|
||||
|
||||
bool enqueued = false;
|
||||
switch (msg.flag) {
|
||||
case 0x00:
|
||||
enqueued = TeamSync::enqueue_team_member_flags_update(cmd.guild_card_number, 0);
|
||||
break;
|
||||
case 0x30:
|
||||
enqueued = TeamSync::enqueue_team_member_flags_update(cmd.guild_card_number, 0x02);
|
||||
break;
|
||||
case 0x40:
|
||||
enqueued = TeamSync::enqueue_team_master_transfer(c->login->account->account_id, cmd.guild_card_number);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("invalid privilege level");
|
||||
}
|
||||
|
||||
if (enqueued && co_await TeamSync::exchange_once_now()) {
|
||||
send_command(c, 0x11EA, 0x00000000);
|
||||
auto refreshed_team = s->team_index->get_by_id(team->team_id);
|
||||
if (refreshed_team) {
|
||||
send_team_metadata_change_notifications(
|
||||
s,
|
||||
refreshed_team,
|
||||
cmd.guild_card_number,
|
||||
(msg.flag == 0x40) ? TeamMetadataChange::TEAM_MASTER : static_cast<TeamMetadataChange>(0));
|
||||
}
|
||||
} else {
|
||||
send_command(c, 0x11EA, 0x00000005);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6361,8 +6399,23 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
|
||||
if (TeamSync::relay_team_actions_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Rewards/points are not yet
|
||||
// routed through the authority.
|
||||
if (!TeamSync::enqueue_team_reward_purchase(
|
||||
team->team_id,
|
||||
reward.key,
|
||||
reward.team_points,
|
||||
static_cast<uint32_t>(reward.reward_flag)) ||
|
||||
!co_await TeamSync::exchange_once_now()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto refreshed_team = s->team_index->get_by_id(team->team_id);
|
||||
if (reward.reward_flag != TeamIndex::Team::RewardFlag::NONE && refreshed_team) {
|
||||
send_team_metadata_change_notifications(s, refreshed_team, 0, TeamMetadataChange::REWARD_FLAGS);
|
||||
}
|
||||
if (!reward.reward_item.empty()) {
|
||||
c->bank_file()->add_item(reward.reward_item, *s->item_stack_limits(c->version()));
|
||||
c->print_bank();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6391,9 +6444,15 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
|
||||
} else if (s->team_index->get_by_name(new_team_name)) {
|
||||
send_command(c, 0x1FEA, 0x00000002);
|
||||
} else if (TeamSync::relay_team_actions_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Rename is not yet routed
|
||||
// through the authority.
|
||||
send_command(c, 0x1FEA, 0x00000001);
|
||||
if (TeamSync::enqueue_team_rename(team->team_id, new_team_name) && co_await TeamSync::exchange_once_now()) {
|
||||
send_command(c, 0x1FEA, 0x00000000);
|
||||
auto refreshed_team = s->team_index->get_by_id(team->team_id);
|
||||
if (refreshed_team) {
|
||||
send_team_metadata_change_notifications(s, refreshed_team, 0, TeamMetadataChange::TEAM_NAME);
|
||||
}
|
||||
} else {
|
||||
send_command(c, 0x1FEA, 0x00000001);
|
||||
}
|
||||
} else {
|
||||
s->team_index->rename(team->team_id, new_team_name);
|
||||
send_command(c, 0x1FEA, 0x00000000);
|
||||
|
||||
@@ -4787,8 +4787,8 @@ static void on_exchange_item_for_team_points_bb(std::shared_ptr<Client> c, Subco
|
||||
|
||||
auto s = c->require_server_state();
|
||||
if (TeamSync::relay_team_actions_enabled() && !TeamSync::relay_team_points_enabled()) {
|
||||
// TeamSync phase 1 is membership-only. Team points are not yet routed
|
||||
// through the authority, so do not consume the item locally.
|
||||
// TeamSync points relay is disabled, so do not consume the item locally
|
||||
// for a local-only point mutation that authority sync may overwrite.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -1664,6 +1664,8 @@ void ServerState::load_teams() {
|
||||
std::string canonical_signature;
|
||||
canonical_signature += "next_team_id=" +
|
||||
std::to_string(canonical_team_state.get_int("next_team_id", canonical_team_state.get_int("NextTeamID", 0))) + "\n";
|
||||
bool empty_is_authoritative = canonical_team_state.get_bool("empty_is_authoritative", false);
|
||||
canonical_signature += "empty_is_authoritative=" + std::to_string(empty_is_authoritative ? 1 : 0) + "\n";
|
||||
canonical_signature += "teams=" + std::to_string(teams_json.size()) + "\n";
|
||||
|
||||
std::unordered_map<uint32_t, uint32_t> account_id_to_team_id;
|
||||
@@ -1675,6 +1677,13 @@ void ServerState::load_teams() {
|
||||
}
|
||||
|
||||
canonical_signature += "team=" + std::to_string(team_id) + "\n";
|
||||
canonical_signature += "name=" + team_json.get_string("name", team_json.get_string("Name", "")) + "\n";
|
||||
canonical_signature += "reward_flags=" + std::to_string(team_json.get_int("reward_flags", team_json.get_int("RewardFlags", 0))) + "\n";
|
||||
canonical_signature += "spent_points=" + std::to_string(team_json.get_int("spent_points", team_json.get_int("SpentPoints", 0))) + "\n";
|
||||
canonical_signature += "flag_data_hex=" + team_json.get_string("flag_data_hex", "") + "\n";
|
||||
for (const auto& key_json_p : team_json.get("reward_keys", phosg::JSON::list()).as_list()) {
|
||||
canonical_signature += "reward_key=" + key_json_p->as_string() + "\n";
|
||||
}
|
||||
|
||||
const auto& members_json = team_json.get("members", phosg::JSON::list()).as_list();
|
||||
for (const auto& member_json_p : members_json) {
|
||||
@@ -1682,10 +1691,11 @@ void ServerState::load_teams() {
|
||||
uint32_t account_id = member_json.get_int("account_id", member_json.get_int("AccountID", 0));
|
||||
uint32_t flags = member_json.get_int("flags", member_json.get_int("Flags", 0));
|
||||
uint32_t points = member_json.get_int("points", member_json.get_int("Points", 0));
|
||||
std::string name = member_json.get_string("name", member_json.get_string("Name", ""));
|
||||
if (account_id) {
|
||||
account_id_to_team_id.emplace(account_id, team_id);
|
||||
canonical_signature += "member=" + std::to_string(account_id) + ":" +
|
||||
std::to_string(flags) + ":" + std::to_string(points) + "\n";
|
||||
std::to_string(flags) + ":" + std::to_string(points) + ":" + name + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1700,7 +1710,7 @@ void ServerState::load_teams() {
|
||||
}
|
||||
|
||||
if (teams_json.empty()) {
|
||||
if (local_team_count || local_account_team_refs) {
|
||||
if (!empty_is_authoritative && (local_team_count || local_account_team_refs)) {
|
||||
std::string rejection_signature = canonical_signature +
|
||||
"local_team_count=" + std::to_string(local_team_count) +
|
||||
";local_account_team_refs=" + std::to_string(local_account_team_refs);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "TeamIndex.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Image.hh>
|
||||
@@ -11,6 +12,27 @@
|
||||
#include "Loggers.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
static uint8_t team_sync_hex_value(char ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + ch - 'a';
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + ch - 'A';
|
||||
}
|
||||
throw std::runtime_error("invalid hex character");
|
||||
}
|
||||
|
||||
static void team_sync_decode_hex_to_bytes(const std::string& hex, void* data, size_t size) {
|
||||
if (hex.size() != size * 2) {
|
||||
throw std::runtime_error("incorrect hex data size");
|
||||
}
|
||||
auto* out = reinterpret_cast<uint8_t*>(data);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
out[z] = (team_sync_hex_value(hex[z * 2]) << 4) | team_sync_hex_value(hex[z * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -482,6 +504,12 @@ void TeamIndex::replace_all_from_authority(const phosg::JSON& canonical_team_sta
|
||||
team->spent_points = team_json.get_int("spent_points", 0);
|
||||
team->points = 0;
|
||||
|
||||
std::string flag_data_hex = team_json.get_string("flag_data_hex", "");
|
||||
if (!flag_data_hex.empty()) {
|
||||
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
|
||||
team_sync_decode_hex_to_bytes(flag_data_hex, team->flag_data.get(), sizeof(*team->flag_data));
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -546,6 +574,9 @@ void TeamIndex::replace_all_from_authority(const phosg::JSON& canonical_team_sta
|
||||
|
||||
for (const auto& [team_id, team] : this->id_to_team) {
|
||||
team->save_config();
|
||||
if (team->flag_data) {
|
||||
team->save_flag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+186
@@ -12,6 +12,7 @@
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
@@ -44,6 +45,15 @@ struct OutboundEvent {
|
||||
std::string creator_name;
|
||||
|
||||
int64_t points_delta = 0;
|
||||
|
||||
std::string new_name;
|
||||
uint8_t flags = 0;
|
||||
uint32_t master_account_id = 0;
|
||||
uint32_t new_master_account_id = 0;
|
||||
std::string key;
|
||||
uint32_t points = 0;
|
||||
uint32_t reward_flag = 0;
|
||||
std::string flag_data_hex;
|
||||
};
|
||||
|
||||
static constexpr size_t MAX_OUTBOUND_EVENTS = 256;
|
||||
@@ -196,6 +206,18 @@ static std::string json_escape(const std::string& s) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::string hex_encode(const void* data, size_t size) {
|
||||
static const char* chars = "0123456789abcdef";
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
|
||||
std::string ret;
|
||||
ret.resize(size * 2);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
ret[z * 2] = chars[bytes[z] >> 4];
|
||||
ret[z * 2 + 1] = chars[bytes[z] & 0x0F];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static asio::awaitable<std::string> read_http_line(
|
||||
asio::ip::tcp::socket& sock,
|
||||
std::string& pending_data,
|
||||
@@ -557,6 +579,121 @@ bool enqueue_team_member_update(uint32_t account_id, const std::string& name, in
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_rename(uint32_t team_id, const std::string& new_name) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || team_id == 0 || new_name.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_rename";
|
||||
ev.team_id = team_id;
|
||||
ev.new_name = new_name;
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_disband(uint32_t team_id) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || team_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_disband";
|
||||
ev.team_id = team_id;
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_member_flags_update(uint32_t account_id, uint8_t flags) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || account_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_member_flags_update";
|
||||
ev.account_id = account_id;
|
||||
ev.flags = flags;
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_master_transfer(uint32_t master_account_id, uint32_t new_master_account_id) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || master_account_id == 0 || new_master_account_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_master_transfer";
|
||||
ev.master_account_id = master_account_id;
|
||||
ev.new_master_account_id = new_master_account_id;
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_reward_purchase(uint32_t team_id, const std::string& key, uint32_t points, uint32_t reward_flag) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || team_id == 0 || key.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_reward_purchase";
|
||||
ev.team_id = team_id;
|
||||
ev.key = key;
|
||||
ev.points = points;
|
||||
ev.reward_flag = reward_flag;
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_flag_update(uint32_t team_id, const void* flag_data, size_t size) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions || team_id == 0 || !flag_data || !size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(exchange_state_mutex);
|
||||
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundEvent ev;
|
||||
ev.type = "team_flag_update";
|
||||
ev.team_id = team_id;
|
||||
ev.flag_data_hex = hex_encode(flag_data, size);
|
||||
outbound_events.emplace_back(std::move(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_team_member_remove(uint32_t account_id) {
|
||||
auto cfg = get_config();
|
||||
if (!cfg.enabled || !cfg.relay_team_actions) {
|
||||
@@ -643,6 +780,55 @@ static std::string exchange_body_for_current_state(const Config& cfg) {
|
||||
json_escape(ev.name),
|
||||
ev.points_delta);
|
||||
|
||||
} else if (ev.type == "team_rename") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_rename\",\"team_namespace\":\"{}\",\"team_id\":{},\"new_name\":\"{}\"}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.team_id,
|
||||
json_escape(ev.new_name));
|
||||
|
||||
} else if (ev.type == "team_disband") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_disband\",\"team_namespace\":\"{}\",\"team_id\":{}}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.team_id);
|
||||
|
||||
} else if (ev.type == "team_member_flags_update") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_member_flags_update\",\"team_namespace\":\"{}\",\"account_id\":{},\"flags\":{}}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.account_id,
|
||||
ev.flags);
|
||||
|
||||
} else if (ev.type == "team_master_transfer") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_master_transfer\",\"team_namespace\":\"{}\",\"master_account_id\":{},\"new_master_account_id\":{}}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.master_account_id,
|
||||
ev.new_master_account_id);
|
||||
|
||||
} else if (ev.type == "team_reward_purchase") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_reward_purchase\",\"team_namespace\":\"{}\",\"team_id\":{},\"key\":\"{}\",\"points\":{},\"reward_flag\":{}}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.team_id,
|
||||
json_escape(ev.key),
|
||||
ev.points,
|
||||
ev.reward_flag);
|
||||
|
||||
} else if (ev.type == "team_flag_update") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_flag_update\",\"team_namespace\":\"{}\",\"team_id\":{},\"flag_data_hex\":\"{}\"}}",
|
||||
ev.seq,
|
||||
json_escape(cfg.team_namespace),
|
||||
ev.team_id,
|
||||
ev.flag_data_hex);
|
||||
|
||||
} else if (ev.type == "team_member_remove") {
|
||||
parts += std::format(
|
||||
"{{\"seq\":{},\"type\":\"team_member_remove\",\"team_namespace\":\"{}\",\"account_id\":{}}}",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -39,6 +40,12 @@ bool enqueue_team_create(const std::string& team_name, uint32_t creator_account_
|
||||
bool enqueue_team_member_add(uint32_t team_id, uint32_t account_id, const std::string& name);
|
||||
bool enqueue_team_member_update(uint32_t account_id, const std::string& name, int64_t points_delta);
|
||||
bool enqueue_team_member_remove(uint32_t account_id);
|
||||
bool enqueue_team_rename(uint32_t team_id, const std::string& new_name);
|
||||
bool enqueue_team_disband(uint32_t team_id);
|
||||
bool enqueue_team_member_flags_update(uint32_t account_id, uint8_t flags);
|
||||
bool enqueue_team_master_transfer(uint32_t master_account_id, uint32_t new_master_account_id);
|
||||
bool enqueue_team_reward_purchase(uint32_t team_id, const std::string& key, uint32_t points, uint32_t reward_flag);
|
||||
bool enqueue_team_flag_update(uint32_t team_id, const void* flag_data, size_t size);
|
||||
asio::awaitable<bool> exchange_once_now();
|
||||
|
||||
using CanonicalTeamStateCallback = std::function<void(const phosg::JSON&)>;
|
||||
|
||||
Reference in New Issue
Block a user