Added CreateChoiceWindow, ClearChoice and GetChoiceSpawnID lua functions, added base of tracking for min, max, avg, first level for raid/group into instances... more work to complete tomorrow
This commit is contained in:
parent
48b2b1cc4d
commit
c17b4d5588
@ -5595,7 +5595,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
|
||||
case COMMAND_SET_GUILD_MEMBER_NOTE : { Command_SetGuildMemberNote(client, sep); break; }
|
||||
case COMMAND_SET_GUILD_OFFICER_NOTE : { Command_SetGuildOfficerNote(client, sep); break; }
|
||||
case COMMAND_GUILD : { Command_Guild(client, sep); break; }
|
||||
case COMMAND_CREATE_GUILD : { Command_CreateGuild(client); break; }
|
||||
case COMMAND_CREATE_GUILD : { Command_CreateGuild(client, sep); break; }
|
||||
case COMMAND_GUILDS : { Command_Guilds(client); break; }
|
||||
case COMMAND_GUILDS_ADD : { Command_GuildsAdd(client, sep); break; }
|
||||
case COMMAND_GUILDS_CREATE : { Command_GuildsCreate(client, sep); break; }
|
||||
@ -6541,9 +6541,9 @@ void Commands::Command_Guild(Client* client, Seperator* sep)
|
||||
Purpose : Display's in-game Guild Creation window
|
||||
Dev : Scatman
|
||||
*/
|
||||
void Commands::Command_CreateGuild(Client* client)
|
||||
void Commands::Command_CreateGuild(Client* client, Seperator* sep)
|
||||
{
|
||||
client->SendGuildCreateWindow();
|
||||
Command_GuildsCreate(client, sep, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -6692,11 +6692,33 @@ void Commands::Command_GuildsAdd(Client* client, Seperator* sep)
|
||||
Dev : Scatman
|
||||
Example : /guilds create [guild name] (player name)
|
||||
*/
|
||||
void Commands::Command_GuildsCreate(Client* client, Seperator* sep)
|
||||
void Commands::Command_GuildsCreate(Client* client, Seperator* sep, bool prompted_dialog)
|
||||
{
|
||||
Spawn* npc = nullptr;
|
||||
if(prompted_dialog) {
|
||||
auto target_npc = client->dialog_manager.getAcceptValue("create guild");
|
||||
if(!target_npc) {
|
||||
// well this is not acceptable!! CHEATER! :D
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if(!client->GetPlayer()->GetZone()){
|
||||
// player isn't in a zone? eh..
|
||||
return;
|
||||
}
|
||||
npc = client->GetPlayer()->GetZone()->GetSpawnByID(target_npc);
|
||||
if(!npc) {
|
||||
client->Message(CHANNEL_COLOR_RED, "Did not find guild registrar, please re-initiate dialog with them.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sep && sep->arg[0])
|
||||
{
|
||||
const char* guild_name = sep->arg[0];
|
||||
if(prompted_dialog)
|
||||
guild_name = sep->argplus[0];
|
||||
|
||||
int8 resp = database.CheckNameFilter(guild_name, 4, 41);
|
||||
if(!guild_name || resp == BADNAMELENGTH_REPLY) {
|
||||
client->Message(CHANNEL_COLOR_YELLOW, "Guild name is too short.");
|
||||
@ -6708,7 +6730,7 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (sep->arg[1] && strlen(sep->arg[1]) > 0 && client->GetAdminStatus() > 0)
|
||||
if (!prompted_dialog && sep->arg[1] && strlen(sep->arg[1]) > 0 && client->GetAdminStatus() > 0)
|
||||
{
|
||||
Client* to_client = zone_list.GetClientByCharName(string(sep->arg[1]));
|
||||
|
||||
@ -6728,7 +6750,7 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep)
|
||||
client->Message(CHANNEL_COLOR_YELLOW, "Could not find target %s or target is already in a guild.", sep->arg[1]);
|
||||
}
|
||||
}
|
||||
else if (client->GetAdminStatus() > 0 && client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer())
|
||||
else if (!prompted_dialog && client->GetAdminStatus() > 0 && client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer())
|
||||
{
|
||||
Client* to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient();
|
||||
|
||||
@ -6752,27 +6774,38 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep)
|
||||
{
|
||||
if(client->GetPlayer()->GetGuild()) {
|
||||
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already in a guild.");
|
||||
if(client->GetAdminStatus() < 1)
|
||||
return;
|
||||
}
|
||||
else if(net.is_primary) {
|
||||
int32 guildID = world.CreateGuild(guild_name);
|
||||
if(guildID > 0)
|
||||
Client* leader = nullptr;
|
||||
if(prompted_dialog || client->GetAdminStatus() < 1)
|
||||
leader = client;
|
||||
int32 guildID = world.CreateGuild(guild_name, leader);
|
||||
if(guildID > 0) {
|
||||
peer_manager.sendPeersCreateGuild(guildID);
|
||||
}
|
||||
else {
|
||||
peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), "");
|
||||
}
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), "", prompted_dialog, npc->GetID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
if (ret) {
|
||||
client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' was successfully created.", guild_name);
|
||||
if(prompted_dialog) { // prompted dialog requires npc be defined
|
||||
client->GetCurrentZone()->CallSpawnScript(npc, SPAWN_SCRIPT_CASTED_ON, client->GetPlayer(), guild_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an error creating the guild.");
|
||||
}
|
||||
else
|
||||
client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' already exists.", guild_name);
|
||||
}
|
||||
else
|
||||
else if(!prompted_dialog)
|
||||
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds create [guild name] (player name). If no player is specified, the player's target will be used. If the player has no target, a guild with no members will be created.");
|
||||
}
|
||||
|
||||
|
@ -311,14 +311,14 @@ public:
|
||||
void Command_StopFollow(Client* client, Seperator* sep);
|
||||
void Command_Grid(Client* client, Seperator* sep);
|
||||
void Command_Guild(Client* client, Seperator* sep);
|
||||
void Command_CreateGuild(Client* client);
|
||||
void Command_CreateGuild(Client* client, Seperator* sep);
|
||||
void Command_SetGuildOfficerNote(Client* client, Seperator* sep);
|
||||
void Command_SetGuildMemberNote(Client* client, Seperator* sep);
|
||||
void Command_OfficerSay(Client* client, Seperator* sep);
|
||||
void Command_GuildSay(Client* client, Seperator* sep);
|
||||
void Command_Guilds(Client* client);
|
||||
void Command_GuildsAdd(Client* client, Seperator* sep);
|
||||
void Command_GuildsCreate(Client* client, Seperator* sep);
|
||||
void Command_GuildsCreate(Client* client, Seperator* sep, bool prompted_dialog = false);
|
||||
void Command_GuildsDelete(Client* client, Seperator* sep);
|
||||
void Command_GuildsList(Client* client);
|
||||
void Command_GuildsRemove(Client* client, Seperator* sep);
|
||||
|
@ -14313,3 +14313,104 @@ int EQ2Emu_lua_CreatePersistedRespawn(lua_State* state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int EQ2Emu_lua_CreateChoiceWindow(lua_State* state) {
|
||||
if (!lua_interface)
|
||||
return 0;
|
||||
Spawn* npc = lua_interface->GetSpawn(state);
|
||||
Spawn* spawn = lua_interface->GetSpawn(state, 2);
|
||||
std::string windowTextPrompt = lua_interface->GetStringValue(state, 3);
|
||||
std::string acceptText = lua_interface->GetStringValue(state, 4);
|
||||
std::string acceptCommand = lua_interface->GetStringValue(state, 5);
|
||||
std::string declineText = lua_interface->GetStringValue(state, 6);
|
||||
std::string declineCommand = lua_interface->GetStringValue(state, 7);
|
||||
int32 time = lua_interface->GetInt32Value(state, 8);
|
||||
int8 textBox = lua_interface->GetInt8Value(state, 9);
|
||||
int8 textBoxRequired = lua_interface->GetInt8Value(state, 10);
|
||||
int32 maxLength = lua_interface->GetInt32Value(state, 11);
|
||||
lua_interface->ResetFunctionStack(state);
|
||||
if(!npc || !npc->IsNPC())
|
||||
{
|
||||
lua_interface->LogError("%s: LUA CreateChoiceWindow command error: npc is not valid, either does not exist or is not a npc", lua_interface->GetScriptName(state));
|
||||
lua_interface->SetBooleanValue(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!spawn || !spawn->IsPlayer())
|
||||
{
|
||||
lua_interface->LogError("%s: LUA CreateChoiceWindow command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state));
|
||||
lua_interface->SetBooleanValue(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Client* client = ((Player*)spawn)->GetClient();
|
||||
|
||||
if(client) {
|
||||
bool success = client->SendDialogChoice(npc->GetID(), windowTextPrompt, acceptText, acceptCommand, declineText, declineCommand, time, textBox, textBoxRequired, maxLength);
|
||||
lua_interface->SetBooleanValue(state, success);
|
||||
return 1;
|
||||
}
|
||||
lua_interface->SetBooleanValue(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int EQ2Emu_lua_ClearChoice(lua_State* state) {
|
||||
if (!lua_interface)
|
||||
return 0;
|
||||
Spawn* spawn = lua_interface->GetSpawn(state);
|
||||
std::string commandToClear = lua_interface->GetStringValue(state, 2);
|
||||
int8 clearDecline = lua_interface->GetInt8Value(state, 3);
|
||||
lua_interface->ResetFunctionStack(state);
|
||||
if(!spawn || !spawn->IsPlayer())
|
||||
{
|
||||
lua_interface->LogError("%s: LUA ClearChoice command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state));
|
||||
lua_interface->SetBooleanValue(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Client* client = ((Player*)spawn)->GetClient();
|
||||
|
||||
if(client) {
|
||||
bool success = false;
|
||||
|
||||
if(clearDecline) {
|
||||
success = client->dialog_manager.clearDecline(commandToClear);
|
||||
}
|
||||
else {
|
||||
success = client->dialog_manager.clearAccept(commandToClear);
|
||||
}
|
||||
lua_interface->SetBooleanValue(state, success);
|
||||
return 1;
|
||||
}
|
||||
lua_interface->SetBooleanValue(state, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int EQ2Emu_lua_GetChoiceSpawnID(lua_State* state) {
|
||||
if (!lua_interface)
|
||||
return 0;
|
||||
Spawn* spawn = lua_interface->GetSpawn(state);
|
||||
std::string commandMatch = lua_interface->GetStringValue(state, 2);
|
||||
int8 declineValue = lua_interface->GetInt8Value(state, 3);
|
||||
lua_interface->ResetFunctionStack(state);
|
||||
if(!spawn || !spawn->IsPlayer())
|
||||
{
|
||||
lua_interface->LogError("%s: LUA GetChoiceSpawnID command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state));
|
||||
lua_interface->SetInt32Value(state, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Client* client = ((Player*)spawn)->GetClient();
|
||||
int32 spawn_id = 0;
|
||||
if(client) {
|
||||
if(declineValue) {
|
||||
spawn_id = client->dialog_manager.getDeclineValue(commandMatch);
|
||||
}
|
||||
else {
|
||||
spawn_id = client->dialog_manager.getAcceptValue(commandMatch);
|
||||
}
|
||||
}
|
||||
lua_interface->SetInt32Value(state, spawn_id);
|
||||
return 1;
|
||||
}
|
@ -663,4 +663,8 @@ int EQ2Emu_lua_DespawnByLocationID(lua_State* state);
|
||||
|
||||
int EQ2Emu_lua_AddRespawn(lua_State* state);
|
||||
int EQ2Emu_lua_CreatePersistedRespawn(lua_State* state);
|
||||
|
||||
int EQ2Emu_lua_CreateChoiceWindow(lua_State* state);
|
||||
int EQ2Emu_lua_ClearChoice(lua_State* state);
|
||||
int EQ2Emu_lua_GetChoiceSpawnID(lua_State* state);
|
||||
#endif
|
@ -1568,6 +1568,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
|
||||
|
||||
lua_register(state,"AddRespawn", EQ2Emu_lua_AddRespawn);
|
||||
lua_register(state,"CreatePersistedRespawn", EQ2Emu_lua_CreatePersistedRespawn);
|
||||
|
||||
lua_register(state,"CreateChoiceWindow", EQ2Emu_lua_CreateChoiceWindow);
|
||||
lua_register(state,"ClearChoice", EQ2Emu_lua_ClearChoice);
|
||||
lua_register(state,"GetChoiceSpawnID", EQ2Emu_lua_GetChoiceSpawnID);
|
||||
}
|
||||
|
||||
void LuaInterface::LogError(const char* error, ...) {
|
||||
|
@ -1977,7 +1977,81 @@ void PlayerGroupManager::ClearGroupRaidLooterFlag(int32 groupID) {
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerGroupManager::EstablishRaidLevelRange(Client* client, int32* min_level, int32* max_level, int32* avg_level, int32* first_level) {
|
||||
std::shared_lock lock(MGroups);
|
||||
if(!client)
|
||||
return;
|
||||
|
||||
if (!client->GetPlayer()->GetGroupMemberInfo()) {
|
||||
*min_level = *max_level = *avg_level = *first_level = client->GetPlayer()->GetLevel();
|
||||
return;
|
||||
}
|
||||
|
||||
int32 groupID = client->GetPlayer()->GetGroupMemberInfo()->group_id;
|
||||
|
||||
if (m_groups.count(groupID) < 1) {
|
||||
*min_level = *max_level = *avg_level = *first_level = client->GetPlayer()->GetLevel();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerGroup* group = m_groups[groupID];
|
||||
if (group) {
|
||||
bool isInRaid = group->IsInRaidGroup(group->GetID());
|
||||
std::vector<int32> raidGroups;
|
||||
group->GetRaidGroups(&raidGroups);
|
||||
|
||||
if (!isInRaid && raidGroups.size() < 1) {
|
||||
raidGroups.push_back(group->GetID());
|
||||
}
|
||||
|
||||
// Initialize tracking variables
|
||||
int32 local_min_level = INT32_MAX;
|
||||
int32 local_max_level = INT32_MIN;
|
||||
int32 total_levels = 0;
|
||||
int8 level_count = 0;
|
||||
|
||||
// Get the first player's level
|
||||
*first_level = client->GetPlayer()->GetLevel();
|
||||
|
||||
for (auto& groupID : raidGroups) {
|
||||
group = m_groups[groupID];
|
||||
if (!group)
|
||||
continue;
|
||||
|
||||
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
|
||||
deque<GroupMemberInfo*>* members = group->GetMembers();
|
||||
|
||||
for (auto& memberInfo : *members) {
|
||||
Entity* member = memberInfo->member;
|
||||
if (!member || !member->IsPlayer())
|
||||
continue;
|
||||
|
||||
if (member->GetZone() != client->GetPlayer()->GetZone())
|
||||
continue;
|
||||
|
||||
int32 member_level = member->GetLevel();
|
||||
local_min_level = std::min(local_min_level, member_level);
|
||||
local_max_level = std::max(local_max_level, member_level);
|
||||
total_levels += member_level;
|
||||
level_count++;
|
||||
}
|
||||
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// Finalize values with divide-by-zero protection
|
||||
if (level_count > 0) {
|
||||
*min_level = local_min_level;
|
||||
*max_level = local_max_level;
|
||||
*avg_level = total_levels / level_count;
|
||||
}
|
||||
else {
|
||||
*min_level = *max_level = *avg_level = *first_level = client->GetPlayer()->GetLevel();
|
||||
}
|
||||
}
|
||||
else {
|
||||
*min_level = *max_level = *avg_level = *first_level = client->GetPlayer()->GetLevel();
|
||||
}
|
||||
}
|
||||
|
||||
Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_position) {
|
||||
Entity* ret = nullptr;
|
||||
|
@ -255,6 +255,7 @@ public:
|
||||
void SplitWithGroupOrRaid(Client* client, int32 coin_plat, int32 coin_gold, int32 coin_silver, int32 coin_copper);
|
||||
bool IdentifyMemberInGroupOrRaid(ZoneChangeDetails* details, Client* client, int32 zoneID, int32 instanceID = 0);
|
||||
void ClearGroupRaidLooterFlag(int32 groupID);
|
||||
void EstablishRaidLevelRange(Client* client, int32* min_level, int32* max_level, int32* avg_level, int32* first_level);
|
||||
private:
|
||||
int32 m_nextGroupID; // Used to generate a new unique id for new groups
|
||||
|
||||
|
@ -295,6 +295,8 @@ void HandleCreateGuild(boost::property_tree::ptree tree) {
|
||||
bool success = false;
|
||||
int32 guildID = 0;
|
||||
std::string leaderName("");
|
||||
bool promptedDialog = false;
|
||||
int32 spawnID = 0;
|
||||
if (auto successful = tree.get_optional<bool>("success")) {
|
||||
success = successful.get();
|
||||
}
|
||||
@ -307,6 +309,14 @@ void HandleCreateGuild(boost::property_tree::ptree tree) {
|
||||
leaderName = name.get();
|
||||
}
|
||||
|
||||
if (auto prompt = tree.get_optional<bool>("prompted_dialog")) {
|
||||
promptedDialog = prompt.get();
|
||||
}
|
||||
|
||||
if (auto spawnid = tree.get_optional<int32>("spawn_id")) {
|
||||
spawnID = spawnid.get();
|
||||
}
|
||||
|
||||
if (net.is_primary) {
|
||||
// we send out to peers
|
||||
}
|
||||
@ -315,6 +325,12 @@ void HandleCreateGuild(boost::property_tree::ptree tree) {
|
||||
Guild* guild = guild_list.GetGuild(guildID);
|
||||
Client* leader = zone_list.GetClientByCharName(leaderName.c_str());
|
||||
if (leader && guild && !leader->GetPlayer()->GetGuild()) {
|
||||
if(spawnID) {
|
||||
Spawn* npc = leader->GetPlayer()->GetZone()->GetSpawnByID(spawnID);
|
||||
if(promptedDialog && npc && npc->IsNPC()) {
|
||||
leader->GetCurrentZone()->CallSpawnScript(npc, SPAWN_SCRIPT_CASTED_ON, leader->GetPlayer(), guild->GetName());
|
||||
}
|
||||
}
|
||||
guild->AddNewGuildMember(leader, 0, GUILD_RANK_LEADER);
|
||||
database.SaveGuildMembers(guild);
|
||||
if (leader && leader->GetPlayer()->GetGroupMemberInfo()) {
|
||||
|
@ -619,7 +619,7 @@ void PeerManager::sendPeersDisbandGroup(int32 group_id) {
|
||||
}
|
||||
}
|
||||
|
||||
bool PeerManager::sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name) {
|
||||
bool PeerManager::sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name, bool prompted_dialog, int32 spawnID) {
|
||||
std::shared_ptr<Peer> primary = getHealthyPrimaryPeerPtr();
|
||||
if (primary) {
|
||||
boost::property_tree::ptree root;
|
||||
@ -628,6 +628,8 @@ bool PeerManager::sendPrimaryCreateGuildRequest(std::string guild_name, std::str
|
||||
root.put("peer_web_port", std::to_string(net.GetWebWorldPort()));
|
||||
root.put("guild_name", guild_name);
|
||||
root.put("leader_name", leader_name);
|
||||
root.put("prompted_dialog", prompted_dialog);
|
||||
root.put("spawn_id", spawnID);
|
||||
|
||||
std::ostringstream jsonStream;
|
||||
boost::property_tree::write_json(jsonStream, root);
|
||||
|
@ -206,7 +206,7 @@ public:
|
||||
std::string peerId = "", std::vector<int32>* raidGroups = nullptr, bool is_update = false);
|
||||
|
||||
void sendPeersDisbandGroup(int32 group_id);
|
||||
bool sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name);
|
||||
bool sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name, bool prompted_dialog = false, int32 spawnID = 0);
|
||||
void sendPeersAddGuildMember(int32 character_id, int32 guild_id, std::string invited_by, int32 join_timestamp, int8 rank);
|
||||
void sendPeersRemoveGuildMember(int32 character_id, int32 guild_id, std::string removed_by);
|
||||
void sendPeersCreateGuild(int32 guild_id);
|
||||
|
@ -618,6 +618,7 @@ void World::Web_worldhandle_startzone(const http::request<http::string_body>& re
|
||||
int32 zoneId = 0;
|
||||
std::string zoneName("");
|
||||
bool alwaysLoaded = false;
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
if (auto inst_id = json_tree.get_optional<int32>("instance_id")) {
|
||||
instanceId = inst_id.get();
|
||||
}
|
||||
@ -634,6 +635,22 @@ void World::Web_worldhandle_startzone(const http::request<http::string_body>& re
|
||||
alwaysLoaded = always_loaded.get();
|
||||
}
|
||||
|
||||
if (auto level = json_tree.get_optional<int32>("min_level")) {
|
||||
minLevel = level.get();
|
||||
}
|
||||
|
||||
if (auto level = json_tree.get_optional<int32>("max_level")) {
|
||||
maxLevel = level.get();
|
||||
}
|
||||
|
||||
if (auto level = json_tree.get_optional<int32>("avg_level")) {
|
||||
avgLevel = level.get();
|
||||
}
|
||||
|
||||
if (auto level = json_tree.get_optional<int32>("first_level")) {
|
||||
firstLevel = level.get();
|
||||
}
|
||||
|
||||
sint32 success = 0;
|
||||
ZoneChangeDetails details;
|
||||
if (instanceId || zoneId || zoneName.length() > 0) {
|
||||
@ -642,7 +659,7 @@ void World::Web_worldhandle_startzone(const http::request<http::string_body>& re
|
||||
success = 1;
|
||||
}
|
||||
else {
|
||||
if ((zone_list.GetZoneByInstance(&details, instanceId, zoneId, true, false, false, false)))
|
||||
if ((zone_list.GetZoneByInstance(&details, instanceId, zoneId, true, false, false, false, minLevel, maxLevel, avgLevel, firstLevel)))
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
@ -684,7 +684,7 @@ void ZoneList::Remove(ZoneServer* zone) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ZoneList::GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::string opt_zone_name, bool loadZone, bool skip_existing_zones, bool increment_zone, bool check_peers, bool check_instances, bool only_always_loaded, bool skip_self) {
|
||||
bool ZoneList::GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::string opt_zone_name, bool loadZone, bool skip_existing_zones, bool increment_zone, bool check_peers, bool check_instances, bool only_always_loaded, bool skip_self, int32 minLevel, int32 maxLevel, int32 avgLevel, int32 firstLevel) {
|
||||
list<ZoneServer*>::iterator zone_iter;
|
||||
ZoneServer* tmp = 0;
|
||||
ZoneServer* ret = 0;
|
||||
@ -736,6 +736,11 @@ bool ZoneList::GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::
|
||||
root.put("zone_name", opt_zone_name);
|
||||
root.put("zone_id", std::to_string(opt_zone_id));
|
||||
root.put("always_loaded", only_always_loaded);
|
||||
|
||||
root.put("min_level", minLevel);
|
||||
root.put("max_level", maxLevel);
|
||||
root.put("avg_level", avgLevel);
|
||||
root.put("first_level", firstLevel);
|
||||
std::ostringstream jsonStream;
|
||||
boost::property_tree::write_json(jsonStream, root);
|
||||
std::string jsonPayload = jsonStream.str();
|
||||
@ -751,7 +756,7 @@ bool ZoneList::GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::
|
||||
}
|
||||
else {
|
||||
tmp = new ZoneServer(opt_zone_name.c_str());
|
||||
database.LoadZoneInfo(tmp);
|
||||
database.LoadZoneInfo(tmp, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
tmp->Init();
|
||||
tmp->SetAlwaysLoaded(only_always_loaded);
|
||||
}
|
||||
@ -771,7 +776,8 @@ bool ZoneList::GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::
|
||||
return (tmp != nullptr) ? true : false;
|
||||
}
|
||||
|
||||
bool ZoneList::GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance_id, int32 zone_id, bool loadZone, bool skip_existing_zones, bool increment_zone, bool check_peers) {
|
||||
bool ZoneList::GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance_id, int32 zone_id, bool loadZone, bool skip_existing_zones, bool increment_zone, bool check_peers,
|
||||
int32 minLevel, int32 maxLevel, int32 avgLevel, int32 firstLevel) {
|
||||
list<ZoneServer*>::iterator zone_iter;
|
||||
ZoneServer* tmp = 0;
|
||||
ZoneServer* ret = 0;
|
||||
@ -814,6 +820,12 @@ bool ZoneList::GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance
|
||||
root.put("zone_name", zonename);
|
||||
root.put("zone_id", std::to_string(zone_id));
|
||||
root.put("always_loaded", false);
|
||||
|
||||
root.put("min_level", minLevel);
|
||||
root.put("max_level", maxLevel);
|
||||
root.put("avg_level", avgLevel);
|
||||
root.put("first_level", firstLevel);
|
||||
|
||||
std::ostringstream jsonStream;
|
||||
boost::property_tree::write_json(jsonStream, root);
|
||||
std::string jsonPayload = jsonStream.str();
|
||||
@ -834,7 +846,7 @@ bool ZoneList::GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance
|
||||
if ( instance_id > 0 )
|
||||
tmp->SetupInstance(instance_id);
|
||||
|
||||
database.LoadZoneInfo(tmp);
|
||||
database.LoadZoneInfo(tmp, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
tmp->Init();
|
||||
}
|
||||
}
|
||||
|
@ -419,8 +419,8 @@ class ZoneList {
|
||||
|
||||
void Add(ZoneServer* zone);
|
||||
void Remove(ZoneServer* zone);
|
||||
bool GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::string opt_zone_name = "", bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true, bool check_peers = true, bool check_instances = false, bool only_always_loaded = false, bool skip_self = false);
|
||||
bool GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance_id, int32 zone_id = 0, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true, bool check_peers = true);
|
||||
bool GetZone(ZoneChangeDetails* zone_details, int32 opt_zone_id, std::string opt_zone_name = "", bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true, bool check_peers = true, bool check_instances = false, bool only_always_loaded = false, bool skip_self = false, int32 minLevel = 0, int32 maxLevel = 0, int32 avgLevel = 0, int32 firstLevel = 0);
|
||||
bool GetZoneByInstance(ZoneChangeDetails* zone_details, int32 instance_id, int32 zone_id = 0, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true, bool check_peers = true, int32 minLevel = 0, int32 maxLevel = 0, int32 avgLevel = 0, int32 firstLevel = 0);
|
||||
|
||||
bool IsClientConnectedPeer(int32 account_id);
|
||||
//ZoneServer* Get(int32 id, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true);
|
||||
|
@ -2954,9 +2954,11 @@ void WorldDatabase::LoadCharacterFriendsIgnoreList(Player* player) {
|
||||
}
|
||||
}
|
||||
|
||||
void WorldDatabase::LoadZoneInfo(ZoneServer* zone){
|
||||
void WorldDatabase::LoadZoneInfo(ZoneServer* zone, int32 minLevel, int32 maxLevel, int32 avgLevel, int32 firstLevel){
|
||||
Query query;
|
||||
int32 ruleset_id;
|
||||
zone->setGroupRaidLevels(minLevel, maxLevel, avgLevel, firstLevel);
|
||||
|
||||
char* escaped = getEscapeString(zone->GetZoneName());
|
||||
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type+0, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, safe_heading, xp_modifier, ruleset_id, expansion_id, weather_allowed, sky_file, can_bind, can_gate, city_zone, can_evac FROM zones where name='%s'",escaped);
|
||||
if(result && mysql_num_rows(result) > 0) {
|
||||
@ -2999,8 +3001,10 @@ void WorldDatabase::LoadZoneInfo(ZoneServer* zone){
|
||||
|
||||
if (zone->IsInstanceZone())
|
||||
{
|
||||
if ( zone->GetInstanceID() < 1 )
|
||||
zone->SetupInstance(CreateNewInstance(zone->GetZoneID()));
|
||||
if ( zone->GetInstanceID() < 1 ) {
|
||||
zone->SetupInstance(CreateNewInstance(zone->GetZoneID(), minLevel, maxLevel, avgLevel, firstLevel));
|
||||
zone->setGroupRaidLevels(minLevel, maxLevel, avgLevel, firstLevel);
|
||||
}
|
||||
else
|
||||
zone->SetupInstance(zone->GetInstanceID());
|
||||
}
|
||||
@ -4085,7 +4089,6 @@ void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_i
|
||||
instance_id = CreateNewInstance(zone_id);
|
||||
tmp->SetInstanceID(instance_id);
|
||||
LoadZoneInfo(tmp);
|
||||
instance_id = CreateNewInstance(zone_id);
|
||||
AddCharacterInstance(char_id, instance_id, string(tmp->GetZoneName()), tmp->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime());
|
||||
safe_delete(tmp);
|
||||
}
|
||||
@ -5935,13 +5938,13 @@ bool WorldDatabase::UpdateInstancedSpawnRemoved(int32 spawn_location_entry_id, i
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 WorldDatabase::CreateNewInstance(int32 zone_id)
|
||||
int32 WorldDatabase::CreateNewInstance(int32 zone_id, int32 playersMinLevel, int32 playersMaxLevel, int32 playersavgLevel, int32 playersfirstLevel)
|
||||
{
|
||||
int32 ret = 0;
|
||||
|
||||
LogWrite(INSTANCE__DEBUG, 0, "Instance", "Creating new instance for zone: %u ", zone_id);
|
||||
|
||||
if( !database_new.Query("INSERT INTO instances (zone_id) VALUES (%u)", zone_id) )
|
||||
if( !database_new.Query("INSERT INTO instances (zone_id, player_minlevel, player_maxlevel, player_avglevel, player_firstlevel) VALUES (%u, %u, %u, %u, %u)", zone_id, playersMinLevel, playersMaxLevel, playersavgLevel, playersfirstLevel) )
|
||||
LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in CreateNewInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError());
|
||||
else
|
||||
ret = database_new.LastInsertID();
|
||||
|
@ -217,7 +217,7 @@ public:
|
||||
void LoadPlayerAA(Player *player);
|
||||
void LoadCharacterQuestProgress(Client* client);
|
||||
void LoadCharacterFriendsIgnoreList(Player* player);
|
||||
void LoadZoneInfo(ZoneServer* zone);
|
||||
void LoadZoneInfo(ZoneServer* zone, int32 minLevel=0, int32 maxLevel=0, int32 avgLevel=0, int32 firstLevel=0);
|
||||
void LoadZoneInfo(ZoneInfo* zone_info);
|
||||
int32 GetZoneID(const char* name);
|
||||
void SaveZoneInfo(int32 zone_id, const char* field, sint32 value);
|
||||
@ -398,7 +398,7 @@ public:
|
||||
|
||||
// Zone Instance DB Functions
|
||||
map<int32,int32>* GetInstanceRemovedSpawns(int32 instance_id, int8 type);
|
||||
int32 CreateNewInstance(int32 zone_id);
|
||||
int32 CreateNewInstance(int32 zone_id, int32 playersMinLevel=0, int32 playersMaxLevel=0, int32 playersavgLevel=0, int32 playersfirstLevel=0);
|
||||
//int32 AddCharacterInstance(int32 char_id, int32 instance_id, int32 grant_reenter_time_left=0, int32 grant_reset_time_left=0, int32 lockout_time=0);
|
||||
int32 AddCharacterInstance(int32 char_id, int32 instance_id, string zone_name, int8 instance_type, int32 last_success, int32 last_failure, int32 success_lockout, int32 failure_lockout);
|
||||
bool UpdateCharacterInstanceTimers(int32 char_id, int32 instance_id, int32 lockout_time=0, int32 reset_time=0, int32 reenter_time=0 );
|
||||
|
@ -4152,7 +4152,9 @@ void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) {
|
||||
current_zone->RemoveSpawn(player, false, true, true, true, true);
|
||||
}
|
||||
ZoneChangeDetails zone_details;
|
||||
if (zone_list.GetZoneByInstance(&zone_details, id, zoneid, true, false, true, false)) {
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
world.GetGroupManager()->EstablishRaidLevelRange(this, &minLevel, &maxLevel, &avgLevel, &firstLevel);
|
||||
if (zone_list.GetZoneByInstance(&zone_details, id, zoneid, true, false, true, false, minLevel, maxLevel, avgLevel, firstLevel)) {
|
||||
SetCurrentZone((ZoneServer*)zone_details.zonePtr);
|
||||
}
|
||||
else {
|
||||
@ -4649,6 +4651,9 @@ bool Client::IdentifyInstance(ZoneChangeDetails* zone_details, int32 zoneID) {
|
||||
if (instanceType < 1)
|
||||
return false;
|
||||
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
world.GetGroupManager()->EstablishRaidLevelRange(this, &minLevel, &maxLevel, &avgLevel, &firstLevel);
|
||||
|
||||
bool foundZone = false;
|
||||
InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID);
|
||||
if (data) {
|
||||
@ -4659,7 +4664,7 @@ bool Client::IdentifyInstance(ZoneChangeDetails* zone_details, int32 zoneID) {
|
||||
}
|
||||
|
||||
// Need to update `character_instances` table with new timestamps (for persistent) and instance id's
|
||||
foundZone = zone_list.GetZoneByInstance(zone_details, data->instance_id, zoneID, true, false, false);
|
||||
foundZone = zone_list.GetZoneByInstance(zone_details, data->instance_id, zoneID, true, false, false, true, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
|
||||
// if we got an instance_zone and the instance_id from the data is 0 or data instance id is not the same as the zone instance id then update values
|
||||
if (foundZone && (data->instance_id == 0 || data->instance_id != zone_details->instanceId)) {
|
||||
@ -4685,6 +4690,9 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
// determine if this is a group instanced zone that already exists
|
||||
foundZone = world.GetGroupManager()->IdentifyMemberInGroupOrRaid(&zone_details, this, zoneID);
|
||||
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
world.GetGroupManager()->EstablishRaidLevelRange(this, &minLevel, &maxLevel, &avgLevel, &firstLevel);
|
||||
|
||||
if (foundZone) {
|
||||
InstanceData* data = nullptr;
|
||||
if (zone_details.instanceId)
|
||||
@ -4720,7 +4728,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
case GROUP_LOCKOUT_INSTANCE:
|
||||
case RAID_LOCKOUT_INSTANCE:
|
||||
{
|
||||
if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID))) {
|
||||
if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel))) {
|
||||
// once lockout instance zone shuts down you can't renenter if you have a lockout or if you don't you get a new zone
|
||||
// so delete `instances` entry for the zone when it shuts down.
|
||||
int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), zone_details.instanceId, zone_details.zoneName, zone_details.instanceType, 0, 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime);
|
||||
@ -4734,7 +4742,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
case GROUP_PERSIST_INSTANCE:
|
||||
case RAID_PERSIST_INSTANCE:
|
||||
{
|
||||
if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID))) {
|
||||
if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel))) {
|
||||
int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), zone_details.instanceId, zone_details.zoneName, zone_details.instanceType, Timer::GetUnixTimeStamp(), 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime);
|
||||
|
||||
if (db_id > 0)
|
||||
@ -4750,7 +4758,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
if (instance_zone) {
|
||||
// Check the current population against the max population, if greater or equal start a new version
|
||||
if (instance_zone->GetClientCount() >= rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, MaxPlayers)->GetInt32()) {
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID);
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
}
|
||||
else {
|
||||
peer_manager.setZonePeerDataSelf(&zone_details, std::string(instance_zone->GetZoneFile()), std::string(instance_zone->GetZoneName()),
|
||||
@ -4762,7 +4770,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID);
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -4779,7 +4787,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) {
|
||||
|
||||
case QUEST_INSTANCE:
|
||||
{
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID);
|
||||
foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel);
|
||||
break;
|
||||
/*
|
||||
ALTER TABLE `zones` CHANGE COLUMN `instance_type` `instance_type` ENUM('NONE','GROUP_LOCKOUT_INSTANCE','GROUP_PERSIST_INSTANCE','RAID_LOCKOUT_INSTANCE','RAID_PERSIST_INSTANCE','SOLO_LOCKOUT_INSTANCE','SOLO_PERSIST_INSTANCE','TRADESKILL_INSTANCE','PUBLIC_INSTANCE','PERSONAL_HOUSE_INSTANCE','GUILD_HOUSE_INSTANCE','QUEST_INSTANCE') NOT NULL DEFAULT 'NONE' COLLATE 'latin1_general_ci' AFTER `start_zone`;
|
||||
@ -4938,7 +4946,9 @@ bool Client::CheckZoneAccess(const char* zoneName) {
|
||||
|
||||
void Client::Zone(int32 instanceid, bool set_coords, bool byInstanceID, bool is_spell) {
|
||||
ZoneChangeDetails zone_details;
|
||||
if (zone_list.GetZoneByInstance(&zone_details, instanceid, 0)) {
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
world.GetGroupManager()->EstablishRaidLevelRange(this, &minLevel, &maxLevel, &avgLevel, &firstLevel);
|
||||
if (zone_list.GetZoneByInstance(&zone_details, instanceid, 0, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel)) {
|
||||
Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, set_coords, is_spell);
|
||||
}
|
||||
|
||||
@ -5102,7 +5112,11 @@ void Client::Zone(const char* new_zone, bool set_coords, bool is_spell)
|
||||
std::string camelCaseName = database.GetZoneName(zone_id);
|
||||
|
||||
InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zone_id);
|
||||
if ((data && zone_list.GetZoneByInstance(&zone_details, data->instance_id, zone_id)) || zone_list.GetZone(&zone_details, 0, camelCaseName)) {
|
||||
|
||||
int32 minLevel = 0, maxLevel = 0, avgLevel = 0, firstLevel = 0;
|
||||
world.GetGroupManager()->EstablishRaidLevelRange(this, &minLevel, &maxLevel, &avgLevel, &firstLevel);
|
||||
|
||||
if ((data && zone_list.GetZoneByInstance(&zone_details, data->instance_id, zone_id, true, false, true, true, minLevel, maxLevel, avgLevel, firstLevel)) || zone_list.GetZone(&zone_details, 0, camelCaseName)) {
|
||||
Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, set_coords, is_spell);
|
||||
}
|
||||
}
|
||||
@ -13403,3 +13417,40 @@ void Client::SendReceiveOffer(Client* target_client, int8 type, std::string name
|
||||
}
|
||||
safe_delete(packet);
|
||||
}
|
||||
|
||||
bool Client::SendDialogChoice(int32 spawnID, const std::string& windowTextPrompt, const std::string& acceptText, const std::string& acceptCommand, const std::string& declineText, const std::string& declineCommand, int32 time, int8 textBox, int8 textBoxRequired, int32 maxLength) {
|
||||
PacketStruct* p = configReader.getStruct("WS_ChoiceWindow", GetVersion());
|
||||
if (!p) {
|
||||
LogWrite(CCLIENT__ERROR, 0, "Client", "CreateChoiceWindow command error: WS_ChoiceWindow packet does not exist for client version %u", GetVersion());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool successAccept = true;
|
||||
bool successDecline = true;
|
||||
|
||||
if(acceptCommand.size() > 0)
|
||||
successAccept = dialog_manager.addAccept(acceptCommand, spawnID, time);
|
||||
|
||||
if(declineCommand.size() > 0)
|
||||
successDecline = dialog_manager.addDecline(declineCommand, spawnID, time);
|
||||
|
||||
if(!successAccept || !successDecline) { // failed to successfully add command
|
||||
LogWrite(CCLIENT__ERROR, 0, "Client", "CreateChoiceWindow command error: Window has a conflict with accept (%s) %u or decline (%s) %u. Call ClearChoice(Player, Command, x) x being 0 for accept 1 for decline commands.", acceptCommand.c_str(), successAccept, declineCommand.c_str(), successDecline);
|
||||
return false;
|
||||
}
|
||||
|
||||
p->setMediumStringByName("text", windowTextPrompt.c_str());
|
||||
p->setMediumStringByName("accept_text", acceptText.c_str());
|
||||
p->setMediumStringByName("accept_command", acceptCommand.c_str());
|
||||
p->setMediumStringByName("cancel_text", declineText.c_str());
|
||||
p->setMediumStringByName("cancel_command", declineCommand.c_str());
|
||||
p->setDataByName("time", time);
|
||||
p->setDataByName("text_box", textBox);
|
||||
p->setDataByName("text_required", textBoxRequired);
|
||||
p->setDataByName("max_length", maxLength);
|
||||
|
||||
QueuePacket(p->serialize());
|
||||
safe_delete(p);
|
||||
|
||||
return true;
|
||||
}
|
@ -24,6 +24,12 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "../common/EQStream.h"
|
||||
#include "../common/timer.h"
|
||||
@ -61,13 +67,13 @@ struct GroupOptions;
|
||||
#define MAIL_TYPE_SPAM 1
|
||||
#define MAIL_TYPE_GM 2
|
||||
|
||||
struct QueuedQuest{
|
||||
struct QueuedQuest {
|
||||
int32 quest_id;
|
||||
int32 step;
|
||||
bool display_quest_helper;
|
||||
};
|
||||
|
||||
struct BuyBackItem{
|
||||
struct BuyBackItem {
|
||||
int32 item_id;
|
||||
int32 unique_id;
|
||||
int16 quantity;
|
||||
@ -75,7 +81,7 @@ struct BuyBackItem{
|
||||
bool save_needed;
|
||||
};
|
||||
|
||||
struct MacroData{
|
||||
struct MacroData {
|
||||
string name;
|
||||
string text;
|
||||
int16 icon;
|
||||
@ -150,6 +156,85 @@ struct WaypointInfo {
|
||||
};
|
||||
|
||||
|
||||
class DialogManager {
|
||||
public:
|
||||
// Add accept string with int32 id and a timer
|
||||
bool addAccept(const std::string& key, int32 id, int32 seconds) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (acceptMap_.count(key) == 0) {
|
||||
acceptMap_[key] = id;
|
||||
if (seconds)
|
||||
startTimer(key, seconds, true); // true indicates it's an accept key
|
||||
return true;
|
||||
}
|
||||
return false; // Duplicate found in either map
|
||||
}
|
||||
|
||||
// Add decline string with int32 id and a timer
|
||||
bool addDecline(const std::string& key, int32 id, int32 seconds) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (declineMap_.count(key) == 0) {
|
||||
declineMap_[key] = id;
|
||||
if (seconds)
|
||||
startTimer(key, seconds, false); // false indicates it's a decline key
|
||||
return true;
|
||||
}
|
||||
return false; // Duplicate found in either map
|
||||
}
|
||||
|
||||
// Clear a specific accept string
|
||||
bool clearAccept(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return acceptMap_.erase(key) > 0;
|
||||
}
|
||||
|
||||
// Clear a specific decline string
|
||||
bool clearDecline(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return declineMap_.erase(key) > 0;
|
||||
}
|
||||
|
||||
int32 getAcceptValue(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (acceptMap_.count(key)) {
|
||||
return acceptMap_.at(key);
|
||||
}
|
||||
return 0; // Key not found
|
||||
}
|
||||
|
||||
int32 getDeclineValue(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (declineMap_.count(key)) {
|
||||
return declineMap_.at(key);
|
||||
}
|
||||
return 0; // Key not found
|
||||
}
|
||||
|
||||
// Check if a dialog is active
|
||||
bool isDialogActive(const std::string& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return acceptMap_.count(key) > 0 || declineMap_.count(key) > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, int32> acceptMap_;
|
||||
std::map<std::string, int32> declineMap_;
|
||||
std::mutex mutex_;
|
||||
|
||||
void startTimer(const std::string& key, int32 seconds, bool isAccept) {
|
||||
std::thread([this, key, seconds, isAccept]() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(seconds));
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (isAccept) {
|
||||
acceptMap_.erase(key);
|
||||
}
|
||||
else {
|
||||
declineMap_.erase(key);
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
};
|
||||
|
||||
class Client {
|
||||
public:
|
||||
Client(EQStream* ieqs);
|
||||
@ -158,14 +243,14 @@ public:
|
||||
void RemoveClientFromZone();
|
||||
bool Process(bool zone_process = false);
|
||||
void Disconnect(bool send_disconnect = true);
|
||||
void SetConnected(bool val){ connected = val; }
|
||||
bool IsConnected(){ return connected; }
|
||||
bool IsReadyForSpawns(){ return ready_for_spawns; }
|
||||
void SetConnected(bool val) { connected = val; }
|
||||
bool IsConnected() { return connected; }
|
||||
bool IsReadyForSpawns() { return ready_for_spawns; }
|
||||
bool IsReadyForUpdates() { return ready_for_updates; }
|
||||
bool IsZoning(){ return client_zoning; }
|
||||
bool IsZoning() { return client_zoning; }
|
||||
void SetReadyForUpdates();
|
||||
void SetReadyForSpawns(bool val);
|
||||
void QueuePacket(EQ2Packet* app, bool attemptedCombine=false);
|
||||
void QueuePacket(EQ2Packet* app, bool attemptedCombine = false);
|
||||
void SendLoginInfo();
|
||||
int8 GetMessageChannelColor(int8 channel_type);
|
||||
void HandleTellMessage(const char* fromName, const char* message, const char* to, int32 current_language_id);
|
||||
@ -174,12 +259,12 @@ public:
|
||||
void SendSpellUpdate(Spell* spell, bool add_silently = false, bool add_to_hotbar = true);
|
||||
void Zone(ZoneChangeDetails* new_zone, ZoneServer* opt_zone = nullptr, bool set_coords = true, bool is_spell = false);
|
||||
void Zone(const char* new_zone, bool set_coords = true, bool is_spell = false);
|
||||
void Zone(int32 instanceid, bool set_coords = true, bool byInstanceID=false, bool is_spell = false);
|
||||
void Zone(int32 instanceid, bool set_coords = true, bool byInstanceID = false, bool is_spell = false);
|
||||
void ApproveZone();
|
||||
void SendZoneInfo();
|
||||
void SendZoneSpawns();
|
||||
void HandleVerbRequest(EQApplicationPacket* app);
|
||||
void SendControlGhost(int32 send_id=0xFFFFFFFF, int8 unknown2=0);
|
||||
void SendControlGhost(int32 send_id = 0xFFFFFFFF, int8 unknown2 = 0);
|
||||
void SendCharInfo();
|
||||
void SendLoginDeniedBadVersion();
|
||||
void SendCharPOVGhost();
|
||||
@ -187,7 +272,7 @@ public:
|
||||
float DistanceFrom(Client* client);
|
||||
void SendDefaultGroupOptions();
|
||||
bool HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target);
|
||||
bool HandleLootItem(Spawn* entity, Item* item, Spawn* target=nullptr, bool overrideLootRestrictions = false);
|
||||
bool HandleLootItem(Spawn* entity, Item* item, Spawn* target = nullptr, bool overrideLootRestrictions = false);
|
||||
void HandleLootItemRequestPacket(EQApplicationPacket* app);
|
||||
void HandleSkillInfoRequest(EQApplicationPacket* app);
|
||||
void HandleExamineInfoRequest(EQApplicationPacket* app);
|
||||
@ -199,8 +284,8 @@ public:
|
||||
bool Summon(const char* search_name);
|
||||
std::string IdentifyInstanceLockout(int32 zoneID, bool displayClient = true);
|
||||
bool IdentifyInstance(ZoneChangeDetails* zone_details, int32 zoneID);
|
||||
bool TryZoneInstance(int32 zoneID, bool zone_coords_valid=false);
|
||||
bool GotoSpawn(const char* search_name, bool forceTarget=false);
|
||||
bool TryZoneInstance(int32 zoneID, bool zone_coords_valid = false);
|
||||
bool GotoSpawn(const char* search_name, bool forceTarget = false);
|
||||
void DisplayDeadWindow();
|
||||
void HandlePlayerRevive(int32 point_id);
|
||||
void Bank(Spawn* banker, bool cancel = false);
|
||||
@ -215,7 +300,7 @@ public:
|
||||
bool AddItemToBank(int32 item_id, int16 quantity = 0);
|
||||
bool AddItemToBank(Item* item);
|
||||
void UnequipItem(int16 index, sint32 bag_id = -999, int8 to_slot = 255, int8 appearance_equip = 0);
|
||||
bool RemoveItem(Item *item, int16 quantity, bool force_override_no_delete = false);
|
||||
bool RemoveItem(Item* item, int16 quantity, bool force_override_no_delete = false);
|
||||
void ProcessTeleport(Spawn* spawn, vector<TransportDestination*>* destinations, int32 transport_id = 0, bool is_spell = false);
|
||||
void ProcessTeleportLocation(EQApplicationPacket* app);
|
||||
|
||||
@ -235,9 +320,9 @@ public:
|
||||
zoning_destination = zone;
|
||||
}
|
||||
ZoneServer* GetZoningDestination() { return zoning_destination; }
|
||||
Player* GetPlayer(){ return player; }
|
||||
EQStream* getConnection(){ return eqs; }
|
||||
void setConnection(EQStream* ieqs){ eqs = ieqs; }
|
||||
Player* GetPlayer() { return player; }
|
||||
EQStream* getConnection() { return eqs; }
|
||||
void setConnection(EQStream* ieqs) { eqs = ieqs; }
|
||||
|
||||
inline int32 GetIP() { return ip; }
|
||||
inline int16 GetPort() { return port; }
|
||||
@ -247,34 +332,34 @@ public:
|
||||
inline const char* GetAccountName() { return account_name; }
|
||||
inline sint16 GetAdminStatus() { return admin_status; }
|
||||
inline int16 GetVersion() { return version; }
|
||||
void SetNameCRC(int32 val){ name_crc = val; }
|
||||
int32 GetNameCRC(){ return name_crc; }
|
||||
void SetNameCRC(int32 val) { name_crc = val; }
|
||||
int32 GetNameCRC() { return name_crc; }
|
||||
|
||||
|
||||
void SetVersion(int16 new_version){ version = new_version; }
|
||||
void SetVersion(int16 new_version) { version = new_version; }
|
||||
void SetAccountID(int32 in_accountid) { account_id = in_accountid; }
|
||||
void SetCharacterID(int32 in_characterid) { character_id = in_characterid; }
|
||||
void SetAdminStatus(sint16 in_status) { admin_status = in_status; }
|
||||
|
||||
|
||||
void DetermineCharacterUpdates ( );
|
||||
void DetermineCharacterUpdates();
|
||||
|
||||
void UpdateTimeStampFlag ( int8 flagType )
|
||||
void UpdateTimeStampFlag(int8 flagType)
|
||||
{
|
||||
if(! (timestamp_flag & flagType ) )
|
||||
if (!(timestamp_flag & flagType))
|
||||
timestamp_flag |= flagType;
|
||||
}
|
||||
|
||||
int8 GetTimeStampFlag ( ) { return timestamp_flag; }
|
||||
int8 GetTimeStampFlag() { return timestamp_flag; }
|
||||
bool UpdateQuickbarNeeded();
|
||||
void Save();
|
||||
bool remove_from_list;
|
||||
void CloseLoot(int32 spawn_id);
|
||||
void SendLootResponsePacket(int32 total_coins, vector<Item*>* items, Spawn* entity, bool ignore_loot_tier = false);
|
||||
void LootSpawnRequest(Spawn* entity, bool attemptDisarm=true);
|
||||
void LootSpawnRequest(Spawn* entity, bool attemptDisarm = true);
|
||||
bool LootSpawnByMethod(Spawn* entity);
|
||||
void OpenChest(Spawn* entity, bool attemptDisarm=true);
|
||||
void CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier=1, float restrictiveRadius=0.0f);
|
||||
void OpenChest(Spawn* entity, bool attemptDisarm = true);
|
||||
void CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier = 1, float restrictiveRadius = 0.0f);
|
||||
void CheckPlayerQuestsKillUpdate(Spawn* spawn);
|
||||
void CheckPlayerQuestsChatUpdate(Spawn* spawn);
|
||||
void CheckPlayerQuestsItemUpdate(Item* item);
|
||||
@ -291,8 +376,8 @@ public:
|
||||
void SendQuestFailure(Quest* quest);
|
||||
void SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
|
||||
void SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
|
||||
void DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0, bool was_displayed = false);
|
||||
void PopulateQuestRewardItems(vector <Item*>* items, PacketStruct* packet, std::string num_rewards_str = "num_rewards", std::string reward_id_str = "reward_id" , std::string item_str = "item");
|
||||
void DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards = 0, vector<Item*>* selectable_rewards = 0, map<int32, sint32>* factions = 0, const char* header = "Quest Reward!", int32 status_points = 0, const char* text = 0, bool was_displayed = false);
|
||||
void PopulateQuestRewardItems(vector <Item*>* items, PacketStruct* packet, std::string num_rewards_str = "num_rewards", std::string reward_id_str = "reward_id", std::string item_str = "item");
|
||||
void DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""), bool was_displayed = false);
|
||||
void DisplayRandomizeFeatures(int32 features);
|
||||
void AcceptQuestReward(Quest* quest, int32 item_id);
|
||||
@ -310,7 +395,7 @@ public:
|
||||
bool ShouldTarget();
|
||||
void TargetSpawn(Spawn* spawn);
|
||||
void ReloadQuests();
|
||||
int32 GetCurrentQuestID(){ return current_quest_id; }
|
||||
int32 GetCurrentQuestID() { return current_quest_id; }
|
||||
void SetLuaDebugClient(bool val);
|
||||
void SetMerchantTransaction(Spawn* spawn);
|
||||
Spawn* GetMerchantTransaction();
|
||||
@ -360,9 +445,9 @@ public:
|
||||
void SetPlayer(Player* new_player);
|
||||
|
||||
void AddPendingQuestAcceptReward(Quest* quest);
|
||||
void AddPendingQuestReward(Quest* quest, bool update=true, bool is_temporary = false, std::string description = std::string(""));
|
||||
void AddPendingQuestReward(Quest* quest, bool update = true, bool is_temporary = false, std::string description = std::string(""));
|
||||
bool HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection);
|
||||
void QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved=false, int32 index=0);
|
||||
void QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved = false, int32 index = 0);
|
||||
void RemoveQueuedQuestReward();
|
||||
void AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
|
||||
void ProcessQuestUpdates();
|
||||
@ -370,19 +455,19 @@ public:
|
||||
void BeginWaypoint(const char* waypoint_name, float x, float y, float z);
|
||||
void InspectPlayer(Player* player_to_inspect);
|
||||
void SetPendingGuildInvite(Guild* guild, Player* invited_by = 0);
|
||||
PendingGuildInvite* GetPendingGuildInvite() {return &pending_guild_invite;}
|
||||
PendingGuildInvite* GetPendingGuildInvite() { return &pending_guild_invite; }
|
||||
void ShowClaimWindow();
|
||||
void ShowGuildSearchWindow();
|
||||
void CheckQuestQueue();
|
||||
void ShowDressingRoom(Item *item, sint32 crc);
|
||||
void ShowDressingRoom(Item* item, sint32 crc);
|
||||
void SendCollectionList();
|
||||
bool SendCollectionsForItem(Item *item);
|
||||
void HandleCollectionAddItem(int32 collection_id, Item *item);
|
||||
void DisplayCollectionComplete(Collection *collection);
|
||||
bool SendCollectionsForItem(Item* item);
|
||||
void HandleCollectionAddItem(int32 collection_id, Item* item);
|
||||
void DisplayCollectionComplete(Collection* collection);
|
||||
void HandInCollections();
|
||||
void AcceptCollectionRewards(Collection *collection, int32 selectable_item_id = 0);
|
||||
void AcceptCollectionRewards(Collection* collection, int32 selectable_item_id = 0);
|
||||
void SendRecipeList();
|
||||
void PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i=0);
|
||||
void PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i = 0);
|
||||
int32 GetRecipeCRC(Recipe* recipe);
|
||||
void SendRecipeDetails(vector<int32>* recipes);
|
||||
void SendTitleUpdate();
|
||||
@ -398,7 +483,7 @@ public:
|
||||
|
||||
bool IsCrafting();
|
||||
|
||||
void SetRecipeListSent(bool val) {m_recipeListSent = val; }
|
||||
void SetRecipeListSent(bool val) { m_recipeListSent = val; }
|
||||
bool GetRecipeListSent() { return m_recipeListSent; }
|
||||
void ShowRecipeBook();
|
||||
PendingResurrection* GetCurrentRez();
|
||||
@ -415,7 +500,7 @@ public:
|
||||
|
||||
bool GetInitialSpawnsSent() { return initial_spawns_sent; }
|
||||
|
||||
void SendQuestJournalUpdate(Quest* quest, bool updated=true);
|
||||
void SendQuestJournalUpdate(Quest* quest, bool updated = true);
|
||||
|
||||
void AddQuestTimer(int32 quest_id);
|
||||
|
||||
@ -461,9 +546,9 @@ public:
|
||||
// finalize the spawn-in of the object in world, remove the item from player inventory, set the spawned in object item id (for future pickup)
|
||||
bool PopulateHouseSpawnFinalize();
|
||||
|
||||
void SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3=0.0f);
|
||||
void SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3 = 0.0f);
|
||||
|
||||
void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color=0xFF);
|
||||
void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color = 0xFF);
|
||||
|
||||
void SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...);
|
||||
void SendShowBook(Spawn* sender, string title, int8 language, vector<Item::BookPage*> pages);
|
||||
@ -480,7 +565,7 @@ public:
|
||||
|
||||
void AddWaypoint(string name, int8 type);
|
||||
void RemoveWaypoint(string name) {
|
||||
if (waypoints.count(name) > 0){
|
||||
if (waypoints.count(name) > 0) {
|
||||
waypoints.erase(name);
|
||||
}
|
||||
}
|
||||
@ -531,7 +616,7 @@ public:
|
||||
bool res = false;
|
||||
MSpellDetails.readlock(__FUNCTION__, __LINE__);
|
||||
std::map<int32, int32>::iterator itr = sent_spell_details.find(id);
|
||||
if(itr != sent_spell_details.end() && itr->second == tier)
|
||||
if (itr != sent_spell_details.end() && itr->second == tier)
|
||||
res = true;
|
||||
MSpellDetails.releasereadlock(__FUNCTION__, __LINE__);
|
||||
return res;
|
||||
@ -585,13 +670,13 @@ public:
|
||||
|
||||
void GiveQuestReward(Quest* quest, bool has_displayed = false);
|
||||
|
||||
void SendReplaceWidget(int32 widget_id, bool delete_widget, float x=0.0f, float y=0.0f, float z=0.0f, int32 grid_id=0);
|
||||
void SendReplaceWidget(int32 widget_id, bool delete_widget, float x = 0.0f, float y = 0.0f, float z = 0.0f, int32 grid_id = 0);
|
||||
void ProcessZoneIgnoreWidgets();
|
||||
|
||||
void SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time);
|
||||
int32 GetSpellVisualOverride(int32 spell_visual);
|
||||
|
||||
sint16 GetClientItemPacketOffset() { sint16 offset = -1; if(GetVersion() <= 373) { offset = -2; } return offset; }
|
||||
sint16 GetClientItemPacketOffset() { sint16 offset = -1; if (GetVersion() <= 373) { offset = -2; } return offset; }
|
||||
|
||||
int32 GetZoningID() { return zoning_id; }
|
||||
int32 GetZoningInstanceID() { return zoning_instance_id; }
|
||||
@ -601,6 +686,9 @@ public:
|
||||
void HandleGroupAcceptResponse(int8 result);
|
||||
void SetGroupOptionsReference(GroupOptions* options);
|
||||
void SendReceiveOffer(Client* client_target, int8 type, std::string name, int8 unknown2);
|
||||
|
||||
bool SendDialogChoice(int32 spawnID, const std::string& windowTextPrompt, const std::string& acceptText, const std::string& acceptCommand, const std::string& declineText, const std::string& declineCommand, int32 time, int8 textBox, int8 textBoxRequired, int32 maxLength);
|
||||
DialogManager dialog_manager;
|
||||
private:
|
||||
void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i);
|
||||
void SavePlayerImages();
|
||||
@ -694,7 +782,7 @@ private:
|
||||
std::atomic<int8> player_pos_change_count;
|
||||
int32 player_pos_timer;
|
||||
bool enabled_player_pos_timer;
|
||||
bool HandlePacket(EQApplicationPacket *app);
|
||||
bool HandlePacket(EQApplicationPacket* app);
|
||||
EQStream* eqs;
|
||||
bool quickbar_changed;
|
||||
ZoneServer* current_zone;
|
||||
|
@ -177,6 +177,11 @@ ZoneServer::ZoneServer(const char* name) {
|
||||
|
||||
lifetime_client_count = 0;
|
||||
|
||||
groupraidMinLevel = 0;
|
||||
groupraidMaxLevel = 0;
|
||||
groupraidAvgLevel = 0;
|
||||
groupraidFirstLevel = 0;
|
||||
|
||||
is_initialized = false;
|
||||
}
|
||||
|
||||
|
@ -1036,6 +1036,11 @@ private:
|
||||
mutable std::shared_mutex MIgnoredWidgets;
|
||||
std::map<int32, bool> ignored_widgets;
|
||||
Map* default_zone_map; // this is the map that npcs, ground spawns, so on use. May not be the same as the clients!
|
||||
|
||||
int32 groupraidMinLevel;
|
||||
int32 groupraidMaxLevel;
|
||||
int32 groupraidAvgLevel;
|
||||
int32 groupraidFirstLevel;
|
||||
public:
|
||||
Spawn* GetSpawn(int32 id);
|
||||
|
||||
@ -1168,6 +1173,29 @@ public:
|
||||
|
||||
void SendStateCommand(Spawn* spawn, int32 state);
|
||||
|
||||
int32 getGroupraidMinLevel() const {
|
||||
return groupraidMinLevel;
|
||||
}
|
||||
|
||||
int32 getGroupraidMaxLevel() const {
|
||||
return groupraidMaxLevel;
|
||||
}
|
||||
|
||||
int32 getGroupraidAvgLevel() const {
|
||||
return groupraidAvgLevel;
|
||||
}
|
||||
|
||||
int32 getGroupraidFirstLevel() const {
|
||||
return groupraidFirstLevel;
|
||||
}
|
||||
|
||||
void setGroupRaidLevels(int32 min_level, int32 max_level, int32 avg_level, int32 first_level) {
|
||||
groupraidMinLevel = min_level;
|
||||
groupraidMaxLevel = max_level;
|
||||
groupraidAvgLevel = avg_level;
|
||||
groupraidFirstLevel = first_level;
|
||||
}
|
||||
|
||||
int32 lifetime_client_count;
|
||||
int32 incoming_clients;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user