From 315ab1d0e7d29b16d6127b2f891289ce9783b080 Mon Sep 17 00:00:00 2001 From: Emagi Date: Mon, 18 Nov 2024 11:13:04 -0500 Subject: [PATCH] Project Nebulark Part 1, So much to list, this is a rough summary - Raid support and cross peer support for Isle of Refuge, DoF, KoS and AoM clients. - Zone Persistence added to non-instanced zones. - Commands: /whogroup, /whoraid, /raidinvite, /raid_looter, /kickfromgroup, /kickfromraid, /leaveraid, /split, /raidsay (rsay) added. - Cross peer zone and instance support - Cross tell support (along with ignore) - Cross ooc support - Cross group support (can chat, leave group, disband cross peers, update group options) - Cross who all support - houses/instances fixed no more cross objects/spawns/etc from other houses - houses now display characters name with the house zone description - 1000's of house items now properly work with wall/ceiling - debug messages removed from housing placement - Encounters locked to raid instead of group - group options restricted to raid leader - reload rules for following are peer wide: COMMAND_RELOADSTRUCTS COMMAND_RELOAD_QUESTS COMMAND_RELOAD_SPELLS COMMAND_RELOAD_ZONESCRIPTS COMMAND_RELOAD_FACTIONS COMMAND_RELOAD_MAIL COMMAND_RELOAD_GUILDS COMMAND_RELOAD_RULES COMMAND_RELOAD_STARTABILITIES COMMAND_RELOAD_VOICEOVERS COMMAND_RELOADSPAWNSCRIPTS COMMAND_RELOADREGIONSCRIPTS COMMAND_RELOADLUASYSTEM - special/static zones (always_loaded) are now defined by a peer_priority unsigned short (smallint(5)) in zones table. peer_priority = server_config world.peerpriority will spawn on that exe instance, if it is not available it is distributed to all peers. Using the value of 0 (assuming no peer has priority of 0) or 65535 will result in peer distribution of zones. server_config.json "WorldServer" block updated with the following (web peer port information), priority must be unique for EACH peer: "peeraddress": "10.1.1.2", "peerport": "9102", "peerpriority": "1", New Command Line Run Arguments for World Exe to override server_config.json values Allowed options: --worldaddress arg World address --internalworldaddress arg Internal world address --worldport arg (=0) Web world port --webworldaddress arg Web world address --webworldport arg (=0) Web world port --peerpriority arg (=0) Peer priority - fixed Isle of Refuge client group struct (raids added also) - new log category Peering - new LUA Functions AddRespawn(Zone, LocationID, RespawnTime) and CreatePersistedRespawn(LocationID, SpawnType, RespawnTime, ZoneID) --- server/WorldStructs.xml | 239 +- server/log_config.xml.example | 8 + source/WorldServer/Bots/BotCommands.cpp | 4 + source/WorldServer/Commands/Commands.cpp | 566 ++- source/WorldServer/Commands/Commands.h | 20 + .../WorldServer/Commands/ConsoleCommands.cpp | 6 +- source/WorldServer/Guilds/Guild.cpp | 133 +- source/WorldServer/Guilds/Guild.h | 14 +- source/WorldServer/Guilds/GuildDB.cpp | 33 + source/WorldServer/Items/Items.h | 1 + source/WorldServer/Items/ItemsDB.cpp | 3 +- source/WorldServer/Items/LootDB.cpp | 4 +- source/WorldServer/LoginServer.cpp | 157 +- source/WorldServer/LoginServer.h | 4 +- source/WorldServer/LuaFunctions.cpp | 64 +- source/WorldServer/LuaFunctions.h | 3 + source/WorldServer/LuaInterface.cpp | 7 +- source/WorldServer/NPC_AI.cpp | 30 +- source/WorldServer/Player.cpp | 218 +- source/WorldServer/Player.h | 14 +- source/WorldServer/PlayerGroups.cpp | 1269 +++++- source/WorldServer/PlayerGroups.h | 86 +- source/WorldServer/Spawn.cpp | 16 +- source/WorldServer/SpellProcess.cpp | 16 +- source/WorldServer/Web/HTTPSClient.cpp | 450 +++ source/WorldServer/Web/HTTPSClient.h | 61 + source/WorldServer/Web/HTTPSClientPool.cpp | 773 ++++ source/WorldServer/Web/HTTPSClientPool.h | 92 + source/WorldServer/Web/PeerManager.cpp | 772 ++++ source/WorldServer/Web/PeerManager.h | 227 ++ source/WorldServer/Web/WorldWeb.cpp | 1244 +++++- source/WorldServer/World.cpp | 782 +++- source/WorldServer/World.h | 63 +- source/WorldServer/WorldDatabase.cpp | 259 +- source/WorldServer/WorldDatabase.h | 6 + source/WorldServer/client.cpp | 3552 +++++++++-------- source/WorldServer/client.h | 25 +- source/WorldServer/makefile | 2 +- source/WorldServer/net.cpp | 130 +- source/WorldServer/net.h | 18 +- source/WorldServer/zoneserver.cpp | 221 +- source/WorldServer/zoneserver.h | 17 +- source/common/LogTypes.h | 9 + source/common/MiscFunctions.cpp | 29 +- source/common/version.h | 6 +- 45 files changed, 9311 insertions(+), 2342 deletions(-) create mode 100644 source/WorldServer/Web/HTTPSClient.cpp create mode 100644 source/WorldServer/Web/HTTPSClient.h create mode 100644 source/WorldServer/Web/HTTPSClientPool.cpp create mode 100644 source/WorldServer/Web/HTTPSClientPool.h create mode 100644 source/WorldServer/Web/PeerManager.cpp create mode 100644 source/WorldServer/Web/PeerManager.h diff --git a/server/WorldStructs.xml b/server/WorldStructs.xml index c80672b..cf9da76 100644 --- a/server/WorldStructs.xml +++ b/server/WorldStructs.xml @@ -1613,11 +1613,14 @@ to zero and treated like placeholders." /> - - + - + + + + + @@ -1784,6 +1787,190 @@ to zero and treated like placeholders." /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1917,7 +2104,7 @@ to zero and treated like placeholders." /> - + @@ -2071,8 +2258,7 @@ to zero and treated like placeholders." /> - - + @@ -2298,7 +2484,7 @@ to zero and treated like placeholders." /> - + @@ -2531,7 +2717,7 @@ to zero and treated like placeholders." /> - + @@ -3243,7 +3429,7 @@ to zero and treated like placeholders." /> - + @@ -7830,7 +8016,7 @@ to zero and treated like placeholders." /> - + @@ -7851,7 +8037,7 @@ to zero and treated like placeholders." /> - + @@ -7871,7 +8057,7 @@ to zero and treated like placeholders." /> - + @@ -7892,7 +8078,7 @@ to zero and treated like placeholders." /> - + @@ -11132,9 +11318,36 @@ to zero and treated like placeholders." /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/log_config.xml.example b/server/log_config.xml.example index 690f0ef..db1a110 100644 --- a/server/log_config.xml.example +++ b/server/log_config.xml.example @@ -360,4 +360,12 @@ + + + + + + + + diff --git a/source/WorldServer/Bots/BotCommands.cpp b/source/WorldServer/Bots/BotCommands.cpp index 494edc0..10934bb 100644 --- a/source/WorldServer/Bots/BotCommands.cpp +++ b/source/WorldServer/Bots/BotCommands.cpp @@ -583,6 +583,10 @@ void Commands::Command_Bot_Spawn(Client* client, Seperator* sep) { } client->GetPlayer()->SpawnedBots[bot_id] = bot->GetID(); + + if(bot->IsNPC()) { + ((NPC*)bot)->HaltMovement(); + } } else { client->Message(CHANNEL_ERROR, "Error spawning bot (%u)", bot_id); diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp index f6b4b43..caeb25a 100644 --- a/source/WorldServer/Commands/Commands.cpp +++ b/source/WorldServer/Commands/Commands.cpp @@ -49,6 +49,7 @@ along with EQ2Emulator. If not, see . #include "../classes.h" #include "../Transmute.h" #include "../Bots/Bot.h" +#include "../Web/PeerManager.h" extern WorldDatabase database; extern MasterSpellList master_spell_list; @@ -72,6 +73,7 @@ extern RuleManager rule_manager; extern MasterAAList master_aa_list; extern MasterRaceTypeList race_types_list; extern Classes classes; +extern PeerManager peer_manager; //devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp #if defined(__GNUC__) @@ -1921,6 +1923,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie world.SetReloadingSubsystem("Structs"); configReader.ReloadStructs(); world.RemoveReloadingSubSystem("Structs"); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -1931,6 +1934,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client_list.ReloadQuests(); zone_list.ReloadClientQuests(); world.RemoveReloadingSubSystem("Quests"); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -1943,6 +1947,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading NPC Spells Lists (Note: Must Reload Spawns/Repop to reset npc spells)..."); world.PurgeNPCSpells(); database.LoadNPCSpells(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler, 1); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); } else { @@ -1957,6 +1962,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie world.RemoveReloadingSubSystem("Spells"); world.PurgeNPCSpells(); database.LoadNPCSpells(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); } break; @@ -1979,6 +1985,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie if (lua_interface) lua_interface->DestroyZoneScripts(); world.RemoveReloadingSubSystem("ZoneScripts"); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -1997,18 +2004,21 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie master_faction_list.Clear(); database.LoadFactionList(); world.RemoveReloadingSubSystem("Factions"); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } case COMMAND_RELOAD_MAIL: { client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Mail..."); zone_list.ReloadMail(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } case COMMAND_RELOAD_GUILDS: { client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Guilds..."); world.ReloadGuilds(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -2022,6 +2032,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie case COMMAND_RELOAD_RULES: { client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Rules..."); database.LoadRuleSets(true); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -2035,6 +2046,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Starting Skills/Spells..."); world.PurgeStartingLists(); world.LoadStartingLists(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -2042,6 +2054,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Voiceovers..."); world.PurgeVoiceOvers(); world.LoadVoiceOvers(); + peer_manager.sendPeersMessage("/reloadcommand", command->handler); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); break; } @@ -3178,8 +3191,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } case COMMAND_GROUPSAY:{ GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); - if(sep && sep->arg[0] && gmi) + if(sep && sep->arg[0] && gmi) { world.GetGroupManager()->GroupChatMessage(gmi->group_id, client->GetPlayer(), client->GetPlayer()->GetCurrentLanguage(), sep->argplus[0]); + peer_manager.SendPeersChannelMessage(gmi->group_id, std::string(client->GetPlayer()->GetName()), std::string(sep->argplus[0]), CHANNEL_GROUP_SAY, client->GetPlayer()->GetCurrentLanguage()); + } break; } case COMMAND_GROUPINVITE: { @@ -3211,18 +3226,11 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), target); - - if (result == 0) { + + if (target && result == 0) { client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", target->GetName()); if (target_client) { - PacketStruct* packet = configReader.getStruct("WS_ReceiveOffer", target_client->GetVersion()); - if (packet) { - packet->setDataByName("type", 1); - packet->setDataByName("name", client->GetPlayer()->GetName()); - packet->setDataByName("unknown2", 1); - target_client->QueuePacket(packet->serialize()); - safe_delete(packet); - } + client->SendReceiveOffer(target_client, 1, std::string(client->GetPlayer()->GetName()), 1); } } else if (result == 1) @@ -3245,9 +3253,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie case COMMAND_GROUPDISBAND: { GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); - if (gmi) { // TODO: Leader check + if (gmi && gmi->leader) { // TODO: Leader check..DONE! :X // world.GetGroupManager()->SimpleGroupMessage(gmi->group_id, "Your group has been disbanded."); world.GetGroupManager()->RemoveGroup(gmi->group_id); + peer_manager.sendPeersDisbandGroup(gmi->group_id); } break; @@ -3419,24 +3428,68 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie break; } case COMMAND_GROUP_ACCEPT_INVITE: { - if((sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) || (!sep && client->GetVersion() <= 561)) { - int8 result = world.GetGroupManager()->AcceptInvite(client->GetPlayer()); - - if (result == 0) - client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have joined the group."); - else if (result == 1) - client->SimpleMessage(CHANNEL_GROUP_CHAT, "You do not have a pending invite."); - else if (result == 2) - client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - could not find leader."); - else - client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - unknown error."); - } + int8 result = 3; + std::string leader = world.GetGroupManager()->HasPendingInvite(client->GetPlayer()); + std::string playerName(client->GetPlayer()->GetName()); + Client* leader_client = client->GetCurrentZone()->GetClientByName((char*)leader.c_str()); + bool group_existed = false; + if(leader_client && leader_client->GetPlayer()->GetGroupMemberInfo()) { + group_existed = true; + } + if(client->GetPlayer()->GetGroupMemberInfo() && client->GetPlayer()->GetGroupMemberInfo()->leader) { + int8 raid_result = world.GetGroupManager()->AcceptRaidInvite(std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetGroupMemberInfo()->group_id); + if(raid_result == 1) { + GroupOptions options; + if(world.GetGroupManager()->GetDefaultGroupOptions(client->GetPlayer()->GetGroupMemberInfo()->group_id, &options)) { + std::vector raidGroups; + world.GetGroupManager()->GetRaidGroups(client->GetPlayer()->GetGroupMemberInfo()->group_id, &raidGroups); + peer_manager.sendPeersNewGroupRequest("", 0, client->GetPlayer()->GetGroupMemberInfo()->group_id, "", "", &options, "", &raidGroups, true); + } + world.GetGroupManager()->ClearGroupRaidLooterFlag(client->GetPlayer()->GetGroupMemberInfo()->group_id); + world.GetGroupManager()->SendGroupUpdate(client->GetPlayer()->GetGroupMemberInfo()->group_id); + break; + } + } + if(net.is_primary) { + int32 group_id = 0; + result = world.GetGroupManager()->AcceptInvite(client->GetPlayer(), &group_id, false); + client->HandleGroupAcceptResponse(result); + if(result == 0) { + GroupOptions options; + if(leader_client) { + if(!group_existed) { + leader_client->SetGroupOptionsReference(&options); + peer_manager.sendPeersNewGroupRequest("", 0, group_id, leader, playerName, &options); + } + + world.GetGroupManager()->AddGroupMember(group_id, leader_client->GetPlayer(), true); + world.GetGroupManager()->GroupMessage(leader_client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has joined the group.", playerName.c_str()); + world.GetGroupManager()->AddGroupMember(leader_client->GetPlayer()->GetGroupMemberInfo()->group_id, client->GetPlayer()); + } + } + } + else { + if(leader.size() < 1) { + client->HandleGroupAcceptResponse(1); + } + else { + Client* leader_client = client->GetCurrentZone()->GetClientByName((char*)leader.c_str()); + GroupOptions options; + if(leader_client) { + leader_client->SetGroupOptionsReference(&options); + world.GetGroupManager()->AddInvite(leader_client->GetPlayer(), client->GetPlayer()); + peer_manager.sendPrimaryNewGroupRequest(leader, playerName, client->GetPlayer()->GetID(), &options); + + } + else { + client->HandleGroupAcceptResponse(2); + } + } + } break; } case COMMAND_GROUP_DECLINE_INVITE: { - if(sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) { world.GetGroupManager()->DeclineInvite(client->GetPlayer()); // TODO: Add message to leader - } break; } case COMMAND_SUMMON:{ @@ -3519,8 +3572,9 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie int32 zone_id = 0; bool listSearch = false; bool isInstance = false; + bool notZoningCommand = false; ZoneServer* zsZone = 0; - + ZoneChangeDetails zone_details; if(sep && sep->arg[0][0]) { if(strncasecmp(sep->arg[0], "list", 4) == 0) @@ -3529,6 +3583,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie else if(strncasecmp(sep->arg[0], "active", 6) == 0) { zone_list.SendZoneList(client); + notZoningCommand = true; break; } @@ -3542,10 +3597,16 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie { PrintSep(sep, "ZONE LOCK"); - if(sep->IsNumber(1)) - zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); - else - zsZone = zone_list.Get(sep->arg[1], false, false, false); + if(sep->IsNumber(1)) { + if(zone_list.GetZone(&zone_details, atoul(sep->arg[1]), "", false, false, false, false)) { + zsZone = (ZoneServer*)zone_details.zonePtr; + } + } + else { + if(zone_list.GetZone(&zone_details, 0, std::string(sep->arg[1]), false, false, false, false)) { + zsZone = (ZoneServer*)zone_details.zonePtr; + } + } if( zsZone ) { @@ -3554,17 +3615,23 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } else client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be locked.", sep->arg[1]); - + notZoningCommand = true; break; } else if(strncasecmp(sep->arg[0], "unlock", 6) == 0) { PrintSep(sep, "ZONE UNLOCK"); - if(sep->IsNumber(1)) - zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); - else - zsZone = zone_list.Get(sep->arg[1], false, false, false); + if(sep->IsNumber(1)) { + if(zone_list.GetZone(&zone_details, atoul(sep->arg[1]), "", false, false, false, false)) { + zsZone = (ZoneServer*)zone_details.zonePtr; + } + } + else { + if(zone_list.GetZone(&zone_details, 0, std::string(sep->arg[1]), false, false, false, false)) { + zsZone = (ZoneServer*)zone_details.zonePtr; + } + } if( zsZone ) { @@ -3573,6 +3640,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } else client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be unlocked.", sep->arg[1]); + notZoningCommand = true; break; } else @@ -3590,15 +3658,12 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } if(instanceID > 0) { - ZoneServer* zsInstance = zone_list.GetByInstanceID(instanceID); - - if(zsInstance != NULL) - { - instanceID = zsInstance->GetInstanceID(); - zone = zsInstance->GetZoneName(); - zone_id = zsInstance->GetZoneID(); - isInstance = true; + if(zone_list.GetZoneByInstance(&zone_details, instanceID, 0, true)) { + instanceID = zone_details.instanceId; + zone = zone_details.zoneName; + zone_id = zone_details.zoneId; } + isInstance = true; } } @@ -3616,7 +3681,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie { client->Message(CHANNEL_COLOR_YELLOW,"Zoning to %s...", zonestr); if(isInstance) - client->Zone(instanceID,true,true,false); + client->Zone(&zone_details,(ZoneServer*)zone_details.zonePtr,true,false); else client->Zone(zonestr); } @@ -3765,7 +3830,9 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) break; - client->SendMoveObjectMode(spawn, 0); + Item* item = master_item_list.GetItem(spawn->GetPickupItemID()); + + client->SendMoveObjectMode(spawn, (item && item->houseitem_info) ? item->houseitem_info->house_location : 0); break; } case COMMAND_PICKUP: @@ -5710,6 +5777,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie case COMMAND_ASSIST: { Command_Assist(client, sep); break; } case COMMAND_TARGET: { Command_Target(client, sep); break; } case COMMAND_TARGET_PET: { Command_Target_Pet(client, sep); break; } + case COMMAND_WHOGROUP: { Command_WhoGroup(client, sep); break; } + case COMMAND_WHORAID: { Command_WhoRaid(client, sep); break; } + case COMMAND_RAIDINVITE: { Command_RaidInvite(client, sep); break; } + case COMMAND_RAID_LOOTER: { Command_Raid_Looter(client, sep); break; } + case COMMAND_KICKFROMGROUP: { Command_KickFromGroup(client, sep); break; } + case COMMAND_KICKFROMRAID: { Command_KickFromRaid(client, sep); break; } + case COMMAND_LEAVERAID: { Command_LeaveRaid(client, sep); break; } + case COMMAND_SPLIT: { Command_Split(client, sep); break; } + case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; } default: { LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str()); @@ -6263,12 +6339,20 @@ void Commands::Command_Guild(Client* client, Seperator* sep) if (strncmp(command, "rank_name", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && guild) guild->SetRankName(atoi(sep->arg[1]), sep->argplus[2]); - else if (strncmp(command, "rank_permission", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + else if (strncmp(command, "rank_permission", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) { guild->SetPermission(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); - else if (strncmp(command, "filter_event", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + peer_manager.sendPeersGuildPermission(guild->GetID(), atoul(sep->arg[1]), atoul(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + } + else if (strncmp(command, "filter_event", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) { guild->SetEventFilter(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); - else if (strncmp(command, "kick", length) == 0 && sep->arg[1] && guild) - guild->KickGuildMember(client, sep->arg[1]); + peer_manager.sendPeersGuildEventFilter(guild->GetID(), atoul(sep->arg[1]), atoul(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + } + else if (strncmp(command, "kick", length) == 0 && sep->arg[1] && guild) { + int32 character_id = guild->KickGuildMember(client, sep->arg[1]); + if(character_id > 0) { + peer_manager.sendPeersRemoveGuildMember(character_id, guild->GetID(), std::string(client->GetPlayer()->GetName())); + } + } else if (strncmp(command, "demote", length) == 0 && sep->arg[1] && guild) guild->DemoteGuildMember(client, sep->arg[1]); else if (strncmp(command, "promote", length) == 0 && sep->arg[1] && guild) @@ -6381,9 +6465,19 @@ void Commands::Command_Guild(Client* client, Seperator* sep) else if (strncmp(command, "create", length) == 0 && sep->arg[1]) { const char* guild_name = sep->argplus[1]; - - if (!guild_list.GetGuild(guild_name)) - world.CreateGuild(guild_name, client, client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + if(!guild_name || strlen(guild_name) < 4) { + client->SimpleMessage(CHANNEL_NARRATIVE, "Guild name is too short."); + } + else if (!guild_list.GetGuild(guild_name)) { + if(net.is_primary) { + int32 guildID = world.CreateGuild(guild_name, client, client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + if(guildID > 0) + peer_manager.sendPeersCreateGuild(guildID); + } + else { + peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), std::string(client->GetPlayer()->GetName())); + } + } else client->SimpleMessage(CHANNEL_NARRATIVE, "A guild with that name already exists."); } @@ -6479,8 +6573,11 @@ void Commands::Command_GuildSay(Client* client, Seperator* sep) if (guild) { - if (sep && sep->arg[0]) - guild->HandleGuildSay(client, sep->argplus[0]); + if (sep && sep->arg[0]) { + bool success = guild->HandleGuildSay(client, sep->argplus[0]); + if(success) + peer_manager.SendPeersGuildChannelMessage(guild->GetID(), std::string(client->GetPlayer()->GetName()), std::string(sep->argplus[0]), CHANNEL_GUILD_SAY, client->GetPlayer()->GetCurrentLanguage()); + } } else client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); @@ -6497,8 +6594,11 @@ void Commands::Command_OfficerSay(Client* client, Seperator* sep) if (guild) { - if (sep && sep->arg[0]) - guild->HandleOfficerSay(client, sep->argplus[0]); + if (sep && sep->arg[0]) { + bool success = guild->HandleOfficerSay(client, sep->argplus[0]); + if(success) + peer_manager.SendPeersGuildChannelMessage(guild->GetID(), std::string(client->GetPlayer()->GetName()), std::string(sep->argplus[0]), CHANNEL_OFFICER_SAY, client->GetPlayer()->GetCurrentLanguage()); + } } else client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); @@ -6581,8 +6681,10 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep) if (sep && sep->arg[0]) { const char* guild_name = sep->arg[0]; - - if (!guild_list.GetGuild(guild_name)) + if(!guild_name || strlen(guild_name) < 4) { + client->Message(CHANNEL_COLOR_YELLOW, "Guild name is too short."); + } + else if (!guild_list.GetGuild(guild_name)) { bool ret = false; @@ -6592,7 +6694,14 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep) if (to_client) { - world.CreateGuild(guild_name, to_client); + if(net.is_primary) { + int32 guildID = world.CreateGuild(guild_name, to_client, to_client->GetPlayer()->GetGroupMemberInfo() ? to_client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + if(guildID > 0) + peer_manager.sendPeersCreateGuild(guildID); + } + else { + peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), std::string(to_client->GetPlayer()->GetName())); + } ret = true; } else @@ -6604,13 +6713,27 @@ void Commands::Command_GuildsCreate(Client* client, Seperator* sep) if (to_client) { - world.CreateGuild(guild_name, to_client); + if(net.is_primary) { + int32 guildID = world.CreateGuild(guild_name, to_client, to_client->GetPlayer()->GetGroupMemberInfo() ? to_client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + if(guildID > 0) + peer_manager.sendPeersCreateGuild(guildID); + } + else { + peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), std::string(to_client->GetPlayer()->GetName())); + } ret = true; } } else { - world.CreateGuild(guild_name); + if(net.is_primary) { + int32 guildID = world.CreateGuild(guild_name); + if(guildID > 0) + peer_manager.sendPeersCreateGuild(guildID); + } + else { + peer_manager.sendPrimaryCreateGuildRequest(std::string(guild_name), ""); + } ret = true; } @@ -6734,29 +6857,21 @@ void Commands::Command_GuildsRemove(Client* client, Seperator* sep) if (found) { Client* to_client = 0; - - if (sep->arg[1] && strlen(sep->arg[1]) > 0) - to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + char* charName = nullptr; + if(sep->arg[1][0]) + charName = sep->arg[1]; else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) - to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); - - if (to_client) - { - Player* to_player = to_client->GetPlayer(); - if (to_player->GetGuild()) - { - if (to_player->GetGuild() == guild) - { - guild->KickGuildMember(client, to_player->GetName()); - } - else - client->Message(CHANNEL_COLOR_YELLOW, "%s is not in the guild '%s'.", to_player->GetName(), guild->GetName()); - } - else - client->Message(CHANNEL_COLOR_YELLOW, "%s is not in a guild.", to_player->GetName()); - } + charName = ((Player*)client->GetPlayer()->GetTarget())->GetName(); else - client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to invite to the guild.", sep->arg[1]); + { + client->Message(CHANNEL_COLOR_YELLOW, "Missing player name or not a valid target to remove from the guild."); + return; + } + int32 character_id = guild->KickGuildMember(client, charName); + if(character_id > 0) + peer_manager.sendPeersRemoveGuildMember(character_id, guild->GetID(), std::string(client->GetPlayer()->GetName())); + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to remove from the guild.", charName); } } else @@ -10775,6 +10890,31 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) { client->QueuePacket(packet->serialize()); safe_delete(packet); } + else if(atoi(sep->arg[0]) == 35) { + if(client->GetPlayer()->GetGroupMemberInfo() && client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) { + Entity* target = (Entity*)client->GetPlayer()->GetTarget(); + if(target->GetGroupMemberInfo()) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(client->GetPlayer()->GetGroupMemberInfo()->group_id); + PlayerGroup* group2 = world.GetGroupManager()->GetGroup(target->GetGroupMemberInfo()->group_id); + if(group && group2) { + group->AddGroupToRaid(group2->GetID()); + group2->AddGroupToRaid(group->GetID()); + } + } + } + } + else if(atoi(sep->arg[0]) == 36) { + EQ2Packet* packet = client->GetPlayer()->GetRaidUpdatePacket(client->GetVersion()); + if(packet) { + client->QueuePacket(packet); + } + } + else if(atoi(sep->arg[0]) == 37) { + Guild* guild = client->GetPlayer()->GetGuild(); + if(guild) + guild->SendGuildMemberList(); + + } } else { PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion()); @@ -11093,7 +11233,10 @@ void Commands::Command_ZoneSafeCoords(Client *client, Seperator *sep) if (zone_id > 0) { - zone = zone_list.Get(zone_id, false, false, false); + ZoneChangeDetails zone_details; + if(zone_list.GetZone(&zone_details, zone_id, "", false, false, false)) { + zone = (ZoneServer*)zone_details.zonePtr; + } if (zone) { zone->SetSafeX(client->GetPlayer()->GetX()); @@ -11162,18 +11305,21 @@ void Commands::Command_ZoneSet(Client* client, Seperator* sep) { ZoneServer* zone = 0; int32 zone_id = 0; - + ZoneChangeDetails zone_details; if (sep->IsNumber(0) && atoi(sep->arg[0]) > 0) { - zone_id = atoi(sep->arg[0]); - zone = zone_list.Get(atoi(sep->arg[0]), false, false, false); + zone_id = atoul(sep->arg[0]); + if(zone_list.GetZone(&zone_details, zone_id, "", false, false, false, false)) { + zone = (ZoneServer*)zone_details.zonePtr; + } } else { zone_id = database.GetZoneID(sep->arg[0]); - if (zone_id > 0) - zone = zone_list.Get(sep->arg[0], false, false, false); + if(zone_list.GetZone(&zone_details, zone_id, "", false, false, false, false)) { + zone = (ZoneServer*)zone_details.zonePtr; + } } if (zone_id > 0) @@ -12379,3 +12525,245 @@ void Commands::Command_Target_Pet(Client* client, Seperator* sep) { } } + +/* + Function: Command_WhoGroup() + Purpose : Lists all members of current group + Example : /whogroup +*/ +void Commands::Command_WhoGroup(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + if(player->GetGroupMemberInfo()) { + world.GetGroupManager()->SendWhoGroupMembers(client, player->GetGroupMemberInfo()->group_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "You are not currently in a group."); + } +} + +/* + Function: Command_WhoRaid() + Purpose : Lists all members of raid + Example : /whoraid +*/ +void Commands::Command_WhoRaid(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + if(player->GetGroupMemberInfo()) { + world.GetGroupManager()->SendWhoRaidMembers(client, player->GetGroupMemberInfo()->group_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "You are not currently in a group or raid."); + } +} + +/* + Function: Command_RaidInvite() + Purpose : Invites a group to the raid + Example : /raidinvite +*/ +void Commands::Command_RaidInvite(Client* client, Seperator* sep) { + Entity* target = nullptr; + if( sep && sep->arg[0] ) { + Client* target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client) + target = (Entity*)target_client->GetPlayer(); + } + if(!target) { + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) + target = (Entity*)client->GetPlayer()->GetTarget(); + } + world.GetGroupManager()->SendRaidInvite(client, target); +} + +/* + Function: Command_Raid_Looter() + Purpose : Adds a looter to the raid loot list + Example : /raid_looter +*/ +void Commands::Command_Raid_Looter(Client* client, Seperator* sep) { + if(!client->GetPlayer()->GetGroupMemberInfo() || client->GetPlayer()->GetGroupMemberInfo()->leader) + return; + Entity* target = nullptr; + if( sep && sep->arg[0] ) { + Client* target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client) + target = (Entity*)target_client->GetPlayer(); + } + if(!target) { + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) + target = (Entity*)client->GetPlayer()->GetTarget(); + } + + bool isLeaderRaid = world.GetGroupManager()->IsInRaidGroup(client->GetPlayer()->GetGroupMemberInfo()->group_id, client->GetPlayer()->GetGroupMemberInfo()->group_id, true); + if(isLeaderRaid && target && target->IsEntity()) { + if(((Entity*)target)->GetGroupMemberInfo() && world.GetGroupManager()->IsInRaidGroup(client->GetPlayer()->GetGroupMemberInfo()->group_id, ((Entity*)target)->GetGroupMemberInfo()->group_id, false)) { + if(((Entity*)target)->GetGroupMemberInfo()->is_raid_looter) { + client->Message(CHANNEL_COLOR_YELLOW, "%s removed as a raid looter.", target->GetName()); + ((Entity*)target)->GetGroupMemberInfo()->is_raid_looter = false; + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "%s added as a raid looter.", target->GetName()); + ((Entity*)target)->GetGroupMemberInfo()->is_raid_looter = true; + } + } + } +} + +/* + Function: Command_KickFromGroup() + Purpose : Kick a player from a group + Example : /kickfromgroup +*/ +void Commands::Command_KickFromGroup(Client* client, Seperator* sep) { + Entity* target = nullptr; + Client* target_client = nullptr; + if( sep && sep->arg[0] ) { + target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client) { + target = target_client->GetPlayer(); + } + } + if(!target) { + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) + target = (Entity*)client->GetPlayer()->GetTarget(); + + if(target && target->IsPlayer()) + target_client = ((Player*)target)->GetClient(); + } + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + + if (gmi && gmi->leader && target && target->GetGroupMemberInfo() && gmi->group_id == target->GetGroupMemberInfo()->group_id) { + int32 group_id = gmi->group_id; + world.GetGroupManager()->RemoveGroupMember(group_id, target); + if (!world.GetGroupManager()->IsGroupIDValid(group_id)) { + // leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName()); + } + else { + world.GetGroupManager()->GroupMessage(group_id, "%s has been removed from the group.", target->GetName()); + } + + if(target_client) + target_client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have been kicked from the group"); + } +} + +/* + Function: Command_KickFromRaid() + Purpose : Kick a group from a raid + Example : /kickfromraid +*/ +void Commands::Command_KickFromRaid(Client* client, Seperator* sep) { + Entity* target = nullptr; + Client* target_client = nullptr; + if( sep && sep->arg[0] ) { + target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client) { + target = target_client->GetPlayer(); + } + } + if(!target) { + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) + target = (Entity*)client->GetPlayer()->GetTarget(); + + if(target && target->IsPlayer()) + target_client = ((Player*)target)->GetClient(); + } + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if(gmi && gmi->leader && target && target->GetGroupMemberInfo() && world.GetGroupManager()->IsInRaidGroup(gmi->group_id, target->GetGroupMemberInfo()->group_id, false) && + world.GetGroupManager()->IsInRaidGroup(gmi->group_id, gmi->group_id, true)) { + GroupOptions goptions; + world.GetGroupManager()->GetDefaultGroupOptions(gmi->group_id, &goptions); + world.GetGroupManager()->RemoveGroupFromRaid(gmi->group_id, target->GetGroupMemberInfo()->group_id); + std::vector raidGroups; + world.GetGroupManager()->GetRaidGroups(gmi->group_id, &raidGroups); + peer_manager.sendPeersNewGroupRequest("", 0, gmi->group_id, "", "", &goptions, "", &raidGroups, true); + std::vector emptyRaid; + peer_manager.sendPeersNewGroupRequest("", 0, target->GetGroupMemberInfo()->group_id, "", "", &goptions, "", &emptyRaid, true); + } +} + +/* + Function: Command_LeaveRaid() + Purpose : Leave a raid + Example : /leaveraid +*/ +void Commands::Command_LeaveRaid(Client* client, Seperator* sep) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + int32 orig_group_id = 0; + if(gmi && gmi->leader && world.GetGroupManager()->IsInRaidGroup(gmi->group_id, gmi->group_id, false)) { + orig_group_id = gmi->group_id; + GroupOptions goptions; + world.GetGroupManager()->GetDefaultGroupOptions(gmi->group_id, &goptions); + std::vector raidGroups; + world.GetGroupManager()->GetRaidGroups(gmi->group_id, &raidGroups); + std::vector::iterator cur_group_itr = std::find(raidGroups.begin(), raidGroups.end(), gmi->group_id); + if(cur_group_itr != raidGroups.end()) + raidGroups.erase(cur_group_itr); + + bool sendEmpty = false; + std::vector emptyRaid; + if(raidGroups.size() < 2) { + sendEmpty = true; + } + + for(cur_group_itr = raidGroups.begin(); cur_group_itr != raidGroups.end(); cur_group_itr++) { + if(sendEmpty) { + world.GetGroupManager()->ClearGroupRaid((*cur_group_itr)); + peer_manager.sendPeersNewGroupRequest("", 0, (*cur_group_itr), "", "", &goptions, "", &emptyRaid, true); + world.GetGroupManager()->SendGroupUpdate((*cur_group_itr), nullptr, true); + } + else { + world.GetGroupManager()->ReplaceRaidGroups((*cur_group_itr), &raidGroups); + } + } + + if(!sendEmpty) { + peer_manager.sendPeersNewGroupRequest("", 0, orig_group_id, "", "", &goptions, "", &raidGroups, true); + } + + world.GetGroupManager()->ClearGroupRaid(orig_group_id); + world.GetGroupManager()->SendGroupUpdate(orig_group_id, nullptr, true); + + peer_manager.sendPeersNewGroupRequest("", 0, orig_group_id, "", "", &goptions, "", &emptyRaid, true); + } +} + +/* + Function: Command_Split() + Purpose : split coin to group + Example : /split {plat} {gold} {silver} {copper} +*/ +void Commands::Command_Split(Client* client, Seperator* sep) { + // int32 item_id = atoul(sep->arg[0,1,2,3]); + int32 plat = 0, gold = 0, silver = 0, copper = 0; + if(sep->IsNumber(0)) + plat = atoul(sep->arg[0]); + if(sep->IsNumber(1)) + gold = atoul(sep->arg[1]); + if(sep->IsNumber(2)) + silver = atoul(sep->arg[2]); + if(sep->IsNumber(3)) + copper = atoul(sep->arg[3]); + + world.GetGroupManager()->SplitWithGroupOrRaid(client, plat, gold, silver, copper); +} + +/* + Function: Command_RaidSay() + Purpose : Speak to raid members + Example : /raidsay {message}, /rsay {message} +*/ +void Commands::Command_RaidSay(Client* client, Seperator* sep) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if(sep && sep->arg[0] && gmi) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + int32 spawn_group_id = gmi->group_id; + PlayerGroup* spawn_group = world.GetGroupManager()->GetGroup(spawn_group_id); + bool israidgroup = (spawn_group && spawn_group->IsGroupRaid()); + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + if(israidgroup) { + world.GetGroupManager()->GroupChatMessage(gmi->group_id, client->GetPlayer(), client->GetPlayer()->GetCurrentLanguage(), sep->argplus[0], CHANNEL_RAID_SAY); + peer_manager.SendPeersChannelMessage(gmi->group_id, std::string(client->GetPlayer()->GetName()), std::string(sep->argplus[0]), CHANNEL_RAID_SAY, client->GetPlayer()->GetCurrentLanguage()); + } + } +} \ No newline at end of file diff --git a/source/WorldServer/Commands/Commands.h b/source/WorldServer/Commands/Commands.h index bea8f47..759fbec 100644 --- a/source/WorldServer/Commands/Commands.h +++ b/source/WorldServer/Commands/Commands.h @@ -454,6 +454,16 @@ public: void Command_Assist(Client* client, Seperator* sep); void Command_Target(Client* client, Seperator* sep); void Command_Target_Pet(Client* client, Seperator* sep); + + void Command_WhoGroup(Client* client, Seperator* sep); + void Command_WhoRaid(Client* client, Seperator* sep); + void Command_RaidInvite(Client* client, Seperator* sep); + void Command_Raid_Looter(Client* client, Seperator* sep); + void Command_KickFromGroup(Client* client, Seperator* sep); + void Command_KickFromRaid(Client* client, Seperator* sep); + void Command_LeaveRaid(Client* client, Seperator* sep); + void Command_Split(Client* client, Seperator* sep); + void Command_RaidSay(Client* client, Seperator* sep); // AA Commands void Get_AA_Xml(Client* client, Seperator* sep); @@ -949,6 +959,16 @@ private: #define COMMAND_SET_CONSUME_FOOD 538 +#define COMMAND_WHOGROUP 539 // /whogroup /whog +#define COMMAND_WHORAID 540 // /whoraid /whor +#define COMMAND_RAIDINVITE 541 // /raidinvite +#define COMMAND_RAID_LOOTER 542 // /raid_looter +#define COMMAND_KICKFROMGROUP 543 // /kickfromgroup +#define COMMAND_KICKFROMRAID 544 // /kickfromraid +#define COMMAND_LEAVERAID 545 // /leaveraid +#define COMMAND_SPLIT 546 // /split +#define COMMAND_RAIDSAY 547 // /raidsay /rsay + #define GET_AA_XML 750 #define ADD_AA 751 #define COMMIT_AA_PROFILE 752 diff --git a/source/WorldServer/Commands/ConsoleCommands.cpp b/source/WorldServer/Commands/ConsoleCommands.cpp index 648ec93..e46ba9f 100644 --- a/source/WorldServer/Commands/ConsoleCommands.cpp +++ b/source/WorldServer/Commands/ConsoleCommands.cpp @@ -359,14 +359,14 @@ bool ConsoleZoneCommand(Seperator *sep) { if( sep->IsNumber(2) ) { - zone = zone_list.Get(atoi(sep->arg[2]), false, false, false); - if( zone ) + ZoneChangeDetails zone_details; + if( zone_list.GetZone(&zone_details, atoi(sep->arg[2]), "", false, false, false) ) { printf("> Zone status for zone ID %i...\n", atoi(sep->arg[2])); printf("============================================================================================\n"); printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value"); printf("============================================================================================\n"); - printf("| %30s | %10s | %42s |\n", zone->GetZoneName(), "locked", zone->GetZoneLockState() ? "true" : "false"); + printf("| %30s | %10s | %42s |\n", zone_details.zoneName, "locked", zone_details.lockState ? "true" : "false"); } else { diff --git a/source/WorldServer/Guilds/Guild.cpp b/source/WorldServer/Guilds/Guild.cpp index 7198d26..a45cebe 100644 --- a/source/WorldServer/Guilds/Guild.cpp +++ b/source/WorldServer/Guilds/Guild.cpp @@ -29,12 +29,14 @@ #include "../WorldDatabase.h" #include "../../common/Log.h" #include "../Rules/Rules.h" +#include "../Web/PeerManager.h" extern ConfigReader configReader; extern ZoneList zone_list; extern WorldDatabase database; extern World world; extern RuleManager rule_manager; +extern PeerManager peer_manager; /*************************************************************************************************************************************************** * GUILD @@ -275,7 +277,7 @@ int8 Guild::GetRecruitingDescTag(int8 index) { return ret; } -bool Guild::SetPermission(int8 rank, int8 permission, int8 value, bool send_packet) { +bool Guild::SetPermission(int8 rank, int8 permission, int8 value, bool send_packet, bool save_needed) { bool ret = false; if (value == 0 || value == 1) { @@ -287,7 +289,8 @@ bool Guild::SetPermission(int8 rank, int8 permission, int8 value, bool send_pack if (ret && send_packet) { LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Permissions - Rank: %i, Permission: %i, Value: %i", rank, permission, value); SendGuildUpdate(); - ranks_save_needed = true; + if(save_needed) + ranks_save_needed = true; } return ret; } @@ -301,7 +304,7 @@ int8 Guild::GetPermission(int8 rank, int8 permission) { return ret; } -bool Guild::SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet) { +bool Guild::SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet, bool save_needed) { bool ret = false; if ((category == GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY || category == GUILD_EVENT_FILTER_CATEGORY_BROADCAST) && (value == 0 || value == 1)) { @@ -313,7 +316,8 @@ bool Guild::SetEventFilter(int8 event_id, int8 category, int8 value, bool send_p if (ret && send_packet) { LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Event Filter - EventID: %i, Category: %i, Value: %i", event_id, category, value); SendGuildUpdate(); - event_filters_save_needed = true; + if(save_needed) + event_filters_save_needed = true; } return ret; } @@ -712,6 +716,36 @@ bool Guild::AddNewGuildMember(Client *client, const char *invited_by, int8 rank) } member_save_needed = true; + + peer_manager.sendPeersAddGuildMember(gm->character_id, GetID(), (invited_by != nullptr) ? std::string(invited_by) : "", gm->join_date, rank); + } + + return true; +} + +bool Guild::AddNewGuildMember(int32 characterID, const char *invited_by, int32 join_timestamp, int8 rank) { + GuildMember *gm; + + if (members.count(characterID) == 0) { + gm = new GuildMember; + bool foundMember = peer_manager.GetClientGuildDetails(characterID, gm); + if(!foundMember) { + LogWrite(GUILD__ERROR, 0, "Guilds", "FAILED TO FIND MEMBER: %s invited %s to join guild: %s", invited_by, gm->name, GetName()); + safe_delete(gm); + return false; + } + gm->rank = rank; + gm->join_date = join_timestamp; + gm->last_login_date = gm->join_date; + mMembers.writelock(__FUNCTION__, __LINE__); + members[characterID] = gm; + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + if (invited_by) { + AddNewGuildEvent(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", Timer::GetUnixTimeStamp(), true, gm->name, invited_by, GetName()); + SendMessageToGuild(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", gm->name, invited_by, GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s invited %s to join guild: %s", invited_by, gm->name, GetName()); + } } return true; @@ -848,20 +882,21 @@ bool Guild::PromoteGuildMember(Client *client, const char *name, bool send_packe return ret; } -bool Guild::KickGuildMember(Client *client, const char *name, bool send_packet) { +int32 Guild::KickGuildMember(Client *client, const char *name, bool send_packet) { GuildMember *gm; Client *kicked_client; const char *kicker_name; - + int32 character_id = 0; assert(client); assert(name); if (!(gm = GetGuildMember(name))) - return false; + return 0; kicker_name = client->GetPlayer()->GetName(); - + character_id = gm->character_id; + if (!strncmp(kicker_name, gm->name, sizeof(gm->name))) { AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", Timer::GetUnixTimeStamp(), true, gm->name); SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", gm->name); @@ -897,7 +932,7 @@ bool Guild::KickGuildMember(Client *client, const char *name, bool send_packet) safe_delete_array(gm->recruiter_picture_data); safe_delete(gm); - return true; + return character_id; } bool Guild::InvitePlayer(Client *client, const char *name, bool send_packet) { @@ -1853,7 +1888,10 @@ void Guild::SendGuildMemberList(Client* client) { } mMembers.releasereadlock(__FUNCTION__, __LINE__); //DumpPacket(packet->serialize()); - client->QueuePacket(packet->serialize()); + //packet->PrintPacket(); + EQ2Packet* pack = packet->serialize(); + //DumpPacket(pack); + client->QueuePacket(pack); safe_delete(packet); } } @@ -2179,7 +2217,7 @@ void Guild::SendGuildRecruiterInfo(Client* client, Player* player) { } } -void Guild::HandleGuildSay(Client* sender, const char* message) { +bool Guild::HandleGuildSay(Client* sender, const char* message) { map::iterator itr; GuildMember *gm; @@ -2189,11 +2227,11 @@ void Guild::HandleGuildSay(Client* sender, const char* message) { assert(message); if (!(gm = GetGuildMemberOnline(sender))) - return; + return false; if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT)) { sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in guild chat."); - return; + return false; } mMembers.readlock(__FUNCTION__, __LINE__); @@ -2206,9 +2244,30 @@ void Guild::HandleGuildSay(Client* sender, const char* message) { } mMembers.releasereadlock(__FUNCTION__, __LINE__); LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Say"); + + return true; } -void Guild::HandleOfficerSay(Client* sender, const char* message) { +void Guild::HandleGuildSay(std::string senderName, const char* message, int8 language) { + + map::iterator itr; + GuildMember *gm; + + assert(message); + Client* client = 0; + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_GUILD_CHAT)) + client->GetCurrentZone()->HandleChatMessage(senderName, client->GetPlayer()->GetName(), CHANNEL_GUILD_SAY, message, 0, 0, language); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Say"); +} + +bool Guild::HandleOfficerSay(Client* sender, const char* message) { map::iterator itr; GuildMember *gm; @@ -2218,11 +2277,11 @@ void Guild::HandleOfficerSay(Client* sender, const char* message) { assert(message); if (!(gm = GetGuildMemberOnline(sender))) - return; + return false; if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT)) { sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in officer chat."); - return; + return false; } mMembers.readlock(__FUNCTION__, __LINE__); @@ -2235,6 +2294,25 @@ void Guild::HandleOfficerSay(Client* sender, const char* message) { } mMembers.releasereadlock(__FUNCTION__, __LINE__); LogWrite(GUILD__DEBUG, 0, "Guilds", "Officer Say"); + return true; +} + +void Guild::HandleOfficerSay(std::string senderName, const char* message, int8 language) { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_OFFICER_CHAT)) + client->GetCurrentZone()->HandleChatMessage(senderName, client->GetPlayer()->GetName(), CHANNEL_OFFICER_SAY, message, 0, 0, language); + + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Officer Say"); } void Guild::SendMessageToGuild(int8 event_type, const char* message, ...) { @@ -2260,6 +2338,29 @@ void Guild::SendMessageToGuild(int8 event_type, const char* message, ...) { LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent message to entire guild."); } +void Guild::SendGuildChatMessage(const char* message, ...) { + + map::iterator itr; + Client *client; + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (event_filters.Get(itr->second->rank)->Get(GUILD_EVENT_FILTER_CATEGORY_BROADCAST)) + client->SimpleMessage(CHANNEL_GUILD_CHAT, buffer); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent message to entire guild."); +} + string Guild::GetEpicMobDeathMessage(const char* player_name, const char* mob_name) { char message[256]; diff --git a/source/WorldServer/Guilds/Guild.h b/source/WorldServer/Guilds/Guild.h index 9ce4923..cad0f57 100644 --- a/source/WorldServer/Guilds/Guild.h +++ b/source/WorldServer/Guilds/Guild.h @@ -297,9 +297,9 @@ public: int8 GetRecruitingPlayStyle() const {return recruiting_play_style;} bool SetRecruitingDescTag(int8 index, int8 tag, bool send_packet = true); int8 GetRecruitingDescTag(int8 index); - bool SetPermission(int8 rank, int8 permission, int8 value, bool send_packet = true); + bool SetPermission(int8 rank, int8 permission, int8 value, bool send_packet = true, bool save_needed = true); int8 GetPermission(int8 rank, int8 permission); - bool SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet = true); + bool SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet = true, bool save_needed = true); int8 GetEventFilter(int8 event_id, int8 category); int32 GetNumUniqueAccounts(); int32 GetNumRecruiters(); @@ -321,12 +321,13 @@ public: bool SetGuildMemberNote(const char* name, const char* note, bool send_packet = true); bool SetGuildOfficerNote(const char* name, const char* note, bool send_packet = true); bool AddNewGuildMember(Client* client, const char* invited_by = 0, int8 rank = GUILD_RANK_RECRUIT); + bool AddNewGuildMember(int32 characterID, const char *invited_by, int32 join_timestamp, int8 rank); bool AddGuildMember(GuildMember* guild_member); void RemoveGuildMember(int32 character_id, bool send_packet = true); void RemoveAllGuildMembers(); bool DemoteGuildMember(Client* client, const char* name, bool send_packet = true); bool PromoteGuildMember(Client* client, const char* name, bool send_packet = true); - bool KickGuildMember(Client* client, const char* name, bool send_packet = true); + int32 KickGuildMember(Client* client, const char* name, bool send_packet = true); bool InvitePlayer(Client* client, const char* name, bool send_packet = true); bool AddPointsToAll(Client* client, float points, const char* comment = 0, bool send_packet = true); bool AddPointsToAllOnline(Client* client, float points, const char* comment = 0, bool send_packet = true); @@ -372,9 +373,12 @@ public: void SendGuildRecruitingDetails(Client* client); void SendGuildRecruitingImages(Client* client); void SendGuildRecruiterInfo(Client* client, Player* player); - void HandleGuildSay(Client* sender, const char* message); - void HandleOfficerSay(Client* sender, const char* message); + bool HandleGuildSay(Client* sender, const char* message); + void HandleGuildSay(std::string senderName, const char* message, int8 language); + bool HandleOfficerSay(Client* sender, const char* message); + void HandleOfficerSay(std::string senderName, const char* message, int8 language); void SendMessageToGuild(int8 event_type, const char* message, ...); + void SendGuildChatMessage(const char* message, ...); void SetSaveNeeded(bool val) {save_needed = val;} bool GetSaveNeeded() {return save_needed;} void SetMemberSaveNeeded(bool val) {member_save_needed = val;} diff --git a/source/WorldServer/Guilds/GuildDB.cpp b/source/WorldServer/Guilds/GuildDB.cpp index 5c0ddae..eca4533 100644 --- a/source/WorldServer/Guilds/GuildDB.cpp +++ b/source/WorldServer/Guilds/GuildDB.cpp @@ -67,6 +67,39 @@ void WorldDatabase::LoadGuilds() { LogWrite(GUILD__INFO, 0, "Guilds", "\tLoaded %u Guild(s)", num_guilds); } +void WorldDatabase::LoadGuild(int32 guild_id) { + Query query; + MYSQL_ROW row; + Guild* tmpGuild = guild_list.GetGuild(guild_id); + if(tmpGuild) // already loaded + return; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds` where id=%u", guild_id); + if (result && (row = mysql_fetch_row(result))) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "%u. %s", atoul(row[0]), row[1]); + Guild* guild = new Guild; + guild->SetID(atoul(row[0])); + guild->SetName(row[1]); + if (row[2]) + guild->SetMOTD(row[2], false); + guild->SetLevel(atoi(row[3]), false); + guild->SetEXPCurrent(atoul(row[4]), false); + guild->SetEXPToNextLevel(atoul(row[5]), false); + guild->SetFormedDate(atoul(row[6])); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoaded %i guild members.", LoadGuildMembers(guild)); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Ranks..."); + LoadGuildRanks(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Event Filters..."); + LoadGuildEventFilters(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Events..."); + LoadGuildEvents(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Recruiting..."); + LoadGuildRecruiting(guild); + guild_list.AddGuild(guild); + } +} + int32 WorldDatabase::LoadGuildMembers(Guild* guild) { int32 num_members = 0; Query query; diff --git a/source/WorldServer/Items/Items.h b/source/WorldServer/Items/Items.h index c098f50..9434128 100644 --- a/source/WorldServer/Items/Items.h +++ b/source/WorldServer/Items/Items.h @@ -888,6 +888,7 @@ public: int32 status_rent_reduction; float coin_rent_reduction; int8 house_only; + int8 house_location; // 0 = floor, 1 = ceiling, 2 = wall }; struct HouseContainer_Info{ int64 allowed_types; diff --git a/source/WorldServer/Items/ItemsDB.cpp b/source/WorldServer/Items/ItemsDB.cpp index 62888b8..98aab1b 100644 --- a/source/WorldServer/Items/ItemsDB.cpp +++ b/source/WorldServer/Items/ItemsDB.cpp @@ -458,7 +458,7 @@ int32 WorldDatabase::LoadHouseItem(int32 item_id) MYSQL_ROW row; std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); - MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, rent_reduction, status_rent_reduction, coin_rent_reduction, house_only FROM item_details_house%s", (item_id == 0) ? "" : select_query_addition.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, rent_reduction, status_rent_reduction, coin_rent_reduction, house_only, house_location FROM item_details_house%s", (item_id == 0) ? "" : select_query_addition.c_str()); int32 total = 0; int32 id = 0; @@ -477,6 +477,7 @@ int32 WorldDatabase::LoadHouseItem(int32 item_id) item->houseitem_info->status_rent_reduction = atoi(row[2]); item->houseitem_info->coin_rent_reduction = atof(row[3]); item->houseitem_info->house_only = atoi(row[4]); + item->houseitem_info->house_location = atoul(row[5]); total++; } else diff --git a/source/WorldServer/Items/LootDB.cpp b/source/WorldServer/Items/LootDB.cpp index 374159a..b448fd1 100644 --- a/source/WorldServer/Items/LootDB.cpp +++ b/source/WorldServer/Items/LootDB.cpp @@ -152,7 +152,7 @@ void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) { count++; } - LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u Global loot list%s.", count, count == 1 ? "" : "s"); + LogWrite(LOOT__DEBUG, 4, "Loot", "--Loaded %u Global loot list%s.", count, count == 1 ? "" : "s"); } } @@ -177,7 +177,7 @@ bool WorldDatabase::LoadSpawnLoot(ZoneServer* zone, Spawn* spawn) LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id); count++; } - LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); + LogWrite(LOOT__DEBUG, 4, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); return true; } return false; diff --git a/source/WorldServer/LoginServer.cpp b/source/WorldServer/LoginServer.cpp index 3f1426c..cd06b50 100644 --- a/source/WorldServer/LoginServer.cpp +++ b/source/WorldServer/LoginServer.cpp @@ -66,6 +66,8 @@ extern int errno; #include "World.h" #include "../common/ConfigReader.h" #include "Rules/Rules.h" +#include "Web/PeerManager.h" +#include "Web/HTTPSClientPool.h" extern sint32 numzones; extern sint32 numclients; @@ -80,6 +82,8 @@ extern volatile bool RunLoops; volatile bool LoginLoopRunning = false; extern ConfigReader configReader; extern RuleManager rule_manager; +extern PeerManager peer_manager; +extern HTTPSClientPool peer_https_pool; bool AttemptingConnect = false; @@ -176,7 +180,7 @@ bool LoginServer::Process() { LogWrite(WORLD__ERROR, 0, "World", "Login Server returned a fatal error: %s\n", pack->pBuffer); tcpc->Disconnect(); ret = false; - net.ReadLoginINI(); + //net.ReadLoginINI(); // can't properly support with command line args now break; } case ServerOP_CharTimeStamp: @@ -367,10 +371,13 @@ bool LoginServer::Process() { int32 access_key = 0; - // if it is a accepted login, we add the zone auth request - access_key = DetermineCharacterLoginRequest ( utwr ); - if ( access_key != 0 ) + ZoneChangeDetails details; + std::string name = database.loadCharacterFromLogin(&details, utwr->char_id, utwr->lsaccountid); + // if it is a accepted login, we add the zone auth request + access_key = DetermineCharacterLoginRequest ( utwr, &details, name); + + if ( access_key != 0 ) { zone_auth.PurgeInactiveAuth(); char* characterName = database.GetCharacterName( utwr->char_id ); @@ -555,22 +562,11 @@ void LoginServer::SendFilterNameResponse ( int8 resp, int32 acct_id , int32 char safe_delete(outpack); } -int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ) { +int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr, ZoneChangeDetails* details, std::string name) { LogWrite(LOGIN__TRACE, 9, "Login", "Enter: %s", __FUNCTION__); - ServerPacket* outpack = new ServerPacket; - outpack->opcode = ServerOP_UsertoWorldResp; - outpack->size = sizeof(UsertoWorldResponse_Struct); - outpack->pBuffer = new uchar[outpack->size]; - memset(outpack->pBuffer, 0, outpack->size); - UsertoWorldResponse_Struct* utwrs = (UsertoWorldResponse_Struct*) outpack->pBuffer; - utwrs->lsaccountid = utwr->lsaccountid; - utwrs->char_id = utwr->char_id; - utwrs->ToID = utwr->FromID; int32 timestamp = Timer::GetUnixTimeStamp(); - utwrs->access_key = timestamp; - - // set default response to 0 - utwrs->response = 0; + int32 key = static_cast(MakeRandomFloat(0.01,1.0) * UINT32_MAX); + int8 response = 0; sint16 lowestStatus = database.GetLowestCharacterAdminStatus( utwr->lsaccountid ); @@ -587,19 +583,19 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); switch(status){ case -10: - utwrs->response = PLAY_ERROR_CHAR_NOT_LOADED; + response = PLAY_ERROR_CHAR_NOT_LOADED; break; case -9: - utwrs->response = 0;//PLAY_ERROR_ACCOUNT_IN_USE; + response = 0;//PLAY_ERROR_ACCOUNT_IN_USE; break; case -8: - utwrs->response = PLAY_ERROR_LOADING_ERROR; + response = PLAY_ERROR_LOADING_ERROR; break; case -1: - utwrs->response = PLAY_ERROR_ACCOUNT_BANNED; + response = PLAY_ERROR_ACCOUNT_BANNED; break; default: - utwrs->response = PLAY_ERROR_PROBLEM; + response = PLAY_ERROR_PROBLEM; } } else if(net.world_locked == true){ @@ -607,7 +603,7 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u // has high enough status, allow it if(status >= loginserver.minLockedStatus) - utwrs->response = 1; + response = 1; } else if(loginserver.maxPlayers > -1 && ((sint16)client_list.Count()) >= loginserver.maxPlayers) { @@ -616,14 +612,66 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u // has high enough status, allow it if(status >= loginserver.minGameFullStatus) { - utwrs->response = 1; + response = 1; } else - utwrs->response = -3; // server full response is -3 + response = -3; // server full response is -3 } else - utwrs->response = 1; + response = 1; + bool attemptedPeer = false; + if(response == 1 && details->peerId.size() > 0 && details->peerId != "self" && name.size() > 0) { + boost::property_tree::ptree root; + root.put("account_id", utwr->lsaccountid); + root.put("character_name", std::string(name)); + root.put("character_id", std::to_string(utwr->char_id)); + root.put("zone_id", std::to_string(details->zoneId)); + root.put("instance_id", std::to_string(details->instanceId)); + root.put("login_key", std::to_string(key)); + root.put("client_ip", std::string(utwr->ip_address)); + root.put("world_id", std::to_string(utwr->worldid)); + root.put("from_id", std::to_string(utwr->FromID)); + root.put("first_login", true); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Sending AddCharAuth for %s to peer %s:%u for existing zone %s", __FUNCTION__, name, details->peerWebAddress.c_str(), details->peerWebPort, details->zoneName.c_str()); + attemptedPeer = true; + peer_https_pool.sendPostRequestToPeerAsync(details->peerId, details->peerWebAddress, std::to_string(details->peerWebPort), "/addcharauth", jsonPayload); + } + else if(response == 1 && details->peerId == "") { + std::shared_ptr peer = peer_manager.getHealthyPeerWithLeastClients(); + if(peer != nullptr) { + boost::property_tree::ptree root; + char* characterName = database.GetCharacterName( utwr->char_id ); + if(!characterName) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: AddCharAuth failed to identify character name for char id %u to peer %s:%u", __FUNCTION__, utwr->char_id, peer->webAddr.c_str(), peer->webPort); + } + else { + root.put("account_id", utwr->lsaccountid); + root.put("character_name", std::string(characterName)); + root.put("character_id", std::to_string(utwr->char_id)); + root.put("zone_id", std::to_string(details->zoneId)); + root.put("instance_id", std::to_string(details->instanceId)); + root.put("login_key", std::to_string(key)); + root.put("client_ip", std::string(utwr->ip_address)); + root.put("world_id", std::to_string(utwr->worldid)); + root.put("from_id", std::to_string(utwr->FromID)); + root.put("first_login", true); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Sending AddCharAuth for %s to peer %s:%u for new zone %s", __FUNCTION__, characterName, peer->webAddr.c_str(), peer->webPort, details->zoneName.c_str()); + attemptedPeer = true; + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addcharauth", jsonPayload); + } + } + else if(peer_manager.hasPeers()) { + LogWrite(PEERING__WARNING, 0, "Peering", "%s: AddCharAuth failed to find healthy peer for char id %u", __FUNCTION__, utwr->char_id); + } + } + /*sint32 x = database.CommandRequirement("$MAXCLIENTS"); if( (sint32)numplayers >= x && x != -1 && x != 255 && status < 80) utwrs->response = -3; @@ -634,34 +682,61 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u utwrs->response = -2; */ //printf("Response is %i for %i\n",utwrs->response,id);struct sockaddr_in sa; + + if(!attemptedPeer) { + SendCharApprovedLogin(response, "", "", std::string(utwr->ip_address), 0, utwr->lsaccountid, utwr->char_id, key, utwr->worldid, utwr->FromID); + } + + LogWrite(LOGIN__TRACE, 9, "Login", "Exit: %s with timestamp=%u", __FUNCTION__, timestamp); + // depending on the response determined above, this could return 0 (for failure) + return (attemptedPeer) ? 0 : key; +} + +void LoginServer::SendCharApprovedLogin(int8 response, std::string peerAddress, std::string peerInternalAddress, std::string clientIP, int16 peerPort, int32 account_id, int32 char_id, int32 key, int32 world_id, int32 from_id) { + ServerPacket* outpack = new ServerPacket; + outpack->opcode = ServerOP_UsertoWorldResp; + outpack->size = sizeof(UsertoWorldResponse_Struct); + outpack->pBuffer = new uchar[outpack->size]; + memset(outpack->pBuffer, 0, outpack->size); + UsertoWorldResponse_Struct* utwrs = (UsertoWorldResponse_Struct*) outpack->pBuffer; + utwrs->response = response; + utwrs->lsaccountid = account_id; + utwrs->char_id = char_id; + utwrs->ToID = from_id; + utwrs->access_key = key; + int32 ipv4addr = 0; int result = 0; #ifdef WIN32 struct sockaddr_in myaddr; ZeroMemory(&myaddr, sizeof(myaddr)); - result = InetPton(AF_INET, utwr->ip_address, &(myaddr.sin_addr)); + result = InetPton(AF_INET, clientIP.c_str(), &(myaddr.sin_addr)); if(result) ipv4addr = ntohl(myaddr.sin_addr.s_addr); #else - result = inet_pton(AF_INET, utwr->ip_address, &ipv4addr); + result = inet_pton(AF_INET, clientIP.c_str(), &ipv4addr); if(result) ipv4addr = ntohl(ipv4addr); #endif - if (((result > 0 && IsPrivateAddress(ipv4addr)) || (strcmp(net.GetWorldAddress(), utwr->ip_address) == 0)) && (strlen(net.GetInternalWorldAddress()) > 0)) - strcpy(utwrs->ip_address, net.GetInternalWorldAddress()); - else - strcpy(utwrs->ip_address, net.GetWorldAddress()); - LogWrite(CCLIENT__INFO, 0, "World", "New client login attempt from %s, providing %s as the world server address.",utwr->ip_address, utwrs->ip_address ); + std::string internalAddress = std::string(net.GetInternalWorldAddress()); + std::string address = std::string(net.GetWorldAddress()); + int16 worldport = net.GetWorldPort(); + if(peerAddress.size() > 0 && peerPort > 0) { + internalAddress = peerInternalAddress; + address = peerAddress; + worldport = peerPort; + } + if (((result > 0 && IsPrivateAddress(ipv4addr)) || (strcmp(address.c_str(), clientIP.c_str()) == 0)) && (internalAddress.size() > 0)) + strcpy(utwrs->ip_address, internalAddress.c_str()); + else + strcpy(utwrs->ip_address, address.c_str()); + + LogWrite(CCLIENT__INFO, 0, "World", "New client login attempt from %s, providing %s:%u as the world server address.",clientIP.c_str(), utwrs->ip_address, worldport ); - utwrs->port = net.GetWorldPort(); - utwrs->worldid = utwr->worldid; + utwrs->port = worldport; + utwrs->worldid = world_id; SendPacket(outpack); delete outpack; - - LogWrite(LOGIN__TRACE, 9, "Login", "Exit: %s with timestamp=%u", __FUNCTION__, timestamp); - // depending on the response determined above, this could return 0 (for failure) - return timestamp; } - diff --git a/source/WorldServer/LoginServer.h b/source/WorldServer/LoginServer.h index d0b4839..e888c8b 100644 --- a/source/WorldServer/LoginServer.h +++ b/source/WorldServer/LoginServer.h @@ -58,8 +58,8 @@ public: void SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ); - int32 DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ); - + int32 DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr, ZoneChangeDetails* details, std::string name); + void SendCharApprovedLogin(int8 response, std::string peerAddress, std::string peerInternalAddress, std::string clientIP, int16 peerPort, int32 account_id, int32 char_id, int32 key, int32 world_id, int32 from_id); void InitLoginServerVariables(); sint16 minLockedStatus; diff --git a/source/WorldServer/LuaFunctions.cpp b/source/WorldServer/LuaFunctions.cpp index ef49870..da8bd1f 100644 --- a/source/WorldServer/LuaFunctions.cpp +++ b/source/WorldServer/LuaFunctions.cpp @@ -1700,19 +1700,21 @@ int EQ2Emu_lua_GetZone(lua_State* state) { if (!lua_interface) return 0; int32 zone_id = lua_interface->GetInt32Value(state); - ZoneServer* zone = 0; - if (zone_id > 0) - zone = zone_list.Get(zone_id, true, false, false); + ZoneChangeDetails zone_details; + std::string zone_name; + ZoneServer* zone = nullptr; + + if(zone_id < 1) { + zone_name = lua_interface->GetStringValue(state); + } + bool zone_avail = zone_list.GetZone(&zone_details, zone_id, zone_name, true, false, false, false); + if (zone_avail) { + zone = (ZoneServer*)zone_details.zonePtr; + } else { - string zone_name = lua_interface->GetStringValue(state); - if (zone_name.length() > 0) { - zone = zone_list.Get(zone_name.c_str(), true, false, false); - } - else { - Spawn* spawn = lua_interface->GetSpawn(state); - if (spawn) - zone = spawn->GetZone(); - } + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) + zone = spawn->GetZone(); } lua_interface->ResetFunctionStack(state); if (zone) { @@ -2597,7 +2599,7 @@ int EQ2Emu_lua_RemoveSpellBonus(lua_State* state) { luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); } else { - LogWrite(LUA__ERROR, 0, "LUA", "Error removing spell bonus buff %s called by %s, zone is not available.", luaspell->spell ? luaspell->spell->GetName() : "NotSet", spawn->GetName()); + LogWrite(LUA__ERROR, 0, "LUA", "Error removing spell bonus buff %s called by %s, zone is not available.", luaspell->spell ? luaspell->spell->GetName() : "NotSet", spawn ? spawn->GetName() : "N/A"); } } else if (spawn && spawn->IsEntity()) { @@ -6680,6 +6682,11 @@ int EQ2Emu_lua_RemoveWard(lua_State* state) { } ZoneServer* zone = spell->caster->GetZone(); + if(!zone) { + lua_interface->LogError("%s: RemoveWard error: no valid zone for caster", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* target = 0; spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); for (int32 i = 0; i < spell->targets.size(); i++) { @@ -14270,3 +14277,34 @@ int EQ2Emu_lua_DespawnByLocationID(lua_State* state) { return 1; } +int EQ2Emu_lua_AddRespawn(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 location_id = lua_interface->GetInt32Value(state, 2); + int32 respawn_time = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (zone) { + zone->AddRespawn(location_id, respawn_time); + lua_interface->SetBooleanValue(state, true); + return 1; + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + + + +int EQ2Emu_lua_CreatePersistedRespawn(lua_State* state) { + int32 location_id = lua_interface->GetInt32Value(state); + int8 spawn_type = lua_interface->GetInt32Value(state, 2); + int32 respawn_time = lua_interface->GetInt32Value(state, 3); + int32 zone_id = lua_interface->GetInt32Value(state, 4); + lua_interface->ResetFunctionStack(state); + if (location_id && zone_id) { + database.CreatePersistedRespawn(location_id,spawn_type,respawn_time,zone_id); + lua_interface->SetBooleanValue(state, true); + return 1; + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + diff --git a/source/WorldServer/LuaFunctions.h b/source/WorldServer/LuaFunctions.h index 877ec44..fb19357 100644 --- a/source/WorldServer/LuaFunctions.h +++ b/source/WorldServer/LuaFunctions.h @@ -660,4 +660,7 @@ int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state); int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state); int EQ2Emu_lua_DespawnByLocationID(lua_State* state); + +int EQ2Emu_lua_AddRespawn(lua_State* state); +int EQ2Emu_lua_CreatePersistedRespawn(lua_State* state); #endif \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.cpp b/source/WorldServer/LuaInterface.cpp index 69ab42f..9b85644 100644 --- a/source/WorldServer/LuaInterface.cpp +++ b/source/WorldServer/LuaInterface.cpp @@ -905,9 +905,9 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool spell->caster->RemoveProc(0, spell); spell->caster->RemoveMaintainedSpell(spell); - int8 spell_type = spell->spell->GetSpellData()->spell_type; - if(spell->caster->IsPlayer() && !removing_all_spells) + if(spell->spell && spell->spell->GetSpellData() && spell->caster->IsPlayer() && !removing_all_spells) { + int8 spell_type = spell->spell->GetSpellData()->spell_type; Player* player = (Player*)spell->caster; switch(spell_type) { @@ -1562,6 +1562,9 @@ void LuaInterface::RegisterFunctions(lua_State* state) { lua_register(state, "GetSpellInitialTarget", EQ2Emu_lua_GetSpellInitialTarget); lua_register(state,"DespawnByLocationID", EQ2Emu_lua_DespawnByLocationID); + + lua_register(state,"AddRespawn", EQ2Emu_lua_AddRespawn); + lua_register(state,"CreatePersistedRespawn", EQ2Emu_lua_CreatePersistedRespawn); } void LuaInterface::LogError(const char* error, ...) { diff --git a/source/WorldServer/NPC_AI.cpp b/source/WorldServer/NPC_AI.cpp index 9934e88..07a9086 100644 --- a/source/WorldServer/NPC_AI.cpp +++ b/source/WorldServer/NPC_AI.cpp @@ -544,20 +544,30 @@ void Brain::AddToEncounter(Entity* entity) { PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); if (group) { - group->MGroupMembers.readlock(__FUNCTION__, __LINE__); - deque* members = group->GetMembers(); - for (itr = members->begin(); itr != members->end(); itr++) { - if ((*itr)->member) - { - bool success = AddToEncounter((*itr)->member->GetID()); - if((*itr)->client && success) { - m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID())); + std::vector raidGroups; + group->GetRaidGroups(&raidGroups); + if(raidGroups.size() < 1) + raidGroups.push_back(group_id); + + std::vector::iterator group_itr; + for(group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + group = world.GetGroupManager()->GetGroup((*group_itr)); + if(group) { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->member) + { + bool success = AddToEncounter((*itr)->member->GetID()); + if((*itr)->client && success) { + m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID())); + } + } } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } } - group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } - world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); } else { diff --git a/source/WorldServer/Player.cpp b/source/WorldServer/Player.cpp index 13a9aea..32046a4 100644 --- a/source/WorldServer/Player.cpp +++ b/source/WorldServer/Player.cpp @@ -77,6 +77,8 @@ Player::Player(){ spell_count = 0; spell_orig_packet = 0; spell_xor_packet = 0; + raid_orig_packet = nullptr; + raid_xor_packet = nullptr; resurrecting = false; spawn_id = 1; spawn_type = 4; @@ -131,6 +133,8 @@ Player::Player(){ need_trait_update = true; active_food_unique_id = 0; active_drink_unique_id = 0; + raidsheet_changed = false; + hassent_raid = false; } Player::~Player(){ SetSaveSpellEffects(true); @@ -181,6 +185,8 @@ Player::~Player(){ safe_delete_array(spawn_tmp_pos_xor_packet); safe_delete_array(spell_xor_packet); safe_delete_array(spell_orig_packet); + safe_delete_array(raid_orig_packet); + safe_delete_array(raid_xor_packet); DestroyQuests(); WritePlayerStatistics(); RemovePlayerStatistics(); @@ -1067,7 +1073,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal player->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); for (int i = 0; i < 45; i++) { if (i < 30) { - maintained_target = player->GetZone()->GetSpawnByID(info_struct->maintained_effects[i].target); + maintained_target = player->GetZone() ? player->GetZone()->GetSpawnByID(info_struct->maintained_effects[i].target) : nullptr; packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); if (maintained_target) packet->setSubstructDataByName("maintained_effects", "target", player->GetIDWithPlayerSpawn(maintained_target), i, 0); @@ -3055,6 +3061,162 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) { } return ret; } +EQ2Packet* Player::GetRaidUpdatePacket(int16 version) { + std::unique_lock lock(raid_update_mutex); + + std::vector raidGroups; + PacketStruct* packet = configReader.getStruct("WS_RaidUpdate", version); + EQ2Packet* ret = 0; + Entity* member = 0; + int8 det_count = 0; + int8 total_groups = 0; + if (packet) { + int16 ptr = 0; + // Get the packet size + PacketStruct* packet2 = configReader.getStruct("Substruct_RaidMember", version); + int32 total_bytes = packet2->GetTotalPacketSize(); + safe_delete(packet2); + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + if (GetGroupMemberInfo()) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(GetGroupMemberInfo()->group_id); + if (group) + { + group->GetRaidGroups(&raidGroups); + std::vector::iterator raid_itr; + int32 group_pos = 0; + for(raid_itr = raidGroups.begin(); raid_itr != raidGroups.end(); raid_itr++) { + group = world.GetGroupManager()->GetGroup((*raid_itr)); + if(!group) + continue; + total_groups++; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + GroupMemberInfo* info = 0; + int x = 1; + int lastpos = 1; + bool gotleader = false; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + + if(!info) + continue; + + member = info->member; + + std::string prop_name("group_member"); + if(!gotleader && info->leader) { + lastpos = x; + x = 0; + gotleader = true; + } + else if(lastpos) { + x = lastpos; + lastpos = 0; + } + prop_name.append(std::to_string(x) + "_" + std::to_string(group_pos)); + x++; + if (member && member->GetZone() == GetZone()) { + packet->setSubstructDataByName(prop_name.c_str(), "spawn_id", GetIDWithPlayerSpawn(member), 0); + + if (member->HasPet()) { + if (member->GetPet()) + packet->setSubstructDataByName(prop_name.c_str(), "pet_id", GetIDWithPlayerSpawn(member->GetPet()), 0); + else + packet->setSubstructDataByName(prop_name.c_str(), "pet_id", GetIDWithPlayerSpawn(member->GetCharmedPet()), 0); + } + else + packet->setSubstructDataByName(prop_name.c_str(), "pet_id", 0xFFFFFFFF, 0); + + //Send detriment counts as 255 if all dets of that type are incurable + det_count = member->GetTraumaCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_TRAUMA)) + det_count = 255; + } + packet->setSubstructDataByName(prop_name.c_str(), "trauma_count", det_count, 0); + + det_count = member->GetArcaneCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ARCANE)) + det_count = 255; + } + packet->setSubstructDataByName(prop_name.c_str(), "arcane_count", det_count, 0); + + det_count = member->GetNoxiousCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_NOXIOUS)) + det_count = 255; + } + packet->setSubstructDataByName(prop_name.c_str(), "noxious_count", det_count, 0); + + det_count = member->GetElementalCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ELEMENTAL)) + det_count = 255; + } + packet->setSubstructDataByName(prop_name.c_str(), "elemental_count", det_count, 0); + + det_count = member->GetCurseCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_CURSE)) + det_count = 255; + } + packet->setSubstructDataByName(prop_name.c_str(), "curse_count", det_count, 0); + + packet->setSubstructDataByName(prop_name.c_str(), "zone_status", 1, 0); + } + else { + packet->setSubstructDataByName(prop_name.c_str(), "pet_id", 0xFFFFFFFF, 0); + //packet->setSubstructDataByName(prop_name.c_str(), "unknown5", 1, 0, 1); // unknown5 > 1 = name is blue + packet->setSubstructDataByName(prop_name.c_str(), "zone_status", 2, 0); + } + + packet->setSubstructDataByName(prop_name.c_str(), "name", info->name.c_str(), 0); + packet->setSubstructDataByName(prop_name.c_str(), "hp_current", info->hp_current, 0); + packet->setSubstructDataByName(prop_name.c_str(), "hp_max", info->hp_max, 0); + packet->setSubstructDataByName(prop_name.c_str(), "hp_current2", info->hp_current, 0); + packet->setSubstructDataByName(prop_name.c_str(), "power_current", info->power_current, 0); + packet->setSubstructDataByName(prop_name.c_str(), "power_max", info->power_max, 0); + packet->setSubstructDataByName(prop_name.c_str(), "level_current", info->level_current, 0); + packet->setSubstructDataByName(prop_name.c_str(), "level_max", info->level_max, 0); + packet->setSubstructDataByName(prop_name.c_str(), "zone", info->zone.c_str(), 0); + packet->setSubstructDataByName(prop_name.c_str(), "race_id", info->race_id, 0); + packet->setSubstructDataByName(prop_name.c_str(), "class_id", info->class_id, 0); + } + + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + group_pos += 1; + } + } + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + //packet->PrintPacket(); + + hassent_raid = true; + string* data = packet->serializeString(); + int32 size = data->length(); + + uchar* tmp = new uchar[size]; + if(!raid_xor_packet){ + raid_orig_packet = new uchar[size]; + raid_xor_packet = new uchar[size]; + memcpy(raid_orig_packet, (uchar*)data->c_str(), size); + size = Pack(tmp, (uchar*)data->c_str(), size, size, version); + } + else{ + memcpy(raid_xor_packet, (uchar*)data->c_str(), size); + Encode(raid_xor_packet, raid_orig_packet, size); + size = Pack(tmp, raid_xor_packet, size, size, version); + } + + ret = new EQ2Packet(OP_UpdateRaidMsg, tmp, size); + safe_delete_array(tmp); + safe_delete(packet); + //DumpPacket(ret); + } + return ret; +} PlayerInfo::~PlayerInfo(){ RemoveOldPackets(); @@ -3946,6 +4108,14 @@ bool Player::GetCharSheetChanged(){ return charsheet_changed; } +void Player::SetRaidSheetChanged(bool val){ + raidsheet_changed = val; +} + +bool Player::GetRaidSheetChanged(){ + return raidsheet_changed; +} + bool Player::AdventureXPEnabled(){ return (GetInfoStruct()->get_flags() & (1 << CF_COMBAT_EXPERIENCE_ENABLED)); } @@ -5828,6 +5998,11 @@ void Player::SetReturningFromLD(bool val){ spell_orig_packet=0; spell_xor_packet=0; spell_count = 0; + + safe_delete_array(raid_orig_packet); + safe_delete_array(raid_xor_packet); + raid_orig_packet=0; + raid_xor_packet=0; reset_character_flag(CF_IS_SITTING); if (GetActivityStatus() & ACTIVITY_STATUS_CAMPING) @@ -5981,40 +6156,6 @@ void Player::DeleteMail(int32 mail_id, bool from_database) { } } -ZoneServer* Player::GetGroupMemberInZone(int32 zone_id) { - ZoneServer* ret = nullptr; - - GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); - // If the player has a group and destination zone id - if (gmi && zone_id) { - deque::iterator itr; - - world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); - - PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); - if (group) - { - group->MGroupMembers.readlock(__FUNCTION__, __LINE__); - deque* members = group->GetMembers(); - // Loop through the group members - for (itr = members->begin(); itr != members->end(); itr++) { - // If a group member matches a target - if ((*itr)->member && (*itr)->member != this && (*itr)->member->GetZone() && (*itr)->member->GetZone()->GetInstanceID() > 0 && - (*itr)->member->GetZone()->GetZoneID() == zone_id) { - // toggle the flag and break the loop - ret = (*itr)->member->GetZone(); - break; - } - } - group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); - } - - world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); - } - return ret; -} - - /* CharacterInstances */ CharacterInstances::CharacterInstances() { @@ -6154,7 +6295,8 @@ void CharacterInstances::ProcessInstanceTimers(Player* player) { if (data->zone_instance_type == SOLO_PERSIST_INSTANCE || data->zone_instance_type == GROUP_PERSIST_INSTANCE || data->zone_instance_type == RAID_PERSIST_INSTANCE) { // Check max duration (last success + success time) - if (Timer::GetUnixTimeStamp() >= (data->last_success_timestamp + data->success_lockout_time)) { + // if the zone does not have a success lockout time, we should not apply this logic + if (data->success_lockout_time > 0 && (Timer::GetUnixTimeStamp() >= (data->last_success_timestamp + data->success_lockout_time))) { // Max duration has passed, instance has expired lets remove the player from it, // this will also delete the instace if all players have been removed from it database.DeleteCharacterFromInstance(player->GetCharacterID(), data->instance_id); @@ -7117,8 +7259,8 @@ void Player::SaveSpellEffects() target_char_id = ((Player*)spawn)->GetCharacterID(); else if (spawn && spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this) target_char_id = 0xFFFFFFFF; - - int32 caster_char_id = (info->maintained_effects[i].spell->caster && info->maintained_effects[i].spell->caster->IsPlayer()) ? ((Player*)info->maintained_effects[i].spell->caster)->GetCharacterID() : 0; + + int32 caster_char_id = info->maintained_effects[i].spell->initial_caster_char_id; int32 timestamp = 0xFFFFFFFF; if(info->maintained_effects[i].spell->spell->GetSpellData() && !info->maintained_effects[i].spell->spell->GetSpellData()->duration_until_cancel) diff --git a/source/WorldServer/Player.h b/source/WorldServer/Player.h index b1491ac..97beb80 100644 --- a/source/WorldServer/Player.h +++ b/source/WorldServer/Player.h @@ -437,6 +437,8 @@ public: PlayerInfo* GetPlayerInfo(); void SetCharSheetChanged(bool val); bool GetCharSheetChanged(); + void SetRaidSheetChanged(bool val); + bool GetRaidSheetChanged(); void AddFriend(const char* name, bool save); bool IsFriend(const char* name); void RemoveFriend(const char* name); @@ -552,8 +554,9 @@ public: int8 GetSpellTier(int32 id); void SetSpellStatus(Spell* spell, int8 status); void RemoveSpellStatus(Spell* spell, int8 status); - EQ2Packet* GetSpellBookUpdatePacket(int16 version); EQ2Packet* GetSpellSlotMappingPacket(int16 version); + EQ2Packet* GetSpellBookUpdatePacket(int16 version); + EQ2Packet* GetRaidUpdatePacket(int16 version); int32 GetCharacterID(); void SetCharacterID(int32 new_id); EQ2Packet* GetQuickbarPacket(int16 version); @@ -775,7 +778,6 @@ public: void SetAwayMessage(string val) { away_message = val; } void SetRangeAttack(bool val); bool GetRangeAttack(); - ZoneServer* GetGroupMemberInZone(int32 zone_id); bool AddMail(Mail* mail); MutexMap* GetMail(); Mail* GetMail(int32 mail_id); @@ -1112,6 +1114,7 @@ public: map m_levelXPReq; mutable std::shared_mutex spell_packet_update_mutex; + mutable std::shared_mutex raid_update_mutex; private: bool reset_mentorship; bool range_attack; @@ -1129,7 +1132,9 @@ private: map current_quest_flagged; PlayerFaction factions; map completed_quests; - bool charsheet_changed; + std::atomic charsheet_changed; + std::atomic raidsheet_changed; + std::atomic hassent_raid; map spawn_vis_packet_list; map spawn_info_packet_list; map spawn_pos_packet_list; @@ -1140,6 +1145,9 @@ private: uchar* spell_orig_packet; uchar* spell_xor_packet; int16 spell_count; + + uchar* raid_orig_packet; + uchar* raid_xor_packet; //float speed; int16 target_id; Spawn* combat_target; diff --git a/source/WorldServer/PlayerGroups.cpp b/source/WorldServer/PlayerGroups.cpp index 483b007..698e9a3 100644 --- a/source/WorldServer/PlayerGroups.cpp +++ b/source/WorldServer/PlayerGroups.cpp @@ -26,10 +26,14 @@ along with EQ2Emulator. If not, see . #include "Bots/Bot.h" #include "SpellProcess.h" #include "Rules/Rules.h" +#include "Web/PeerManager.h" +#include "WorldDatabase.h" +extern ConfigReader configReader; extern ZoneList zone_list; extern RuleManager rule_manager; - +extern PeerManager peer_manager; +extern WorldDatabase database; /******************************************************** PlayerGroup ********************************************************/ PlayerGroup::PlayerGroup(int32 id) { @@ -42,7 +46,7 @@ PlayerGroup::~PlayerGroup() { Disband(); } -bool PlayerGroup::AddMember(Entity* member) { +bool PlayerGroup::AddMember(Entity* member, bool is_leader) { // Check to make sure the entity we are adding is valid if (!member) { LogWrite(GROUP__ERROR, 0, "Group", "New member is null"); @@ -59,13 +63,27 @@ bool PlayerGroup::AddMember(Entity* member) { GroupMemberInfo* gmi = new GroupMemberInfo; gmi->group_id = m_id; gmi->member = member; - gmi->leader = false; - if (member->IsPlayer()) + gmi->leader = is_leader; + if (member->IsPlayer()) { + gmi->is_client = true; gmi->client = ((Player*)member)->GetClient(); - else + } + else { + gmi->is_client = false; gmi->client = 0; + } gmi->mentor_target_char_id = 0; + if (member->GetZone()) { + gmi->zone_id = member->GetZone()->GetZoneID(); + gmi->instance_id = member->GetZone()->GetInstanceID(); + } + + gmi->client_peer_address = std::string(net.GetWorldAddress()); + gmi->client_peer_port = net.GetWorldPort(); + + gmi->is_raid_looter = false; + member->SetGroupMemberInfo(gmi); member->group_id = gmi->group_id; MGroupMembers.writelock(); @@ -77,6 +95,45 @@ bool PlayerGroup::AddMember(Entity* member) { return true; } +bool PlayerGroup::AddMemberFromPeer(std::string name, bool isleader, bool isclient, int8 class_id, sint32 hp_cur, sint32 hp_max, int16 level_cur, int16 level_max, + sint32 power_cur, sint32 power_max, int8 race_id, std::string zonename, int32 mentor_target_char_id, int32 zone_id, int32 instance_id, + std::string peer_client_address, int16 peer_client_port, bool is_raid_looter) { + // Create a new GroupMemberInfo and assign it to the new member + GroupMemberInfo* gmi = new GroupMemberInfo; + gmi->group_id = m_id; + gmi->member = nullptr; + gmi->leader = isleader; + gmi->is_client = isclient; + gmi->client = 0; + gmi->mentor_target_char_id = 0; + + gmi->class_id = class_id; + gmi->hp_max = hp_max; + gmi->hp_current = hp_cur; + gmi->level_max = level_max; + gmi->level_current = level_cur; + gmi->name = name; + gmi->power_current = power_cur; + gmi->power_max = power_max; + gmi->race_id = race_id; + gmi->zone = zonename; + gmi->zone_id = zone_id; + gmi->instance_id = instance_id; + + gmi->mentor_target_char_id = mentor_target_char_id; + + gmi->client_peer_address = peer_client_address; + gmi->client_peer_port = peer_client_port; + + gmi->is_raid_looter = is_raid_looter; + MGroupMembers.writelock(); + m_members.push_back(gmi); + MGroupMembers.releasewritelock(); + + SendGroupUpdate(); + return true; +} + bool PlayerGroup::RemoveMember(Entity* member) { GroupMemberInfo* gmi = member->GetGroupMemberInfo(); if (!gmi) { @@ -85,6 +142,8 @@ bool PlayerGroup::RemoveMember(Entity* member) { bool ret = false; + bool selfInRaid = IsInRaidGroup(gmi->group_id, false); + MGroupMembers.writelock(); member->SetGroupMemberInfo(0); @@ -93,15 +152,18 @@ bool PlayerGroup::RemoveMember(Entity* member) { for (itr = m_members.begin(); itr != m_members.end(); itr++) { if (gmi == *itr) erase_itr = itr; - - if(member->IsPlayer() && (*itr)->mentor_target_char_id == ((Player*)member)->GetCharacterID() && (*itr)->client) + + if (member->IsPlayer() && (*itr)->mentor_target_char_id == ((Player*)member)->GetCharacterID() && (*itr)->client) { (*itr)->mentor_target_char_id = 0; (*itr)->client->GetPlayer()->EnableResetMentorship(); } - if ((*itr)->client) + if ((*itr)->client) { (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + if (selfInRaid) + (*itr)->client->GetPlayer()->SetRaidSheetChanged(true); + } } if (erase_itr != m_members.end()) { ret = true; @@ -117,23 +179,65 @@ bool PlayerGroup::RemoveMember(Entity* member) { return ret; } +bool PlayerGroup::RemoveMember(std::string name, bool is_client, int32 charID) { + bool ret = false; + GroupMemberInfo* gmi = nullptr; + bool selfInRaid = IsInRaidGroup(GetID(), false); + deque::iterator erase_itr = m_members.end(); + deque::iterator itr; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if ((*itr)->name == name && (*itr)->is_client == is_client) { + gmi = (*itr); + erase_itr = itr; + } + + if (is_client && charID > 0 && (*itr)->mentor_target_char_id == charID && (*itr)->client) + { + (*itr)->mentor_target_char_id = 0; + (*itr)->client->GetPlayer()->EnableResetMentorship(); + } + + if ((*itr)->client) { + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + if (selfInRaid) + (*itr)->client->GetPlayer()->SetRaidSheetChanged(true); + } + } + if (erase_itr != m_members.end()) { + ret = true; + m_members.erase(erase_itr); + } + MGroupMembers.releasewritelock(); + + safe_delete(gmi); + + return ret; +} + void PlayerGroup::Disband() { + m_raidgroups.clear(); deque::iterator itr; MGroupMembers.writelock(); + + bool selfInRaid = IsInRaidGroup(GetID(), false); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { if ((*itr)->member) { (*itr)->member->SetGroupMemberInfo(0); if ((*itr)->member->IsBot()) ((Bot*)(*itr)->member)->Camp(); } - if((*itr)->mentor_target_char_id && (*itr)->client) + if ((*itr)->mentor_target_char_id && (*itr)->client) { (*itr)->mentor_target_char_id = 0; (*itr)->client->GetPlayer()->EnableResetMentorship(); } - if ((*itr)->client) + if ((*itr)->client) { (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + if (selfInRaid) + (*itr)->client->GetPlayer()->SetRaidSheetChanged(true); + } safe_delete(*itr); } @@ -142,13 +246,17 @@ void PlayerGroup::Disband() { MGroupMembers.releasewritelock(); } -void PlayerGroup::SendGroupUpdate(Client* exclude) { +void PlayerGroup::SendGroupUpdate(Client* exclude, bool forceRaidUpdate) { + bool selfInRaid = IsInRaidGroup(GetID(), false); deque::iterator itr; MGroupMembers.readlock(__FUNCTION__, __LINE__); for (itr = m_members.begin(); itr != m_members.end(); itr++) { GroupMemberInfo* gmi = *itr; - if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning()) + if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning()) { gmi->client->GetPlayer()->SetCharSheetChanged(true); + if (selfInRaid || forceRaidUpdate) + gmi->client->GetPlayer()->SetRaidSheetChanged(true); + } } MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } @@ -156,9 +264,9 @@ void PlayerGroup::SendGroupUpdate(Client* exclude) { void PlayerGroup::SimpleGroupMessage(const char* message) { deque::iterator itr; MGroupMembers.readlock(__FUNCTION__, __LINE__); - for(itr = m_members.begin(); itr != m_members.end(); itr++) { + for (itr = m_members.begin(); itr != m_members.end(); itr++) { GroupMemberInfo* info = *itr; - if(info->client) + if (info->client) info->client->SimpleMessage(CHANNEL_GROUP_CHAT, message); } MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); @@ -182,58 +290,97 @@ void PlayerGroup::SendGroupMessage(int8 type, const char* message, ...) { MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } -void PlayerGroup::GroupChatMessage(Spawn* from, int32 language, const char* message) { +void PlayerGroup::GroupChatMessage(Spawn* from, int32 language, const char* message, int16 channel) { deque::iterator itr; MGroupMembers.readlock(__FUNCTION__, __LINE__); - for(itr = m_members.begin(); itr != m_members.end(); itr++) { + for (itr = m_members.begin(); itr != m_members.end(); itr++) { GroupMemberInfo* info = *itr; - if(info && info->client && info->client->GetCurrentZone()) - info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, CHANNEL_GROUP_SAY, message, 0, 0, true, language); + if (info && info->client && info->client->GetCurrentZone()) + info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, channel, message, 0, 0, true, language); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::GroupChatMessage(std::string fromName, int32 language, const char* message, int16 channel) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info && info->client && info->client->GetCurrentZone()) + info->client->GetCurrentZone()->HandleChatMessage(info->client, fromName, "", channel, message, 0, 0, language); } MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } bool PlayerGroup::MakeLeader(Entity* new_leader) { + if (!new_leader || new_leader->GetGroupMemberInfo()) + return false; + + bool selfInRaid = IsInRaidGroup(GetID(), false); + + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + new_leader->GetGroupMemberInfo()->leader = true; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info && info != new_leader->GetGroupMemberInfo() && info->leader) { + info->leader = false; + peer_manager.sendPeersGroupMember(GetID(), info, true); + break; + } + if ((*itr)->client) { + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + if (selfInRaid) + (*itr)->client->GetPlayer()->SetRaidSheetChanged(true); + } + } + peer_manager.sendPeersGroupMember(GetID(), new_leader->GetGroupMemberInfo(), true); + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + + SendGroupUpdate(); + + return true; +} + +std::string PlayerGroup::GetLeaderName() { + std::string name(""); deque::iterator itr; MGroupMembers.readlock(__FUNCTION__, __LINE__); for (itr = m_members.begin(); itr != m_members.end(); itr++) { GroupMemberInfo* info = *itr; - if (info->leader) { - info->leader = false; + if (info->leader && info->name.size() > 0) { + name = info->name; break; } } MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); - - new_leader->GetGroupMemberInfo()->leader = true; - SendGroupUpdate(); - - return true; + return name; } bool PlayerGroup::ShareQuestWithGroup(Client* quest_sharer, Quest* quest) { - if(!quest || !quest_sharer) + if (!quest || !quest_sharer) return false; - + bool canShare = quest->CanShareQuestCriteria(quest_sharer); - - if(!canShare) { + + if (!canShare) { return false; } - + deque::iterator itr; MGroupMembers.readlock(__FUNCTION__, __LINE__); - for(itr = m_members.begin(); itr != m_members.end(); itr++) { + for (itr = m_members.begin(); itr != m_members.end(); itr++) { GroupMemberInfo* info = *itr; - if(info && info->client && info->client->GetCurrentZone()) { - if( quest_sharer != info->client && info->client->GetPlayer()->HasAnyQuest(quest->GetQuestID()) == 0 ) { + if (info && info->client && info->client->GetCurrentZone()) { + if (quest_sharer != info->client && info->client->GetPlayer()->HasAnyQuest(quest->GetQuestID()) == 0) { info->client->AddPendingQuest(new Quest(quest)); info->client->Message(CHANNEL_COLOR_YELLOW, "%s has shared the quest %s with you.", quest_sharer->GetPlayer()->GetName(), quest->GetName()); } } } MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); - + return true; } @@ -252,7 +399,7 @@ PlayerGroupManager::~PlayerGroupManager() { m_pendingInvites.clear(); MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); - std::unique_lock lock(MGroups); + std::unique_lock lock(MGroups); map::iterator itr; for (itr = m_groups.begin(); itr != m_groups.end(); itr++) safe_delete(itr->second); @@ -260,37 +407,58 @@ PlayerGroupManager::~PlayerGroupManager() { m_groups.clear(); } -bool PlayerGroupManager::AddGroupMember(int32 group_id, Entity* member) { - std::shared_lock lock(MGroups); +bool PlayerGroupManager::AddGroupMember(int32 group_id, Entity* member, bool is_leader) { + std::shared_lock lock(MGroups); bool ret = false; if (m_groups.count(group_id) > 0) { PlayerGroup* group = m_groups[group_id]; - ret = group->AddMember(member); + ret = group->AddMember(member, is_leader); + peer_manager.sendPeersGroupMember(group_id, member->GetGroupMemberInfo()); + } + + return ret; +} + +bool PlayerGroupManager::AddGroupMemberFromPeer(int32 group_id, GroupMemberInfo* info) { + if (!info) + return false; + + std::shared_lock lock(MGroups); + bool ret = false; + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + ret = group->AddMemberFromPeer(info->name, info->leader, info->is_client, info->class_id, info->hp_current, info->hp_max, info->level_current, info->level_max, + info->power_current, info->power_max, info->race_id, info->zone, info->mentor_target_char_id, info->zone_id, info->instance_id, + info->client_peer_address, info->client_peer_port, info->is_raid_looter); } return ret; } bool PlayerGroupManager::RemoveGroupMember(int32 group_id, Entity* member) { + if (!member) + return false; + bool ret = false; bool remove = false; Client* client = 0; - if(member->GetGroupMemberInfo()->mentor_target_char_id) + if (member->GetGroupMemberInfo() && member->GetGroupMemberInfo()->mentor_target_char_id) { - if(member->IsPlayer()) + if (member->IsPlayer()) { Player* tmpPlayer = (Player*)member; member->GetGroupMemberInfo()->mentor_target_char_id = 0; tmpPlayer->EnableResetMentorship(); } } - + GroupLock(__FUNCTION__, __LINE__); if (m_groups.count(group_id) > 0) { PlayerGroup* group = m_groups[group_id]; - + if (member->IsPlayer()) client = member->GetGroupMemberInfo()->client; @@ -300,53 +468,87 @@ bool PlayerGroupManager::RemoveGroupMember(int32 group_id, Entity* member) { if (group->Size() == 1) remove = true; } - + ReleaseGroupLock(__FUNCTION__, __LINE__); if (client) RemoveGroupBuffs(group_id, client); + peer_manager.sendPeersRemoveGroupMember(group_id, std::string(member->GetName()), (member->IsPlayer() ? ((Player*)member)->GetCharacterID() : 0), member->IsPlayer()); + // Call RemoveGroup outside the locks as it uses the same locks - if (remove) + if (remove) { RemoveGroup(group_id); + peer_manager.sendPeersDisbandGroup(group_id); + } return ret; } +bool PlayerGroupManager::RemoveGroupMember(int32 group_id, std::string name, bool is_client, int32 charID) { + bool ret = false; + bool remove = false; + Client* client = 0; -void PlayerGroupManager::NewGroup(Entity* leader) { + GroupLock(__FUNCTION__, __LINE__); + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + + ret = group->RemoveMember(name, is_client, charID); + + // If only 1 person left in the group set a flag to remove the group + if (group->Size() == 1) + remove = true; + } + + ReleaseGroupLock(__FUNCTION__, __LINE__); + + //if (client) + // RemoveGroupBuffs(group_id, client); + + // Call RemoveGroup outside the locks as it uses the same locks + if (remove) + RemoveGroup(group_id); + else { + GroupMessage(group_id, "%s has left the group.", name.c_str()); + } + return ret; +} + +int32 PlayerGroupManager::NewGroup(Entity* leader, GroupOptions* goptions, int32 override_group_id) { std::unique_lock lock(MGroups); + int32 groupID = 0; // Highly doubt this will ever be needed but putting it in any way, basically bump the id and ensure // no active group is currently using this id, if we hit the max for an int32 then reset the id to 1 - while (m_groups.count(m_nextGroupID) > 0) { + if (!override_group_id) { + do { + groupID = peer_manager.getUniqueGroupId(); + } while (m_groups.count(groupID) > 0); + } + else if (override_group_id) { + groupID = override_group_id; + if (m_groups.count(groupID)) + return 0; // group already exists + } + + // last resort if the unique group id is not working out + while (m_groups.count(groupID) > 0) { // If m_nextGroupID is at its max then reset it to 1, else increment it - if (m_nextGroupID == 4294967295) - m_nextGroupID = 1; + if (groupID == 4294967295) + groupID = 1; else - m_nextGroupID++; + groupID++; } // Create a new group with the valid ID we got from above - PlayerGroup* new_group = new PlayerGroup(m_nextGroupID); - - GroupOptions goptions; - goptions.loot_method = leader->GetInfoStruct()->get_group_loot_method(); - goptions.loot_items_rarity = leader->GetInfoStruct()->get_group_loot_items_rarity(); - goptions.auto_split = leader->GetInfoStruct()->get_group_auto_split(); - goptions.default_yell = leader->GetInfoStruct()->get_group_default_yell(); - goptions.group_autolock = leader->GetInfoStruct()->get_group_autolock(); - goptions.group_lock_method = leader->GetInfoStruct()->get_group_lock_method(); - goptions.solo_autolock = leader->GetInfoStruct()->get_group_solo_autolock(); - goptions.auto_loot_method = leader->GetInfoStruct()->get_group_auto_loot_method(); - new_group->SetDefaultGroupOptions(&goptions); + PlayerGroup* new_group = new PlayerGroup(groupID); + new_group->SetDefaultGroupOptions(goptions); // Add the new group to the list (need to do this first, AddMember needs ref to the PlayerGroup ptr -> UpdateGroupMemberInfo) - m_groups[m_nextGroupID] = new_group; + m_groups[groupID] = new_group; - // Add the leader to the group - new_group->AddMember(leader); - - leader->GetGroupMemberInfo()->leader = true; + return groupID; } void PlayerGroupManager::RemoveGroup(int32 group_id) { @@ -356,6 +558,18 @@ void PlayerGroupManager::RemoveGroup(int32 group_id) { if (m_groups.count(group_id) > 0) { // Get a pointer to the group PlayerGroup* group = m_groups[group_id]; + + std::vector raidGroups; + m_groups[group_id]->GetRaidGroups(&raidGroups); + if (raidGroups.size() > 0) { + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->ReplaceRaidGroups(&raidGroups); + } + } + } + m_groups[group_id]->ClearGroupRaid(); // Erase the group from the list m_groups.erase(group_id); // Delete the group @@ -397,7 +611,7 @@ int8 PlayerGroupManager::Invite(Player* leader, Entity* member) { } } // Inviter is not in a group - else { + else { // Check to see if the inviter has a pending invite himself if (m_pendingInvites.count(leader->GetName()) > 0) ret = 4; // inviter already has a pending group invite @@ -411,16 +625,69 @@ int8 PlayerGroupManager::Invite(Player* leader, Entity* member) { // Release the lock on pending invites MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + bool group_existed = false; + if (leader && leader->GetGroupMemberInfo()) { + group_existed = true; + } /* testing purposes only */ - if (ret == 0 && member->IsNPC()) - AcceptInvite(member); + if (ret == 0 && member->IsNPC()) { + if (net.is_primary) { + int32 group_id = 0; + int8 result = AcceptInvite(member, &group_id, false); + if (result == 0) { + if (leader && leader->GetClient()) { + if (group_existed) { + group_id = leader->GetGroupMemberInfo()->group_id; + GroupOptions options; + leader->GetClient()->SetGroupOptionsReference(&options); + peer_manager.sendPeersNewGroupRequest("", 0, group_id, std::string(leader->GetName()), std::string(member->GetName()), &options); + } + else { + AddGroupMember(group_id, leader, true); + } + GroupMessage(group_id, "%s has joined the group.", member->GetName()); + AddGroupMember(group_id, member); + } + } + } + else { + GroupOptions options; + if (leader && leader->GetClient()) { + GroupOptions options; + leader->GetClient()->SetGroupOptionsReference(&options); + peer_manager.sendPrimaryNewGroupRequest(std::string(leader->GetName()), std::string(member->GetName()), member->GetID(), &options); + } + } + } - return ret; + return ret; } -int8 PlayerGroupManager::AcceptInvite(Entity* member) { - int8 ret = 3; // default to unknown error +bool PlayerGroupManager::AddInvite(Player* leader, Entity* member) { + bool ret = true; + MPendingInvites.writelock(__FUNCTION__, __LINE__); + if (leader == member) + ret = false; // failure, can't invite yourself + else if (member->GetGroupMemberInfo()) + ret = false; + // Check to see if the target of the invite already has a pending invite + else if (m_pendingInvites.count(member->GetName()) > 0) + ret = false; // Target already has an invite + if (leader->GetGroupMemberInfo()) { + m_pendingInvites[member->GetName()] = leader->GetName(); + } + else { + m_pendingInvites[leader->GetName()] = leader->GetName(); + m_pendingInvites[member->GetName()] = leader->GetName(); + } + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +int8 PlayerGroupManager::AcceptInvite(Entity* member, int32* group_override_id, bool auto_add_group) { + int8 ret = 3; // default to unknown error + int32 groupResultID = 0; MPendingInvites.writelock(__FUNCTION__, __LINE__); if (m_pendingInvites.count(member->GetName()) > 0) { @@ -429,20 +696,53 @@ int8 PlayerGroupManager::AcceptInvite(Entity* member) { if (client_leader) { if (m_pendingInvites.count(leader) > 0) { - NewGroup(client_leader->GetPlayer()); + if (!client_leader->GetPlayer()->GetGroupMemberInfo()) { + GroupOptions goptions; + goptions.loot_method = client_leader->GetPlayer()->GetInfoStruct()->get_group_loot_method(); + goptions.loot_items_rarity = client_leader->GetPlayer()->GetInfoStruct()->get_group_loot_items_rarity(); + goptions.auto_split = client_leader->GetPlayer()->GetInfoStruct()->get_group_auto_split(); + goptions.default_yell = client_leader->GetPlayer()->GetInfoStruct()->get_group_default_yell(); + goptions.group_autolock = client_leader->GetPlayer()->GetInfoStruct()->get_group_autolock(); + goptions.group_lock_method = client_leader->GetPlayer()->GetInfoStruct()->get_group_lock_method(); + goptions.solo_autolock = client_leader->GetPlayer()->GetInfoStruct()->get_group_solo_autolock(); + goptions.auto_loot_method = client_leader->GetPlayer()->GetInfoStruct()->get_group_auto_loot_method(); + groupResultID = NewGroup(client_leader->GetPlayer(), &goptions, (group_override_id != nullptr) ? (*group_override_id) : 0); + if (group_override_id != nullptr && *group_override_id == 0) { + *group_override_id = groupResultID; + } + } + else { + if (group_override_id != nullptr && *group_override_id == 0) { + if (client_leader->GetPlayer()->GetGroupMemberInfo()) + *group_override_id = client_leader->GetPlayer()->GetGroupMemberInfo()->group_id; + } + } m_pendingInvites.erase(leader); } // Remove from invite list and add to the group - m_pendingInvites.erase(member->GetName()); - - GroupMessage(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has joined the group.", member->GetName()); - AddGroupMember(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, member); + if (m_pendingInvites.count(member->GetName())) { + m_pendingInvites.erase(member->GetName()); + } + int32 result_group_id = 0; + if (*group_override_id && *group_override_id > 0) { + result_group_id = *group_override_id; + } + else + result_group_id = groupResultID; + + if (auto_add_group && result_group_id) { + AddGroupMember(result_group_id, client_leader->GetPlayer()); + GroupMessage(result_group_id, "%s has joined the group.", member->GetName()); + AddGroupMember(result_group_id, member); + } ret = 0; // success } else { // Was unable to find the leader, remove from the invite list - m_pendingInvites.erase(member->GetName()); + if (m_pendingInvites.count(member->GetName())) { + m_pendingInvites.erase(member->GetName()); + } ret = 2; // failure, can't find leader } } @@ -459,11 +759,18 @@ void PlayerGroupManager::DeclineInvite(Entity* member) { if (m_pendingInvites.count(member->GetName()) > 0) { string leader = m_pendingInvites[member->GetName()]; + // send decline to leader m_pendingInvites.erase(member->GetName()); if (m_pendingInvites.count(leader) > 0) m_pendingInvites.erase(leader); } + if (m_raidPendingInvites.count(member->GetName()) > 0) { + string leader = m_raidPendingInvites[member->GetName()]; + // send decline to leader + m_raidPendingInvites.erase(member->GetName()); + } + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); } @@ -474,11 +781,20 @@ bool PlayerGroupManager::IsGroupIDValid(int32 group_id) { return ret; } -void PlayerGroupManager::SendGroupUpdate(int32 group_id, Client* exclude) { +void PlayerGroupManager::SendGroupUpdate(int32 group_id, Client* exclude, bool forceRaidUpdate) { std::shared_lock lock(MGroups); if (m_groups.count(group_id) > 0) { - m_groups[group_id]->SendGroupUpdate(exclude); + std::vector raidGroups; + m_groups[group_id]->GetRaidGroups(&raidGroups); + if (raidGroups.size() < 1) + raidGroups.push_back(group_id); + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->SendGroupUpdate(exclude, forceRaidUpdate); + } + } } } @@ -498,6 +814,18 @@ void PlayerGroupManager::ClearPendingInvite(Entity* member) { MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); } +std::string PlayerGroupManager::HasPendingInvite(Entity* member) { + std::string leader(""); + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) { + leader = m_pendingInvites[member->GetName()]; + } + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + return leader; +} + void PlayerGroupManager::RemoveGroupBuffs(int32 group_id, Client* client) { SpellEffects* se = 0; Spell* spell = 0; @@ -517,7 +845,7 @@ void PlayerGroupManager::RemoveGroupBuffs(int32 group_id, Client* client) { player = client->GetPlayer(); bool recoup_lock = true; for (int i = 0; i < NUM_SPELL_EFFECTS; i++) { - if(recoup_lock) { + if (recoup_lock) { player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); recoup_lock = false; se = player->GetSpellEffects(); @@ -543,22 +871,22 @@ void PlayerGroupManager::RemoveGroupBuffs(int32 group_id, Client* client) { //Also remove group buffs from pets pet = 0; charmed_pet = 0; - if (player->HasPet()){ + if (player->HasPet()) { pet = player->GetPet(); pet = player->GetCharmedPet(); } - if (pet){ + if (pet) { pet->RemoveSpellEffect(luaspell); pet->RemoveSpellBonus(luaspell); } - if (charmed_pet){ + if (charmed_pet) { charmed_pet->RemoveSpellEffect(luaspell); charmed_pet->RemoveSpellBonus(luaspell); } } } } - if(!recoup_lock) { // we previously set a readlock that we now release + if (!recoup_lock) { // we previously set a readlock that we now release player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); } packet = client->GetPlayer()->GetSkills()->GetSkillPacket(client->GetVersion()); @@ -609,7 +937,7 @@ bool PlayerGroupManager::HasGroupCompletedQuest(int32 group_id, int32 quest_id) info = *itr; if (info->client) { bool isComplete = info->client->GetPlayer()->HasQuestBeenCompleted(quest_id); - if(!isComplete) + if (!isComplete) { questComplete = isComplete; break; @@ -630,14 +958,14 @@ void PlayerGroupManager::SimpleGroupMessage(int32 group_id, const char* message) void PlayerGroupManager::SendGroupMessage(int32 group_id, int8 type, const char* message, ...) { std::shared_lock lock(MGroups); - + va_list argptr; char buffer[4096]; buffer[0] = 0; va_start(argptr, message); vsnprintf(buffer, sizeof(buffer), message, argptr); va_end(argptr); - + if (m_groups.count(group_id) > 0) m_groups[group_id]->SendGroupMessage(type, buffer); } @@ -652,11 +980,74 @@ void PlayerGroupManager::GroupMessage(int32 group_id, const char* message, ...) SimpleGroupMessage(group_id, buffer); } -void PlayerGroupManager::GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message) { +void PlayerGroupManager::GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message, int16 channel) { std::shared_lock lock(MGroups); - if (m_groups.count(group_id) > 0) - m_groups[group_id]->GroupChatMessage(from, language, message); + if (m_groups.count(group_id) > 0) { + std::vector raidGroups; + + if (channel == CHANNEL_RAID_SAY) + m_groups[group_id]->GetRaidGroups(&raidGroups); + + if (raidGroups.size() < 1) + raidGroups.push_back(group_id); + + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->GroupChatMessage(from, language, message, channel); + } + } + } +} + +void PlayerGroupManager::GroupChatMessage(int32 group_id, std::string fromName, int32 language, const char* message, int16 channel) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) { + std::vector raidGroups; + + if (channel == CHANNEL_RAID_SAY) + m_groups[group_id]->GetRaidGroups(&raidGroups); + + if (raidGroups.size() < 1) + raidGroups.push_back(group_id); + + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->GroupChatMessage(fromName, language, message, channel); + } + } + } +} + +void PlayerGroupManager::SendGroupChatMessage(int32 group_id, int16 channel, const char* message, ...) { + std::shared_lock lock(MGroups); + + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + if (m_groups.count(group_id) > 0) { + std::vector raidGroups; + + if (channel == CHANNEL_RAID_SAY || channel == CHANNEL_LOOT_ROLLS) + m_groups[group_id]->GetRaidGroups(&raidGroups); + + if (raidGroups.size() < 1) + raidGroups.push_back(group_id); + + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->SendGroupMessage(channel, buffer); + } + } + } } bool PlayerGroupManager::MakeLeader(int32 group_id, Entity* new_leader) { @@ -664,7 +1055,7 @@ bool PlayerGroupManager::MakeLeader(int32 group_id, Entity* new_leader) { if (m_groups.count(group_id) > 0) return m_groups[group_id]->MakeLeader(new_leader); - + return false; } @@ -730,7 +1121,7 @@ void PlayerGroupManager::UpdateGroupBuffs() { if (!luaspell) continue; - + if (!luaspell->caster) { LogWrite(PLAYER__ERROR, 0, "Player", "Bad luaspell, caster is NULL, spellid: %u", me[i].spell_id); @@ -757,17 +1148,17 @@ void PlayerGroupManager::UpdateGroupBuffs() { client = (*target_itr)->client; has_effect = false; - + if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) { has_effect = true; } - if(!has_effect && (std::find(luaspell->removed_targets.begin(), + if (!has_effect && (std::find(luaspell->removed_targets.begin(), luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) { continue; } // Check if player is within range of the caster - if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && - (group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) { + if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && + (group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) { if (has_effect) { group_member->RemoveSpellEffect(luaspell); group_member->RemoveSpellBonus(luaspell); @@ -794,9 +1185,9 @@ void PlayerGroupManager::UpdateGroupBuffs() { continue; } - if(group_member->GetZone() != caster->GetZone()) + if (group_member->GetZone() != caster->GetZone()) { - SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); + SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); } else { @@ -869,7 +1260,7 @@ void PlayerGroupManager::UpdateGroupBuffs() { bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) { std::shared_lock lock(MGroups); bool ret = false; - + if (m_groups.count(group_id) > 0) { m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); deque* members = m_groups[group_id]->GetMembers(); @@ -887,7 +1278,7 @@ bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) { Entity* PlayerGroupManager::IsPlayerInGroup(int32 group_id, int32 character_id) { std::shared_lock lock(MGroups); - + Entity* ret = nullptr; if (m_groups.count(group_id) > 0) { @@ -901,10 +1292,36 @@ Entity* PlayerGroupManager::IsPlayerInGroup(int32 group_id, int32 character_id) } m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } - + return ret; } +void PlayerGroupManager::SendPeerGroupData(std::string peerId) { + if (peerId.size() < 1) // must provide a peerId + return; + + std::shared_lock lock(MGroups); // unique lock to avoid updates while we push to a new peer + map::iterator itr; + for (itr = m_groups.begin(); itr != m_groups.end(); itr++) { + PlayerGroup* group = itr->second; + if (group) { + int32 groupID = group->GetID(); + std::vector raidGroups; + group->GetRaidGroups(&raidGroups); + peer_manager.sendPeersNewGroupRequest("", 0, groupID, "", "", group->GetGroupOptions(), peerId, &raidGroups); + m_groups[groupID]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[groupID]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* info = members->at(i); + if (info) + peer_manager.sendPeersGroupMember(groupID, info, false, peerId); + } + m_groups[groupID]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } +} + + void PlayerGroup::RemoveClientReference(Client* remove) { deque::iterator itr; MGroupMembers.writelock(); @@ -926,7 +1343,7 @@ void PlayerGroup::UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked) { if (!player || !player->GetGroupMemberInfo()) return; - if(!groupMembersLocked) + if (!groupMembersLocked) MGroupMembers.writelock(); GroupMemberInfo* group_member_info = player->GetGroupMemberInfo(); @@ -939,34 +1356,648 @@ void PlayerGroup::UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked) { group_member_info->power_current = player->GetPower(); group_member_info->power_max = player->GetTotalPower(); group_member_info->race_id = player->GetRace(); - if (player->GetZone()) + if (player->GetZone()) { group_member_info->zone = player->GetZone()->GetZoneDescription(); - else + group_member_info->zone_id = player->GetZone()->GetZoneID(); + group_member_info->instance_id = player->GetZone()->GetInstanceID(); + } + else { group_member_info->zone = "Unknown"; + group_member_info->zone_id = 0; + group_member_info->instance_id = 0; + } - if(!groupMembersLocked) + peer_manager.sendPeersGroupMember(group_member_info->group_id, group_member_info, true); + + if (!groupMembersLocked) MGroupMembers.releasewritelock(); } +void PlayerGroup::GetRaidGroups(std::vector* groups) { + std::shared_lock lock(MRaidGroups); + if (groups) + groups->insert(groups->end(), m_raidgroups.begin(), m_raidgroups.end()); +} + +void PlayerGroup::ReplaceRaidGroups(std::vector* groups) { + std::unique_lock lock(MRaidGroups); + if (groups) { + m_raidgroups.clear(); + m_raidgroups.insert(m_raidgroups.end(), groups->begin(), groups->end()); + } +} + +bool PlayerGroup::IsInRaidGroup(int32 groupID, bool isLeaderGroup) { + std::unique_lock lock(MRaidGroups); + if (isLeaderGroup) { + if (m_raidgroups.size() > 0) { + int32 group = m_raidgroups[0]; + if (group == groupID) + return true; + } + } + else if (std::find(m_raidgroups.begin(), m_raidgroups.end(), groupID) != m_raidgroups.end()) { + return true; + } + return false; +} +void PlayerGroup::AddGroupToRaid(int32 groupID) { + std::unique_lock lock(MRaidGroups); + if (std::find(m_raidgroups.begin(), m_raidgroups.end(), groupID) == m_raidgroups.end()) { + m_raidgroups.push_back(groupID); + } +} + +void PlayerGroup::RemoveGroupFromRaid(int32 groupID) { + std::unique_lock lock(MRaidGroups); + std::vector::iterator group_itr = std::find(m_raidgroups.begin(), m_raidgroups.end(), groupID); + + if (group_itr != m_raidgroups.end()) { + m_raidgroups.erase(group_itr); + } +} + +bool PlayerGroup::IsGroupRaid() { + std::unique_lock lock(MRaidGroups); + return (m_raidgroups.size() > 1); +} + +void PlayerGroup::ClearGroupRaid() { + std::unique_lock lock(MRaidGroups); + m_raidgroups.clear(); +} + +void PlayerGroupManager::UpdateGroupMemberInfoFromPeer(int32 group_id, std::string name, bool is_client, GroupMemberInfo* updateinfo) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.writelock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo && curinfo->name == name && curinfo->is_client == is_client) { + curinfo->class_id = updateinfo->class_id; + curinfo->hp_max = updateinfo->hp_max; + curinfo->hp_current = updateinfo->hp_current; + curinfo->level_max = updateinfo->level_max; + curinfo->level_current = updateinfo->level_current; + curinfo->name = updateinfo->name; + curinfo->power_current = updateinfo->power_current; + curinfo->power_max = updateinfo->power_max; + curinfo->race_id = updateinfo->race_id; + curinfo->zone = updateinfo->zone; + curinfo->mentor_target_char_id = updateinfo->mentor_target_char_id; + curinfo->is_client = updateinfo->is_client; + curinfo->leader = updateinfo->leader; + curinfo->client_peer_address = updateinfo->client_peer_address; + curinfo->client_peer_port = updateinfo->client_peer_port; + curinfo->is_raid_looter = updateinfo->is_raid_looter; + break; + } + } + m_groups[group_id]->MGroupMembers.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void PlayerGroupManager::ClearGroupRaid(int32 groupID) { + std::shared_lock lock(MGroups); + + // Check to see if the id is in the list + if (m_groups.count(groupID) > 0) { + m_groups[groupID]->ClearGroupRaid(); + } + SendGroupUpdate(groupID); +} + +void PlayerGroupManager::RemoveGroupFromRaid(int32 groupID, int32 targetGroupID) { + std::shared_lock lock(MGroups); + + // Check to see if the id is in the list + if (m_groups.count(groupID) > 0) { + // Get a pointer to the group + PlayerGroup* group = m_groups[groupID]; + + std::vector raidGroups; + m_groups[groupID]->GetRaidGroups(&raidGroups); + if (raidGroups.size() > 0) { + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + m_groups[(*group_itr)]->RemoveGroupFromRaid(targetGroupID); + } + } + } + } +} + +bool PlayerGroupManager::IsInRaidGroup(int32 groupID, int32 targetGroupID, bool isLeaderGroup) { + std::shared_lock lock(MGroups); + if (m_groups.count(groupID)) + return m_groups[groupID]->IsInRaidGroup(targetGroupID, isLeaderGroup); + + return false; +} + +bool PlayerGroupManager::GetDefaultGroupOptions(int32 groupID, GroupOptions* options) { + std::shared_lock lock(MGroups); + if (m_groups.count(groupID)) + return m_groups[groupID]->GetDefaultGroupOptions(options); + + return false; +} + +void PlayerGroupManager::GetRaidGroups(int32 groupID, std::vector* newGroups) { + std::shared_lock lock(MGroups); + if (m_groups.count(groupID)) + m_groups[groupID]->GetRaidGroups(newGroups); +} + +void PlayerGroupManager::ReplaceRaidGroups(int32 groupID, std::vector* newGroups) { + std::shared_lock lock(MGroups); + if (m_groups.count(groupID)) + m_groups[groupID]->ReplaceRaidGroups(newGroups); + + SendGroupUpdate(groupID); +} + +void PlayerGroupManager::SetGroupOptions(int32 groupID, GroupOptions* options) { + std::shared_lock lock(MGroups); + if (m_groups.count(groupID)) + m_groups[groupID]->SetDefaultGroupOptions(options); +} + +void PlayerGroupManager::SendWhoGroupMembers(Client* client, int32 groupID) { + std::shared_lock lock(MGroups); + + if (m_groups.count(groupID) > 0) { + PacketStruct* packet = configReader.getStruct("WS_WhoQueryReply", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + int8 num_characters = 0; + + m_groups[groupID]->MGroupMembers.writelock(__FUNCTION__, __LINE__); + deque* members = m_groups[groupID]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo && curinfo->name.size() > 0) { + num_characters++; + } + } + packet->setDataByName("response", 2); + packet->setArrayLengthByName("num_characters", (int8)num_characters); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo && curinfo->name.size() > 0) { + packet->setArrayDataByName("char_name", curinfo->name.c_str(), i); + packet->setArrayDataByName("level", curinfo->level_current, i); + packet->setArrayDataByName("class", curinfo->class_id, i); + packet->setArrayDataByName("unknown4", 0xFF, i); //probably tradeskill class + packet->setArrayDataByName("race", curinfo->race_id, i); + packet->setArrayDataByName("zone", curinfo->zone.c_str(), i); + } + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + m_groups[groupID]->MGroupMembers.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void PlayerGroupManager::SendWhoRaidMembers(Client* client, int32 groupID) { + std::shared_lock lock(MGroups); + + PlayerGroup* group = nullptr; + if (m_groups.count(groupID)) + group = m_groups[groupID]; + + std::vector groups; + std::vector::iterator group_itr; + if (group) + group->GetRaidGroups(&groups); + + if (groups.size() > 0) { + PacketStruct* packet = configReader.getStruct("WS_WhoQueryReply", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + int8 num_characters = 0; + for (group_itr = groups.begin(); group_itr != groups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + // we will release this lock after we submit the data out below in the second group_itr + m_groups[(*group_itr)]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[(*group_itr)]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo && curinfo->name.size() > 0) { + num_characters++; + } + } + } + } + packet->setDataByName("response", 2); + packet->setArrayLengthByName("num_characters", (int8)num_characters); + int8 pos = 0; + for (group_itr = groups.begin(); group_itr != groups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + deque* members = m_groups[(*group_itr)]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo && curinfo->name.size() > 0) { + packet->setArrayDataByName("char_name", curinfo->name.c_str(), pos); + packet->setArrayDataByName("level", curinfo->level_current, pos); + packet->setArrayDataByName("class", curinfo->class_id, pos); + packet->setArrayDataByName("unknown4", 0xFF, pos); //probably tradeskill class + packet->setArrayDataByName("race", curinfo->race_id, pos); + packet->setArrayDataByName("zone", curinfo->zone.c_str(), pos); + pos++; + } + } + // release previously established lock during the count + m_groups[(*group_itr)]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "You are not currently in a raid."); + } +} + +int8 PlayerGroupManager::AcceptRaidInvite(std::string acceptorName, int32 groupID) { + std::unique_lock lock(MGroups); + + std::string raidLeaderName(""); + MPendingInvites.writelock(__FUNCTION__, __LINE__); + bool inviteAccept = true; + if (m_raidPendingInvites.count(acceptorName.c_str()) > 0) { + raidLeaderName = m_raidPendingInvites[acceptorName.c_str()]; + m_raidPendingInvites.erase(acceptorName.c_str()); + } + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + if (raidLeaderName.size() < 1) + return 0; + + Client* client_leader = zone_list.GetClientByCharName(raidLeaderName.c_str()); + if (client_leader) { + if (client_leader->GetPlayer()->GetGroupMemberInfo() && m_groups.count(groupID) && m_groups.count(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id)) { + Player* player = client_leader->GetPlayer(); + PlayerGroup* spawn_group = m_groups[groupID]; + PlayerGroup* player_group = m_groups[player->GetGroupMemberInfo()->group_id]; + if (spawn_group && player_group) { + std::vector groups; + spawn_group->GetRaidGroups(&groups); + if (groups.size() > 0) { + return 0; + } + + player_group->GetRaidGroups(&groups); + if (groups.size() > 3) { + return 0; + } + + spawn_group->SetDefaultGroupOptions(player_group->GetGroupOptions()); + if (groups.size() < 1) { + player_group->AddGroupToRaid(player_group->GetID()); + player_group->AddGroupToRaid(spawn_group->GetID()); + player_group->GetRaidGroups(&groups); + spawn_group->ReplaceRaidGroups(&groups); + return 1; + } + else { + groups.clear(); + player_group->AddGroupToRaid(spawn_group->GetID()); + player_group->GetRaidGroups(&groups); + std::vector::iterator group_itr; + for (group_itr = groups.begin(); group_itr != groups.end(); group_itr++) { + int32 cur_group_id = (*group_itr); + if (cur_group_id == player_group->GetID() || m_groups.count(cur_group_id) < 1) // we already set the player_group above, skip + continue; + PlayerGroup* temp_group = m_groups[cur_group_id]; + temp_group->ReplaceRaidGroups(&groups); + } + return 1; + } + + } + } + } + else { + // must be somewhere else in the world + } + return 0; +} + +bool PlayerGroupManager::SendRaidInvite(Client* sender, Entity* target) { + std::shared_lock lock(MGroups); + if (!sender || !target) + return false; + + if (!target->IsPlayer() || !((Player*)target)->GetClient() || !target->GetGroupMemberInfo() || !sender->GetPlayer()->GetGroupMemberInfo()) + return false; + + Player* player = sender->GetPlayer(); + int32 spawn_group_id = target->GetGroupMemberInfo()->group_id; + + if (m_groups.count(spawn_group_id) < 1) + return false; + + PlayerGroup* spawn_group = m_groups[spawn_group_id]; + + int32 player_group_id = player->GetGroupMemberInfo() ? player->GetGroupMemberInfo()->group_id : 0; + PlayerGroup* player_group = nullptr; + if (m_groups.count(player_group_id)) + player_group = m_groups[player_group_id]; + // check if already invited + if (spawn_group && !player->IsGroupMember((Player*)target) && !spawn_group->IsGroupRaid() && player_group && player->GetGroupMemberInfo()->leader + && (!player_group->IsInRaidGroup(spawn_group_id) || player_group->IsInRaidGroup(player_group_id, true))) { + std::string leader = spawn_group->GetLeaderName(); + std::vector groups; + player_group->GetRaidGroups(&groups); + if (groups.size() > 3) { + sender->SimpleMessage(CHANNEL_COLOR_RED, "You are currently in a full raid."); + return false; + } + if (leader.size() < 1) { + sender->SimpleMessage(CHANNEL_COLOR_RED, "Your target has no leader."); + return false; + } + MPendingInvites.readlock(__FUNCTION__, __LINE__); + bool inviteAccept = true; + if (m_raidPendingInvites.count(leader.c_str()) > 0) { + inviteAccept = false; + sender->SimpleMessage(CHANNEL_COLOR_RED, "Leader of the other group has a pending raid invite."); + } + MPendingInvites.releasereadlock(__FUNCTION__, __LINE__); + + if (!inviteAccept) + return false; + + MPendingInvites.writelock(__FUNCTION__, __LINE__); + m_raidPendingInvites[leader] = player->GetGroupMemberInfo()->name.c_str(); + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + sender->SendReceiveOffer(((Player*)target)->GetClient(), 3, std::string(sender->GetPlayer()->GetName()), 1); + return true; + } + return false; +} + +void PlayerGroupManager::SplitWithGroupOrRaid(Client* client, int32 coin_plat, int32 coin_gold, int32 coin_silver, int32 coin_copper) { + std::shared_lock lock(MGroups); + bool startWithLooter = true; + if (!client->GetPlayer()->GetGroupMemberInfo()) + return; + if (!coin_plat && !coin_gold && !coin_silver && !coin_copper) + return; + if (client->GetPlayer()->GetInfoStruct()->get_coin_plat() < coin_plat && + client->GetPlayer()->GetInfoStruct()->get_coin_gold() < coin_gold && + client->GetPlayer()->GetInfoStruct()->get_coin_silver() < coin_silver && + client->GetPlayer()->GetInfoStruct()->get_coin_copper() < coin_copper) { + // lacking coin + return; + } + int32 groupID = client->GetPlayer()->GetGroupMemberInfo()->group_id; + + if (m_groups.count(groupID) < 1) { + return; + } + + PlayerGroup* group = m_groups[groupID]; + if (group) + { + bool isLeadGroup = group->IsInRaidGroup(group->GetID(), true); + bool isInRaid = group->IsInRaidGroup(group->GetID()); + std::vector raidGroups; + group->GetRaidGroups(&raidGroups); + + if (!isInRaid && raidGroups.size() < 1) { + raidGroups.push_back(group->GetID()); + } + std::vector::iterator group_itr; + + int32 split_coin_per_player = 0; + int32 actual_coins = coin_plat * 1000000 + coin_gold * 10000 + coin_silver * 100 + coin_copper; + int32 coins_remain_after_split = actual_coins; + int32 total_coins = actual_coins; + + bool foundLooterResetRaidRun = false; + int8 members_in_zone = 0; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + group = m_groups[(*group_itr)]; + if (!group) + continue; + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + if (member->GetZone() != client->GetPlayer()->GetZone()) + continue; + + members_in_zone++; + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + if (members_in_zone < 1) // this should not happen, but divide by zero checked + members_in_zone = 1; + split_coin_per_player = actual_coins / members_in_zone; + coins_remain_after_split = actual_coins - (split_coin_per_player * members_in_zone); + // try to individually take each tier of coin and not split the coin in inventory + client->GetPlayer()->GetInfoStruct()->set_coin_plat(client->GetPlayer()->GetInfoStruct()->get_coin_plat() - coin_plat); + client->GetPlayer()->GetInfoStruct()->set_coin_gold(client->GetPlayer()->GetInfoStruct()->get_coin_gold() - coin_gold); + client->GetPlayer()->GetInfoStruct()->set_coin_silver(client->GetPlayer()->GetInfoStruct()->get_coin_silver() - coin_silver); + client->GetPlayer()->GetInfoStruct()->set_coin_copper(client->GetPlayer()->GetInfoStruct()->get_coin_copper() - coin_copper); + int32 lootGroup = 0; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end();) { + group = m_groups[(*group_itr)]; + if (!group) + continue; + + isLeadGroup = group->IsInRaidGroup((*group_itr), true); + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + LogWrite(LOOT__INFO, 0, "Loot", "%s: Group SplitWithGroupOrRaid, split coin per player %u, remaining coin after split %u", client->GetPlayer()->GetName(), split_coin_per_player, coins_remain_after_split); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + + if (member->GetZone() != client->GetPlayer()->GetZone()) + continue; + + // this will make sure we properly send the loot window to the initial requester if there is no item rarity matches + if (startWithLooter && member != client->GetPlayer()) + continue; + else if (!startWithLooter && member == client->GetPlayer()) + continue; + + int32 coin_recv = 0; + if (member == client->GetPlayer() && (split_coin_per_player + coins_remain_after_split) > 0) { + coin_recv = split_coin_per_player + coins_remain_after_split; + ((Player*)member)->AddCoins(split_coin_per_player + coins_remain_after_split); + if (coins_remain_after_split > 0) // overflow of coin division went to the first player + coins_remain_after_split = 0; + } + else if (split_coin_per_player > 0) { + coin_recv = split_coin_per_player; + ((Player*)member)->AddCoins(split_coin_per_player); + } + if (coin_recv && ((Player*)member)->GetClient()) { + ((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of the %s split from %s is %s.", client->GetCoinMessage(total_coins).c_str(), client->GetPlayer()->GetName(), client->GetCoinMessage(coin_recv).c_str()); + } + if (startWithLooter) { + startWithLooter = false; + foundLooterResetRaidRun = true; // we got it, shouldn't hit this again + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + if (foundLooterResetRaidRun) { + group_itr = raidGroups.begin(); + foundLooterResetRaidRun = false; // disable running it again + } + else + group_itr++; + } // end raid groups + } // end group +} + +bool PlayerGroupManager::IdentifyMemberInGroupOrRaid(ZoneChangeDetails* details, Client* client, int32 zoneID, int32 instanceID) { + std::shared_lock lock(MGroups); + ZoneServer* ret = nullptr; + PlayerGroup* group = nullptr; + bool succeed = false; + if (client->GetPlayer()->GetGroupMemberInfo() && m_groups.count(client->GetPlayer()->GetGroupMemberInfo()->group_id)) + group = m_groups[client->GetPlayer()->GetGroupMemberInfo()->group_id]; + else + return false; + + std::vector raidGroups; + std::vector::iterator group_itr; + if (group) + group->GetRaidGroups(&raidGroups); + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->is_client && (*itr)->member && (*itr)->member->GetZone() && (*itr)->zone_id == zoneID && (*itr)->instance_id == instanceID) { + // toggle the flag and break the loop + ret = (*itr)->member->GetZone(); + break; + } + else if ((*itr)->is_client && (*itr)->zone_id == zoneID && (*itr)->instance_id == instanceID) { + // toggle the flag and break the loop + std::string id = peer_manager.isPeer((*itr)->client_peer_address, (*itr)->client_peer_port); + std::shared_ptr peer = peer_manager.getPeerById(id); + if (peer) { + ZoneServer* tmp = new ZoneServer((*itr)->zone.c_str()); + database.LoadZoneInfo(tmp); + peer_manager.setZonePeerData(details, peer->id, peer->worldAddr, peer->internalWorldAddr, peer->worldPort, peer->webAddr, peer->webPort, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + tmp->GetInstanceID(), tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), tmp->GetMaximumLevel(), + tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers()); + safe_delete(tmp); + succeed = true; + break; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + if (succeed) { + return true; + } + else if (ret) { + peer_manager.setZonePeerDataSelf(details, std::string(ret->GetZoneFile()), std::string(ret->GetZoneName()), + ret->GetZoneID(), ret->GetInstanceID(), ret->GetSafeX(), ret->GetSafeY(), + ret->GetSafeZ(), ret->GetSafeHeading(), ret->GetZoneLockState(), + ret->GetMinimumStatus(), ret->GetMinimumLevel(), ret->GetMaximumLevel(), + ret->GetMinimumVersion(), ret->GetDefaultLockoutTime(), ret->GetDefaultReenterTime(), + ret->GetInstanceType(), ret->NumPlayers(), ret); + return true; + } + + + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + if (m_groups.count((*group_itr))) { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->is_client && (*itr)->zone_id == zoneID && (*itr)->instance_id == instanceID) { + // toggle the flag and break the loop + + std::string id = peer_manager.isPeer((*itr)->client_peer_address, (*itr)->client_peer_port); + std::shared_ptr peer = peer_manager.getPeerById(id); + if (peer) { + ZoneServer* tmp = new ZoneServer((*itr)->zone.c_str()); + database.LoadZoneInfo(tmp); + peer_manager.setZonePeerData(details, peer->id, peer->worldAddr, peer->internalWorldAddr, peer->worldPort, peer->webAddr, peer->webPort, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + tmp->GetInstanceID(), tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), tmp->GetMaximumLevel(), + tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers()); + safe_delete(tmp); + succeed = true; + break; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + if (succeed) + break; + } + } + return succeed; +} + +void PlayerGroupManager::ClearGroupRaidLooterFlag(int32 groupID) { + std::shared_lock lock(MGroups); + + if (m_groups.count(groupID) > 0) { + m_groups[groupID]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[groupID]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* curinfo = members->at(i); + if (curinfo) { + curinfo->is_raid_looter = false; + } + } + m_groups[groupID]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } +} + + + Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_position) { Entity* ret = nullptr; deque::iterator itr; - + MGroupMembers.readlock(); - + int32 count = 1; for (itr = m_members.begin(); itr != m_members.end(); itr++) { if ((*itr)->member == seeker) { continue; } count++; - if(count >= mapped_position) { + if (count >= mapped_position) { ret = (Entity*)(*itr)->member; break; } } - + MGroupMembers.releasereadlock(); return ret; @@ -998,3 +2029,21 @@ void PlayerGroup::SetDefaultGroupOptions(GroupOptions* options) { MGroupMembers.releasewritelock(); } + +bool PlayerGroup::GetDefaultGroupOptions(GroupOptions* options) { + bool setOptions = false; + MGroupMembers.readlock(); + if (options != nullptr) { + options->loot_method = group_options.loot_method; + options->loot_items_rarity = group_options.loot_items_rarity; + options->auto_split = group_options.auto_split; + options->default_yell = group_options.default_yell; + options->group_lock_method = group_options.group_lock_method; + options->group_autolock = group_options.group_autolock; + options->solo_autolock = group_options.solo_autolock; + options->auto_loot_method = group_options.auto_loot_method; + setOptions = true; + } + MGroupMembers.releasereadlock(); + return setOptions; +} diff --git a/source/WorldServer/PlayerGroups.h b/source/WorldServer/PlayerGroups.h index e7b9b27..1a24d6b 100644 --- a/source/WorldServer/PlayerGroups.h +++ b/source/WorldServer/PlayerGroups.h @@ -33,7 +33,7 @@ along with EQ2Emulator. If not, see . using namespace std; // GroupOptions isn't used yet -struct GroupOptions{ +struct GroupOptions { int8 loot_method; int8 loot_items_rarity; int8 auto_split; @@ -59,9 +59,15 @@ struct GroupMemberInfo { int8 race_id; int8 class_id; bool leader; - Client* client; - Entity* member; + Client* client; + Entity* member; int32 mentor_target_char_id; + bool is_client; + int32 zone_id; + int32 instance_id; + string client_peer_address; + int16 client_peer_port; + bool is_raid_looter; }; /// Represents a players group in game @@ -73,19 +79,22 @@ public: /// Adds a new member to the players group /// Entity to add to the group, can be a Player or NPC /// True if the member was added - bool AddMember(Entity* member); - + bool AddMember(Entity* member, bool is_leader); + bool AddMemberFromPeer(std::string name, bool isleader, bool isclient, int8 class_id, sint32 hp_cur, sint32 hp_max, int16 level_cur, int16 level_max, + sint32 power_cur, sint32 power_max, int8 race_id, std::string zonename, int32 mentor_target_char_id, int32 zone_id, int32 instance_id, + std::string peer_client_address, int16 peer_client_port, bool is_raid_looter); /// Removes a member from the players group /// Entity to remove from the player group /// True if the member was removed bool RemoveMember(Entity* member); + bool RemoveMember(std::string name, bool is_client, int32 charID); /// Removes all members from the group and destroys the group void Disband(); /// Sends updates to all the clients in the group /// Client to exclude from the update - void SendGroupUpdate(Client* exclude = 0); + void SendGroupUpdate(Client* exclude = 0, bool forceRaidUpdate = false); /// Gets the total number of members in the group /// int32, number of members in the group @@ -98,26 +107,40 @@ public: void SimpleGroupMessage(const char* message); void SendGroupMessage(int8 type, const char* message, ...); - void GroupChatMessage(Spawn* from, int32 language, const char* message); + void GroupChatMessage(Spawn* from, int32 language, const char* message, int16 channel = CHANNEL_GROUP_SAY); + void GroupChatMessage(std::string fromName, int32 language, const char* message, int16 channel = CHANNEL_GROUP_SAY); bool MakeLeader(Entity* new_leader); - + std::string GetLeaderName(); + bool ShareQuestWithGroup(Client* quest_sharer, Quest* quest); - + void RemoveClientReference(Client* remove); - void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked=false); + void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked = false); Entity* GetGroupMemberByPosition(Entity* seeker, int32 mapped_position); - - void SetDefaultGroupOptions(GroupOptions* options=nullptr); - + + void SetDefaultGroupOptions(GroupOptions* options = nullptr); + bool GetDefaultGroupOptions(GroupOptions* options); + GroupOptions* GetGroupOptions() { return &group_options; } int8 GetLastLooterIndex() { return group_options.last_looted_index; } void SetNextLooterIndex(int8 new_index) { group_options.last_looted_index = new_index; } + + int32 GetID() { return m_id; } + + void GetRaidGroups(std::vector* groups); + void ReplaceRaidGroups(std::vector* groups); + bool IsInRaidGroup(int32 groupID, bool isLeaderGroup = false); + void AddGroupToRaid(int32 groupID); + void RemoveGroupFromRaid(int32 groupID); + bool IsGroupRaid(); + void ClearGroupRaid(); Mutex MGroupMembers; // Mutex for the group members private: GroupOptions group_options; int32 m_id; // ID of this group deque m_members; // List of members in this group - + std::vector m_raidgroups; + mutable std::shared_mutex MRaidGroups; // mutex for std vector }; /// Responsible for managing all the player groups in the world @@ -130,17 +153,19 @@ public: /// ID of the group to add a member to /// Entity* to add to the group /// True if the member was added to the group - bool AddGroupMember(int32 group_id, Entity* member); + bool AddGroupMember(int32 group_id, Entity* member, bool is_leader = false); + bool AddGroupMemberFromPeer(int32 group_id, GroupMemberInfo* info); /// Removes a member from a group /// ID of the group to remove a member from /// Entity* to remove from the group /// True if the member was removed from the group bool RemoveGroupMember(int32 group_id, Entity* member); + bool RemoveGroupMember(int32 group_id, std::string name, bool is_client, int32 charID); /// Creates a new group with the provided Entity* as the leader /// The Entity* that will be the leader of the group - void NewGroup(Entity* leader); + int32 NewGroup(Entity* leader, GroupOptions* goptions, int32 override_group_id = 0); /// Removes the group from the group manager /// ID of the group to remove @@ -151,11 +176,12 @@ public: /// Player or NPC that is the target of the invite /// Error code if invite was unsuccessful, 0 if successful int8 Invite(Player* leader, Entity* member); + bool AddInvite(Player* leader, Entity* member); /// Handles accepting of a group invite /// Entity* that is accepting the invite /// Error code if accepting the invite failed, 0 if successful - int8 AcceptInvite(Entity* member); + int8 AcceptInvite(Entity* member, int32* group_override_id = nullptr, bool auto_add_group = true); /// Handles declining of a group invite /// Entity* that is declining the invite @@ -169,7 +195,7 @@ public: /// Send updates to all the clients in the group /// ID of the group to send updates to /// Client* to exclude from the update, usually the one that triggers the update - void SendGroupUpdate(int32 group_id, Client* exclude = 0); + void SendGroupUpdate(int32 group_id, Client* exclude = 0, bool forceRaidUpdate = false); PlayerGroup* GetGroup(int32 group_id); @@ -188,6 +214,8 @@ public: void ClearPendingInvite(Entity* member); + std::string HasPendingInvite(Entity* member); + void RemoveGroupBuffs(int32 group_id, Client* client); int32 GetGroupSize(int32 group_id); @@ -198,7 +226,9 @@ public: void SimpleGroupMessage(int32 group_id, const char* message); void SendGroupMessage(int32 group_id, int8 type, const char* message, ...); void GroupMessage(int32 group_id, const char* message, ...); - void GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message); + void GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message, int16 channel = CHANNEL_GROUP_SAY); + void GroupChatMessage(int32 group_id, std::string fromName, int32 language, const char* message, int16 channel = CHANNEL_GROUP_SAY); + void SendGroupChatMessage(int32 group_id, int16 channel, const char* message, ...); bool MakeLeader(int32 group_id, Entity* new_leader); void UpdateGroupBuffs(); @@ -208,11 +238,29 @@ public: bool IsSpawnInGroup(int32 group_id, string name); // used in follow Player* GetGroupLeader(int32 group_id); + void UpdateGroupMemberInfoFromPeer(int32 group_id, std::string name, bool is_client, GroupMemberInfo* updateinfo); + void SendPeerGroupData(std::string peerId); + + void ClearGroupRaid(int32 groupID); + void RemoveGroupFromRaid(int32 groupID, int32 targetGroupID); + bool IsInRaidGroup(int32 groupID, int32 targetGroupID, bool isLeaderGroup = false); + bool GetDefaultGroupOptions(int32 group_id, GroupOptions* options); + void GetRaidGroups(int32 group_id, std::vector* groups); + void ReplaceRaidGroups(int32 groupID, std::vector* newGroups); + void SetGroupOptions(int32 groupID, GroupOptions* options); + void SendWhoGroupMembers(Client* client, int32 groupID); + void SendWhoRaidMembers(Client* client, int32 groupID); + int8 AcceptRaidInvite(std::string acceptorName, int32 groupID); + bool SendRaidInvite(Client* sender, Entity* target); + 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); private: int32 m_nextGroupID; // Used to generate a new unique id for new groups map m_groups; // int32 is the group id, PlayerGroup* is a pointer to the actual group map m_pendingInvites; // First string is the person invited to the group, second string is the leader of the group + map m_raidPendingInvites; // First string is the other group leader invited to the group, second string is the leader of the raid mutable std::shared_mutex MGroups; // Mutex for the group map (m_groups) Mutex MPendingInvites; // Mutex for the pending invites map (m_pendingInvites) diff --git a/source/WorldServer/Spawn.cpp b/source/WorldServer/Spawn.cpp index 72eb580..67f1ae0 100644 --- a/source/WorldServer/Spawn.cpp +++ b/source/WorldServer/Spawn.cpp @@ -1320,6 +1320,9 @@ EQ2Packet* Spawn::serialize(Player* player, int16 version){ Spawn* Spawn::GetTarget(){ Spawn* ret = 0; + if(!GetZone()) + return 0; + // only attempt to get a spawn if we had a target stored if (target != 0) { @@ -4933,14 +4936,7 @@ void Spawn::AddIgnoredWidget(int32 id) { void Spawn::SendGroupUpdate() { if (IsEntity() && ((Entity*)this)->GetGroupMemberInfo()) { ((Entity*)this)->UpdateGroupMemberInfo(); - if (IsPlayer()) { - Client* client = ((Player*)this)->GetClient(); - if(client) { - world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id, client); - } - } - else - world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id); + world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id); } } @@ -4996,7 +4992,7 @@ bool Spawn::HasSpawnLootWindowCompleted(int32 spawn_id) { bool Spawn::HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id) { for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) { - LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnNeedGreedEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second.first); + LogWrite(LOOT__DEBUG, 1, "Loot", "%s: HasSpawnNeedGreedEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second.first); if (spawn_id == itr->second.first) { return true; } @@ -5006,7 +5002,7 @@ bool Spawn::HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id) { bool Spawn::HasSpawnLottoEntry(int32 item_id, int32 spawn_id) { for (auto [itr, rangeEnd] = lotto_items.equal_range(item_id); itr != rangeEnd; itr++) { - LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnLottoEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second); + LogWrite(LOOT__DEBUG, 1, "Loot", "%s: HasSpawnLottoEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second); if (spawn_id == itr->second) { return true; } diff --git a/source/WorldServer/SpellProcess.cpp b/source/WorldServer/SpellProcess.cpp index 18e9492..87f24ad 100644 --- a/source/WorldServer/SpellProcess.cpp +++ b/source/WorldServer/SpellProcess.cpp @@ -51,6 +51,8 @@ void SpellProcess::RemoveCaster(Spawn* caster){ if(spell->caster == caster) { spell->caster = nullptr; } + if(spell->initial_target == caster->GetID()) + spell->initial_target = 0; spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); } } @@ -2051,14 +2053,16 @@ void SpellProcess::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, boo bool foundMatch = false; - spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); - for (i = 0; i < spell->targets.size(); i++){ - if (spawn->GetID() == spell->targets.at(i)){ - foundMatch = true; - break; + if(spawn != spell->caster) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (i = 0; i < spell->targets.size(); i++){ + if (spawn->GetID() == spell->targets.at(i)){ + foundMatch = true; + break; + } } - } spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } if(foundMatch) { if (spawn->IsEntity()) ((Entity*)spawn)->RemoveSpellEffect(spell); diff --git a/source/WorldServer/Web/HTTPSClient.cpp b/source/WorldServer/Web/HTTPSClient.cpp new file mode 100644 index 0000000..6627447 --- /dev/null +++ b/source/WorldServer/Web/HTTPSClient.cpp @@ -0,0 +1,450 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#include "HTTPSClient.h" +#include "PeerManager.h" + +#include "../net.h" +#include "../../common/Log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost_net = boost::asio; // From +extern NetConnection net; +extern PeerManager peer_manager; +static const std::string base64_chars = +"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"abcdefghijklmnopqrstuvwxyz" +"0123456789+/"; + +std::string base64_encode(const std::string& input) { + std::string encoded_string; + unsigned char const* bytes_to_encode = reinterpret_cast(input.c_str()); + size_t in_len = input.size(); + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + encoded_string += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + encoded_string += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) + encoded_string += '='; + } + + return encoded_string; +} + +HTTPSClient::HTTPSClient(const std::string& certFile, const std::string& keyFile) + : certFile(certFile), keyFile(keyFile) {} + +std::shared_ptr HTTPSClient::createSSLContext() { + auto sslCtx = std::make_shared(boost::asio::ssl::context::tlsv13_client); + sslCtx->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); + sslCtx->use_certificate_file(certFile, boost::asio::ssl::context::pem); + sslCtx->use_private_key_file(keyFile, boost::asio::ssl::context::pem); + sslCtx->set_verify_mode(ssl::verify_peer); + sslCtx->set_default_verify_paths(); + return sslCtx; +} + +void HTTPSClient::parseAndStoreCookies(const http::response& res) { + if (res.count(http::field::set_cookie)) { + std::istringstream stream(res[http::field::set_cookie].to_string()); + std::string token; + + // Parse "Set-Cookie" field for name-value pairs + while (std::getline(stream, token, ';')) { + auto pos = token.find('='); + if (pos != std::string::npos) { + std::string name = token.substr(0, pos); + std::string value = token.substr(pos + 1); + cookies[name] = value; // Store each cookie + } + } + } +} + +std::string HTTPSClient::buildCookieHeader() const { + std::string cookieHeader; + for (const auto& [name, value] : cookies) { + cookieHeader += name + "=" + value; + } + return cookieHeader; +} + +std::string HTTPSClient::sendRequest(const std::string& server, const std::string& port, const std::string& target) { + try { + boost::asio::io_context ioContext; + + // SSL and TCP setup + auto sslCtx = createSSLContext(); + auto stream = std::make_shared>(ioContext, *sslCtx); + auto resolver = std::make_shared(ioContext); + auto results = resolver->resolve(server, port); + + // Persistent objects to manage response, request, and buffer + auto res = std::make_shared>(); + auto buffer = std::make_shared(); + auto req = std::make_shared>(http::verb::get, target, 11); + + // SNI hostname (required for many hosts) + if (!SSL_set_tlsext_host_name(stream->native_handle(), server.c_str())) { + throw boost::beast::system_error( + boost::beast::error_code(static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category())); + } + + // Prepare request headers + req->set(http::field::host, server); + req->set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req->set(boost::beast::http::field::connection, "close"); + req->set(http::field::content_type, "application/json"); + if (!cookies.empty()) { + req->set(http::field::cookie, buildCookieHeader()); + } + else { + std::string credentials = net.GetCmdUser() + ":" + net.GetCmdPassword(); + std::string encodedCredentials = base64_encode(credentials); + req->set(http::field::authorization, "Basic " + encodedCredentials); + } + + // Step 1: Asynchronous connect with timeout + auto connect_timer = std::make_shared(ioContext); + connect_timer->expires_after(std::chrono::seconds(2)); + + connect_timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); // Cancel operation on timeout + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Connect Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + auto timer = std::make_shared(ioContext, std::chrono::seconds(2)); + boost::asio::async_connect(stream->lowest_layer(), results, + [stream, connect_timer, req, buffer, res, timer, server, port, target](boost::system::error_code ec, const auto&) { + connect_timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Connect Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 2: Asynchronous handshake with timeout + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Handshake Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + stream->async_handshake(boost::asio::ssl::stream_base::client, + [stream, timer, req, buffer, res, server, port, target](boost::system::error_code ec) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Handshake Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 3: Asynchronous write request + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Write Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + http::async_write(*stream, *req, + [stream, buffer, res, timer, server, port, target](boost::system::error_code ec, std::size_t) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Write Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 4: Asynchronous read response + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Read Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + http::async_read(*stream, *buffer, *res, + [stream, timer, res, server, port, target](boost::system::error_code ec, std::size_t) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Read Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 5: Shutdown the stream + stream->async_shutdown([stream, server, port](boost::system::error_code ec) { + if (ec && ec != boost::asio::error::eof) { + // ignore these + //std::cerr << "Shutdown error: " << ec.message() << std::endl; + } + }); + }); + }); + }); + }); + + ioContext.run(); + + // Store cookies from the response + if (res->base().count(http::field::set_cookie) > 0) { + auto set_cookie_value = res->base()[http::field::set_cookie].to_string(); + std::istringstream stream(set_cookie_value); + std::string token; + + // Parse "Set-Cookie" field for name-value pairs + while (std::getline(stream, token, ';')) { + auto pos = token.find('='); + if (pos != std::string::npos) { + std::string name = token.substr(0, pos); + std::string value = token.substr(pos + 1); + cookies[name] = value; // Store each cookie + } + } + } + + if (res->body() == "Unauthorized") { + cookies.clear(); + } + + // Return the response body, if available + return res->body(); + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Request Error %s for %s:%s/%s", __FUNCTION__, e.what() ? e.what() : "??", server.c_str(), port.c_str(), target.c_str()); + return {}; + } +} + +std::string HTTPSClient::sendPostRequest(const std::string& server, const std::string& port, const std::string& target, const std::string& jsonPayload) { + try { + boost::asio::io_context ioContext; + + // SSL and TCP setup + auto sslCtx = createSSLContext(); + auto stream = std::make_shared>(ioContext, *sslCtx); + auto resolver = std::make_shared(ioContext); + auto results = resolver->resolve(server, port); + + // Persistent objects to manage response, request, and buffer + auto res = std::make_shared>(); + auto buffer = std::make_shared(); + auto req = std::make_shared>(http::verb::post, target, 11); + + // SNI hostname (required for many hosts) + if (!SSL_set_tlsext_host_name(stream->native_handle(), server.c_str())) { + throw boost::beast::system_error( + boost::beast::error_code(static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category())); + } + + // Prepare HTTP POST request with JSON payload + req->set(http::field::host, server); + req->set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req->set(boost::beast::http::field::connection, "close"); + req->set(http::field::content_type, "application/json"); + if (!cookies.empty()) { + req->set(http::field::cookie, buildCookieHeader()); + } + else { + std::string credentials = net.GetCmdUser() + ":" + net.GetCmdPassword(); + std::string encodedCredentials = base64_encode(credentials); + req->set(http::field::authorization, "Basic " + encodedCredentials); + } + + req->body() = jsonPayload; + req->prepare_payload(); + + // Step 1: Asynchronous connect with timeout + auto connect_timer = std::make_shared(ioContext); + connect_timer->expires_after(std::chrono::seconds(2)); + + connect_timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); // Cancel operation on timeout + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Connect Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + auto timer = std::make_shared(ioContext, std::chrono::seconds(2)); + boost::asio::async_connect(stream->lowest_layer(), results, + [stream, connect_timer, req, buffer, res, timer, server, port, target](boost::system::error_code ec, const auto&) { + connect_timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Connect Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 2: Asynchronous handshake with timeout + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Handshake Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + stream->async_handshake(boost::asio::ssl::stream_base::client, + [stream, timer, req, buffer, res, server, port, target](boost::system::error_code ec) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Handshake Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 3: Asynchronous write request + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Write Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + http::async_write(*stream, *req, + [stream, buffer, res, timer, server, port, target](boost::system::error_code ec, std::size_t) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Write Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 4: Asynchronous read response + timer->expires_after(std::chrono::seconds(2)); + + timer->async_wait([stream, server, port, target](boost::system::error_code ec) { + if (!ec) { + stream->lowest_layer().cancel(); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Read Timeout for %s:%s/%s", __FUNCTION__, server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + } + }); + + http::async_read(*stream, *buffer, *res, + [stream, timer, res, server, port, target](boost::system::error_code ec, std::size_t) { + timer->cancel(); + if (ec) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Read Error %s for %s:%s/%s", __FUNCTION__, ec.message().c_str(), server.c_str(), port.c_str(), target.c_str()); + peer_manager.SetPeerErrorState(server, port); + return; + } + + // Step 5: Shutdown the stream + stream->async_shutdown([stream, server, port](boost::system::error_code ec) { + if (ec && ec != boost::asio::error::eof) { + // ignore these + //std::cerr << "Shutdown error: " << ec.message() << std::endl; + } + }); + }); + }); + }); + }); + + ioContext.run(); + + // Store cookies from the response + if (res->base().count(http::field::set_cookie) > 0) { + auto set_cookie_value = res->base()[http::field::set_cookie].to_string(); + std::istringstream stream(set_cookie_value); + std::string token; + + // Parse "Set-Cookie" field for name-value pairs + while (std::getline(stream, token, ';')) { + auto pos = token.find('='); + if (pos != std::string::npos) { + std::string name = token.substr(0, pos); + std::string value = token.substr(pos + 1); + cookies[name] = value; // Store each cookie + } + } + } + + if (res->body() == "Unauthorized") { + cookies.clear(); + } + + // Return the response body, if available + return res->body(); + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Request Error %s for %s:%s/%s", __FUNCTION__, e.what() ? e.what() : "??", server.c_str(), port.c_str(), target.c_str()); + return {}; + } +} \ No newline at end of file diff --git a/source/WorldServer/Web/HTTPSClient.h b/source/WorldServer/Web/HTTPSClient.h new file mode 100644 index 0000000..75690f1 --- /dev/null +++ b/source/WorldServer/Web/HTTPSClient.h @@ -0,0 +1,61 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#ifndef HTTPSCLIENT_H +#define HTTPSCLIENT_H + +#include +#include +#include +#include +#include + +namespace ssl = boost::asio::ssl; +namespace asio = boost::asio; +namespace beast = boost::beast; +namespace http = beast::http; + +class HTTPSClient { +public: + HTTPSClient(const std::string& certFile, const std::string& keyFile); + + // Send a request with stored cookies and return response as string + std::string sendRequest(const std::string& server, const std::string& port, const std::string& target); + + // Send a POST request with a JSON payload and return response as string + std::string sendPostRequest(const std::string& server, const std::string& port, const std::string& target, const std::string& jsonPayload); + + std::string getServer() const { return server; } + std::string getPort() const { return port; } + +private: + std::unordered_map cookies; + + std::string certFile; + std::string keyFile; + std::string server; + std::string port; + + void parseAndStoreCookies(const http::response& res); + std::shared_ptr createSSLContext(); // New helper function + std::string buildCookieHeader() const; +}; + +#endif // HTTPSCLIENT_H diff --git a/source/WorldServer/Web/HTTPSClientPool.cpp b/source/WorldServer/Web/HTTPSClientPool.cpp new file mode 100644 index 0000000..a69c542 --- /dev/null +++ b/source/WorldServer/Web/HTTPSClientPool.cpp @@ -0,0 +1,773 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#include "HTTPSClientPool.h" +#include "PeerManager.h" +#include "../net.h" +#include "../World.h" +#include "../LoginServer.h" +#include "../WorldDatabase.h" +#include "../Guilds/Guild.h" +#include "../../common/Log.h" +#include +#include +#include +#include +#include + +extern NetConnection net; +extern PeerManager peer_manager; +extern World world; +extern LoginServer loginserver; +extern ZoneList zone_list; +extern WorldDatabase database; +extern GuildList guild_list; +extern HTTPSClientPool peer_https_pool; + +// Handler functions for each endpoint +void AddCharAuth(boost::property_tree::ptree tree) { + int32 success = 0; + std::string charName(""), zoneName(""); + int32 acct_id = 0; + int32 zoneId = 0, instanceId = 0; + bool firstLogin = false; + std::string worldAddr(""), internalWorldAddr(""), clientIP(""); + int16 worldPort = 0; + int32 charID = 0; + int32 worldID = 0, fromID = 0; + int32 loginKey = 0; + if (auto char_name = tree.get_optional("character_name")) { + charName = char_name.get(); + } + if (auto successful = tree.get_optional("success")) { + success = successful.get(); + } + if (auto account_id = tree.get_optional("account_id")) { + acct_id = account_id.get(); + } + if (auto zone_name = tree.get_optional("zone_name")) { + zoneName = zone_name.get(); + } + if (auto zone_id = tree.get_optional("zone_id")) { + zoneId = zone_id.get(); + } + if (auto instance_id = tree.get_optional("instance_id")) { + instanceId = instance_id.get(); + } + if (auto first_login = tree.get_optional("first_login")) { + firstLogin = first_login.get(); + } + + if (auto peerclientaddr = tree.get_optional("peer_client_address")) { + worldAddr = peerclientaddr.get(); + } + if (auto peerclient_internaladdr = tree.get_optional("peer_client_internal_address")) { + internalWorldAddr = peerclient_internaladdr.get(); + } + if (auto peerclientport = tree.get_optional("peer_client_port")) { + worldPort = peerclientport.get(); + } + if (auto clientip = tree.get_optional("client_ip")) { + clientIP = clientip.get(); + } + if (auto character_id = tree.get_optional("character_id")) { + charID = character_id.get(); + } + if (auto login_key = tree.get_optional("login_key")) { + loginKey = login_key.get(); + } + if (auto world_id = tree.get_optional("world_id")) { + worldID = world_id.get(); + } + if (auto from_id = tree.get_optional("from_id")) { + fromID = from_id.get(); + } + + LogWrite(PEERING__INFO, 0, "Peering", "%s: Handling AddCharAuth %s (%u) for zone %u instance %u", __FUNCTION__, charName.c_str(), charID, zoneId, instanceId); + + if (firstLogin) { + loginserver.SendCharApprovedLogin(success, worldAddr, internalWorldAddr, clientIP, worldPort, acct_id, charID, loginKey, worldID, fromID); + } + else if (acct_id > 0 && charName.length() > 0) { + world.ClientAuthApproval(success, charName, acct_id, zoneName, zoneId, instanceId, firstLogin); + } +} + +void StartZone(boost::property_tree::ptree tree) { + std::string peerWebAddress(""); + int16 peerWebPort = 0; + if (auto addr = tree.get_optional("peer_web_address")) { + peerWebAddress = addr.get(); + } + if (auto port = tree.get_optional("peer_web_port")) { + peerWebPort = port.get(); + } + + std::string id = peer_manager.isPeer(peerWebAddress, peerWebPort); + + if (id.size() > 0) { + std::string strPort = std::to_string(peerWebPort); + auto client = peer_https_pool.getOrCreateClient(id, peerWebAddress, strPort); + if (client) { + auto responseZones = client->sendRequest(peerWebAddress, strPort, "/zones"); // Assumes HTTPSClient has a get method + // Load the JSON data into the property tree + std::istringstream json_stream(responseZones); + if (json_stream.str().empty()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Stream Empty for %s:%s/zones", __FUNCTION__, peerWebAddress.c_str(), strPort.c_str()); + } + else if (json_stream.fail()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Failed State for %s:%s/zones", __FUNCTION__, peerWebAddress.c_str(), strPort.c_str()); + } + else if (json_stream.bad()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Stream Bad for %s:%s/zones", __FUNCTION__, peerWebAddress.c_str(), strPort.c_str()); + } + else if (json_stream.eof()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Stream EOF for %s:%s/zones", __FUNCTION__, peerWebAddress.c_str(), strPort.c_str()); + } + else { + boost::property_tree::ptree pt; + boost::property_tree::read_json(json_stream, pt); + peer_manager.updateZoneTree(id, pt); + LogWrite(PEERING__DEBUG, 5, "Peering", "%s: StartZone Update for %s:%s/zones complete, zone tree updated.", __FUNCTION__, peerWebAddress.c_str(), strPort.c_str()); + } + } + } +} + +void SendGlobalMessage(boost::property_tree::ptree tree) { + int32 success = 0; + int8 language = 0; + int16 in_channel = 0; + std::string toName(""), fromName(""), msg(""), awaymsg(""); + int8 away_language = 0; + int32 group_id = 0; + if (auto successful = tree.get_optional("success")) { + success = successful.get(); + } + if (auto name = tree.get_optional("to_name")) { + toName = name.get(); + } + if (auto name = tree.get_optional("from_name")) { + fromName = name.get(); + } + if (auto message = tree.get_optional("message")) { + msg = message.get(); + } + if (auto from_language = tree.get_optional("from_language")) { + language = from_language.get(); + } + if (auto channel = tree.get_optional("channel")) { + in_channel = channel.get(); + } + + if (auto msg = tree.get_optional("away_message")) { + awaymsg = msg.get(); + } + if (auto away_lang = tree.get_optional("away_language")) { + away_language = away_lang.get(); + } + if (auto group = tree.get_optional("group_id")) { + group_id = group.get(); + } + + switch (in_channel) { + case CHANNEL_PRIVATE_TELL: { + Client* from_client = zone_list.GetClientByCharName(fromName.c_str()); + if (from_client) { + if (success) { + from_client->HandleTellMessage(from_client->GetPlayer()->GetName(), msg.c_str(), toName.c_str(), language); + if (awaymsg.size() > 0) { + from_client->HandleTellMessage(toName.c_str(), awaymsg.c_str(), from_client->GetPlayer()->GetName(), away_language); + } + } + else { + from_client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + } + } + break; + } + } +} + +void HandleNewGroup(boost::property_tree::ptree tree) { + + int32 success = 0; + if (auto successful = tree.get_optional("success")) { + success = successful.get(); + } + if (!net.is_primary) { + // we are a peer we ask primary for a new group and get the response + GroupOptions options; + std::string leader(""), member(""); + int32 group_id = 0; + int32 member_entity_id = 0; + if (auto leader_name = tree.get_optional("leader_name")) { + leader = leader_name.get(); + } + if (auto member_name = tree.get_optional("member_name")) { + member = member_name.get(); + } + if (auto lootmethod = tree.get_optional("loot_method")) { + options.loot_method = lootmethod.get(); + } + if (auto itemrarity = tree.get_optional("loot_item_rarity")) { + options.loot_items_rarity = itemrarity.get(); + } + if (auto autosplit = tree.get_optional("auto_split")) { + options.auto_split = autosplit.get(); + } + if (auto defaultyell = tree.get_optional("default_yell")) { + options.default_yell = defaultyell.get(); + } + if (auto grouplockmethod = tree.get_optional("group_lock_method")) { + options.group_lock_method = grouplockmethod.get(); + } + if (auto groupautolock = tree.get_optional("group_auto_lock")) { + options.group_autolock = groupautolock.get(); + } + if (auto soloautolock = tree.get_optional("solo_auto_lock")) { + options.solo_autolock = soloautolock.get(); + } + if (auto autoloot = tree.get_optional("auto_loot_method")) { + options.auto_loot_method = autoloot.get(); + } + if (auto lastlootindex = tree.get_optional("last_looted_index")) { + options.last_looted_index = lastlootindex.get(); + } + if (auto groupid = tree.get_optional("group_id")) { + group_id = groupid.get(); + } + + if (auto entityid = tree.get_optional("member_entity_id")) { + member_entity_id = entityid.get(); + } + + LogWrite(PEERING__INFO, 0, "Peering", "%s: Add New Group %u with member %s (%u) leader %s", __FUNCTION__, group_id, member.c_str(), member_entity_id, leader.c_str()); + Client* member_client = zone_list.GetClientByCharName(member.c_str()); + Client* client_leader = zone_list.GetClientByCharName(leader.c_str()); + if (success) { + if (!member_client) { + if (client_leader && client_leader->GetPlayer()->GetZone()) { + Spawn* spawn = client_leader->GetPlayer()->GetZone()->GetSpawnByID(member_entity_id); + if (spawn && spawn->IsEntity()) { + world.GetGroupManager()->AcceptInvite((Entity*)spawn, &group_id); + } + else { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Failed Adding New Group %u, entity %s (%u) did not exist.", __FUNCTION__, group_id, member.c_str(), member_entity_id); + } + } + else { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Failed Adding New Group %u, member %s (%u) did not exist.", __FUNCTION__, group_id, member.c_str(), member_entity_id); + } + } + else if (!client_leader) { + if (member_client) + member_client->HandleGroupAcceptResponse(2); + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Failed Adding New Group %u, leader %s did not exist.", __FUNCTION__, group_id, leader.c_str()); + } + else { + world.GetGroupManager()->AcceptInvite(member_client->GetPlayer(), &group_id); + } + } + } +} + + +void HandleCreateGuild(boost::property_tree::ptree tree) { + + bool success = false; + int32 guildID = 0; + std::string leaderName(""); + if (auto successful = tree.get_optional("success")) { + success = successful.get(); + } + + if (auto guild_id = tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + + if (auto name = tree.get_optional("leader_name")) { + leaderName = name.get(); + } + + if (net.is_primary) { + // we send out to peers + } + else if (guildID) { + database.LoadGuild(guildID); + Guild* guild = guild_list.GetGuild(guildID); + Client* leader = zone_list.GetClientByCharName(leaderName.c_str()); + if (leader && guild && !leader->GetPlayer()->GetGuild()) { + guild->AddNewGuildMember(leader, 0, GUILD_RANK_LEADER); + database.SaveGuildMembers(guild); + if (leader && leader->GetPlayer()->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(leader->GetPlayer()->GetGroupMemberInfo()->group_id); + if (group) + { + GroupMemberInfo* gmi = nullptr; + deque::iterator itr; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + gmi = *itr; + if (gmi->client && gmi->client != leader && !gmi->client->GetPlayer()->GetGuild()) + guild->InvitePlayer(gmi->client, leader->GetPlayer()->GetName()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + } + } +} + +// Define a type for handler functions +using HandlerFunction = std::function; + +// Set up the endpoint-to-function map +std::unordered_map endpointHandlers = { + {"/addcharauth", AddCharAuth}, + {"/startzone", StartZone}, + {"/sendglobalmessage", SendGlobalMessage}, + {"/newgroup", HandleNewGroup}, + {"/createguild", HandleCreateGuild} +}; + +HTTPSClientPool::HTTPSClientPool() {} + +void HTTPSClientPool::init(const std::string& cert, const std::string& key) { + certFile = cert; + keyFile = key; + pollingInterval = 1000; + + int32 numThreads = 2; + // Start worker threads + for (int32 i = 0; i < numThreads; ++i) { + workers.emplace_back(&HTTPSClientPool::workerFunction, this); + } +} + +HTTPSClientPool::~HTTPSClientPool() { + { + std::unique_lock lock(queueMutex); + stop = true; + } + condition.notify_all(); + for (std::thread& worker : workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +void HTTPSClientPool::addPeerClient(const std::string& peerId, const std::string& server, const std::string& port, const std::string& authEndpoint) { + bool newClient = false; + // Check if client already exists for the specified (server, port) pair + auto it = clients.find(std::make_pair(server, port)); + if (it == clients.end()) { + newClient = true; + } + + if (newClient) { + auto client = getOrCreateClient(peerId, server, port); + } +} + +boost::property_tree::ptree HTTPSClientPool::sendRequestToPeer(const std::string& peerId, const std::string& target) { + auto client = getClient(peerId); + boost::property_tree::ptree pt; + + if (!client) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Client for peer %s could not be found.", __FUNCTION__, peerId.c_str()); + return pt; + } + + // Retrieve the response from the client + std::string responseBody = client->sendRequest(client->getServer(), client->getPort(), target); + + if (responseBody.size() < 1) { + + int16 peer_port = 0; + try { + peer_port = std::stoul(client->getPort()); + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Error for convering peer port for %s: %s.", __FUNCTION__, peerId.c_str(), e.what() ? e.what() : "??"); + } + HealthStatus curStatus = peer_manager.getPeerStatus(client->getServer(), peer_port); + switch (curStatus) { + case HealthStatus::WARN: { + peer_manager.updateHealth(peerId, HealthStatus::ERROR); + break; + } + case HealthStatus::ERROR: { + peer_manager.updateHealth(peerId, HealthStatus::SHUTDOWN); + break; + } + default: { + peer_manager.updateHealth(peerId, HealthStatus::WARN); + break; + } + } + } + else { + // Parse the response as JSON + try { + std::istringstream responseStream(responseBody); + boost::property_tree::read_json(responseStream, pt); + } + catch (const boost::property_tree::json_parser_error& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Parsing error for %s: %s.", __FUNCTION__, peerId.c_str(), e.what() ? e.what() : "??"); + } + } + + return pt; +} + +boost::property_tree::ptree HTTPSClientPool::sendPostRequestToPeer(const std::string& peerId, const std::string& target, const std::string& jsonPayload) { + auto client = getClient(peerId); + boost::property_tree::ptree pt; + + if (!client) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Client for peer %s could not be found.", __FUNCTION__, peerId.c_str()); + return pt; + } + + // Retrieve the response from the client + std::string responseBody = client->sendPostRequest(client->getServer(), client->getPort(), target, jsonPayload); + + // Parse the response as JSON + try { + std::istringstream responseStream(responseBody); + boost::property_tree::read_json(responseStream, pt); + } + catch (const boost::property_tree::json_parser_error& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Client for peer %s could not be found.", __FUNCTION__, peerId.c_str()); + } + + return pt; +} + +std::shared_ptr HTTPSClientPool::getClient(const std::string& peerId) { + std::unique_lock lock(queueMutex); + // Lookup the client by ID + auto idIt = clientsById.find(peerId); + if (idIt != clientsById.end()) { + return idIt->second; + } + return nullptr; // Return nullptr if no client exists with the given ID +} + +std::shared_ptr HTTPSClientPool::getOrCreateClient(const std::string& id, const std::string& server, const std::string& port) { + std::unique_lock lock(queueMutex); + // Create a key based on (server, port) + auto clientKey = std::make_pair(server, port); + + // Check if client already exists for the specified (server, port) pair + auto it = clients.find(clientKey); + if (it != clients.end()) { + // Client already exists, return the existing client + return it->second; + } + + // No existing client for this (server, port), create and store a new HTTPSClient + auto client = std::make_shared(certFile, keyFile); + clients[clientKey] = client; + clientsById[id] = client; + + return client; +} + +void HTTPSClientPool::pollPeerHealth(const std::string& server, const std::string& port) { + int16 web_worldport = 0; + try { + web_worldport = std::stoul(port); + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Getting port for peer %s:%s could not be complete.", __FUNCTION__, server.c_str(), port.c_str()); + } + std::string id = peer_manager.isPeer(server, web_worldport); + if (id.size() < 1) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Error finding peer %s:%s.", __FUNCTION__, server.c_str(), port.c_str()); + } + else { + auto client = getOrCreateClient(id, port, server + ":" + port); + int16 interval = pollingInterval; + while (running.load()) { + interval++; + if (interval > pollingInterval) { + HealthStatus curStatus = peer_manager.getPeerStatus(server, web_worldport); + id = peer_manager.isPeer(server, web_worldport); + try { + auto response = client->sendRequest(server, port, "/status"); // Assumes HTTPSClient has a get method + //std::cout << "Health check response from " << server << ":" << port << " - " << response << std::endl; + + boost::property_tree::ptree json_tree; + std::istringstream json_stream(response); + boost::property_tree::read_json(json_stream, json_tree); + std::string online_status; + int16 peer_priority = 65535; + bool peer_primary = false; + if (auto status = json_tree.get_optional("world_status")) { + online_status = status.get(); + } + if (auto priority = json_tree.get_optional("peer_priority")) { + peer_priority = priority.get(); + } + if (auto isprimary = json_tree.get_optional("peer_primary")) { + peer_primary = isprimary.get(); + } + peer_manager.updatePriority(id, peer_priority); + + if (peer_primary && net.is_primary) { + peer_manager.handlePrimaryConflict(id); + std::shared_ptr hasPrimary = peer_manager.getHealthyPrimaryPeerPtr(); + if (hasPrimary) { // demote self + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s at %s:%s - HAS PRIMARY status, demoting self.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + net.SetPrimary(false); + } + } + + switch (curStatus) { + case HealthStatus::STARTUP: { + pollPeerHealthData(client, id, server, port); + if (online_status == "offline") { + std::shared_ptr peer = peer_manager.getPeerById(id); + if (peer) { + peer->wasOffline = true; + if (peer->sentInitialPeerData) { + peer->sentInitialPeerData = false; + } + } + } + if (online_status == "online") { + peer_manager.updateHealth(id, HealthStatus::OK); + std::shared_ptr peer = peer_manager.getPeerById(id); + if (net.is_primary) { + if (peer) { + if (peer->wasOffline && !peer->sentInitialPeerData) { + world.GetGroupManager()->SendPeerGroupData(id); + } + peer->sentInitialPeerData = true; + } + } + else if (peer) { // set as if we already sent the data since if we take over we don't want the peer trying to resubmit all groups + peer->wasOffline = false; + peer->sentInitialPeerData = true; + } + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s at %s:%s - HAS OK/UP state.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + } + if (!net.is_primary && !peer_manager.hasPrimary() && peer_priority < net.GetPeerPriority()) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s at %s:%s - HAS PRIMARY.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + peer_manager.setPrimary(id); + net.SetPrimary(false); + } + else if (!peer_manager.hasPrimary() && !net.is_primary && net.GetPeerPriority() <= peer_priority) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: I AM PRIMARY!", __FUNCTION__); + net.SetPrimary(); + } + break; + } + case HealthStatus::OK: { + pollPeerHealthData(client, id, server, port); + if (!net.is_primary && !peer_manager.hasPrimary() && peer_priority < net.GetPeerPriority()) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s at %s:%s - HAS PRIMARY.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + peer_manager.setPrimary(id); + net.SetPrimary(false); + } + else if (!peer_manager.hasPrimary() && !net.is_primary && net.GetPeerPriority() <= peer_priority) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: I AM PRIMARY!!", __FUNCTION__); + net.SetPrimary(); + } + break; + } + case HealthStatus::WARN: + case HealthStatus::ERROR: + case HealthStatus::SHUTDOWN: { + peer_manager.updateHealth(id, HealthStatus::STARTUP); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s at %s:%s - HAS ENTERED STARTUP state.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + + if (net.is_primary) { + std::shared_ptr peer = peer_manager.getPeerById(id); + if (peer && peer->sentInitialPeerData == true) { + peer->sentInitialPeerData = false; + } + } + break; + } + } + } + catch (const std::exception& e) { + HealthStatus curStatus = peer_manager.getPeerStatus(server, web_worldport); + switch (curStatus) { + case HealthStatus::WARN: { + peer_manager.updateHealth(id, HealthStatus::ERROR); + break; + } + case HealthStatus::ERROR: { + + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Peer %s at %s:%s - HAS ERROR->SHUTDOWN state.", __FUNCTION__, id.c_str(), server.c_str(), port.c_str()); + peer_manager.updateHealth(id, HealthStatus::SHUTDOWN); + if (peer_manager.getHealthyPeer() == std::nullopt) { + if (!net.is_primary && world.world_loaded) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: TAKING OVER AS PRIMARY, NO PEERS AVAILABLE TO CHECK", __FUNCTION__); + net.SetPrimary(); + } + } + else if (!peer_manager.hasPrimary()) { + std::string newPrimary = peer_manager.getPriorityPeer(); + if (newPrimary.size() > 0) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: NEW PRIMARY %s", __FUNCTION__, newPrimary); + peer_manager.setPrimary(newPrimary); + net.SetPrimary(false); + } + else { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: NEW PRIMARY CANNOT BE ESTABLISHED!", __FUNCTION__); + } + } + break; + } + default: { + peer_manager.updateHealth(id, HealthStatus::WARN); + break; + } + } + LogWrite(PEERING__ERROR, 0, "Peering", "%s: ERROR POLLING %s:%s reason: %s", __FUNCTION__, server.c_str(), port.c_str(), e.what() ? e.what() : "??"); + } + interval = 0; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } +} + +void HTTPSClientPool::pollPeerHealthData(auto client, const std::string& id, const std::string& server, const std::string& port) { + if (client == nullptr) { + + } + auto responseZones = client->sendRequest(server, port, "/zones"); // Assumes HTTPSClient has a get method + // Load the JSON data into the property tree + std::istringstream json_stream(responseZones); + if (json_stream.str().empty()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream Empty for %s:%s/zones", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream.fail()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Failed State for %s:%s/zones", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream.bad()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream Bad for %s:%s/zones", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream.eof()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream EOF for %s:%s/zones", __FUNCTION__, server.c_str(), port.c_str()); + } + else { + boost::property_tree::ptree pt; + boost::property_tree::read_json(json_stream, pt); + peer_manager.updateZoneTree(id, pt); + LogWrite(PEERING__DEBUG, 5, "Peering", "%s: Polling for %s:%s/zones complete, zone tree updated.", __FUNCTION__, server.c_str(), port.c_str()); + } + auto responseClients = client->sendRequest(server, port, "/clients"); // Assumes HTTPSClient has a get method + + // Load the JSON data into the property tree + std::istringstream json_stream2(responseClients); + if (json_stream2.str().empty()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream Empty for %s:%s/clients", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream2.fail()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Failed State for %s:%s/clients", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream2.bad()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream Bad for %s:%s/clients", __FUNCTION__, server.c_str(), port.c_str()); + } + else if (json_stream2.eof()) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Polling JSON Stream EOF for %s:%s/clients", __FUNCTION__, server.c_str(), port.c_str()); + } + else { + boost::property_tree::ptree pt2; + boost::property_tree::read_json(json_stream2, pt2); + peer_manager.updateClientTree(id, pt2); + } +} + +void HTTPSClientPool::startPolling() { + running.store(true); + + for (const auto& clientPair : clients) { + auto server = clientPair.first.first; + auto port = clientPair.first.second; + + std::async(std::launch::async, &HTTPSClientPool::pollPeerHealth, this, server, port); + } +} + +void HTTPSClientPool::stopPolling() { + running.store(false); +} + +void HTTPSClientPool::sendPostRequestToPeerAsync(const std::string& peerId, const std::string& server, const std::string& port, const std::string& target, const std::string& payload) { + { + std::unique_lock lock(queueMutex); + taskQueue.emplace([this, peerId, server, port, target, payload]() { + std::shared_ptr client = getClient(peerId); + if (client) { + std::string response = client->sendPostRequest(server, port, target, payload); + + boost::property_tree::ptree pt; + + try { + std::istringstream responseStream(response); + boost::property_tree::read_json(responseStream, pt); + } + catch (const boost::property_tree::json_parser_error& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: JSON Parsing error for %s (%s:%s): %s.", __FUNCTION__, peerId.c_str(), server.c_str(), port.c_str(), e.what() ? e.what() : "??"); + } + if (endpointHandlers.find(target) != endpointHandlers.end()) { + endpointHandlers[target](pt); // Call the corresponding handler + } + else { + //std::cout << "No handler for endpoint: " << endpoint << std::endl; + } + } + else { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Client not found for %s (%s:%s).", __FUNCTION__, peerId.c_str(), server.c_str(), port.c_str()); + } + }); + } + condition.notify_one(); +} + +void HTTPSClientPool::workerFunction() { + while (true) { + std::function task; + { + std::unique_lock lock(queueMutex); + condition.wait(lock, [this] { return stop || !taskQueue.empty(); }); + if (stop && taskQueue.empty()) { + return; + } + task = std::move(taskQueue.front()); + taskQueue.pop(); + } + task(); + } +} \ No newline at end of file diff --git a/source/WorldServer/Web/HTTPSClientPool.h b/source/WorldServer/Web/HTTPSClientPool.h new file mode 100644 index 0000000..d980cfd --- /dev/null +++ b/source/WorldServer/Web/HTTPSClientPool.h @@ -0,0 +1,92 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#ifndef HTTPSCLIENTPOOL_H +#define HTTPSCLIENTPOOL_H + +#include "HTTPSClient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include Boost property tree + +struct pair_hash { + template + std::size_t operator()(const std::pair& pair) const { + return std::hash()(pair.first) ^ std::hash()(pair.second); + } +}; + +class HTTPSClientPool { +public: + HTTPSClientPool(); + ~HTTPSClientPool(); + + // init cert and key file + void init(const std::string& cert, const std::string& key); + + // Pre-authenticate and add a client to the pool + void addPeerClient(const std::string& peerId, const std::string& server, const std::string& port, const std::string& authEndpoint); + + std::shared_ptr getOrCreateClient(const std::string& id, const std::string& server, const std::string& port); + + // Send a request to a peer by ID and parse response as a ptree + boost::property_tree::ptree sendRequestToPeer(const std::string& peerId, const std::string& target); + boost::property_tree::ptree sendPostRequestToPeer(const std::string& peerId, const std::string& target, const std::string& jsonPayload); + void pollPeerHealthData(auto client, const std::string& id, const std::string& server, const std::string& port); + + void startPolling(); // Starts asynchronous polling of peers + void stopPolling(); // Stops the polling process + + // Sends a POST request asynchronously by adding it to the task queue + void sendPostRequestToPeerAsync(const std::string& peerId, const std::string& server, const std::string& port, const std::string& target, const std::string& payload); + + // Worker thread function + void workerFunction(); + + bool isPolling() { return running; } +private: + std::shared_ptr getClient(const std::string& peerId); + + std::unordered_map, std::shared_ptr, pair_hash> clients; + std::unordered_map> clientsById; // New map for ID-based lookup + std::string certFile; + std::string keyFile; + int pollingInterval; // Polling interval in milliseconds + + std::queue> taskQueue; // Queue of tasks to execute + std::mutex queueMutex; // Mutex to protect access to the task queue + std::condition_variable condition; // Condition variable to signal worker threads + std::vector workers; // Worker threads + bool stop = false; // Flag to stop workers + + std::atomic running; // Flag to control polling loop + void pollPeerHealth(const std::string& server, const std::string& port); // Polls individual peer +}; + +#endif // HTTPSCLIENTPOOL_H diff --git a/source/WorldServer/Web/PeerManager.cpp b/source/WorldServer/Web/PeerManager.cpp new file mode 100644 index 0000000..74d219d --- /dev/null +++ b/source/WorldServer/Web/PeerManager.cpp @@ -0,0 +1,772 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#include "PeerManager.h" +#include "../../common/Log.h" +#include "../net.h" +#include "../PlayerGroups.h" +#include "HTTPSClientPool.h" + +extern NetConnection net; +extern HTTPSClientPool peer_https_pool; + +// HealthCheck method definitions +void HealthCheck::updateStatus(HealthStatus newStatus) { + status = newStatus; + lastReceived = std::chrono::system_clock::now(); +} + +std::chrono::duration HealthCheck::timeSinceLastCheck() const { + return std::chrono::system_clock::now() - lastReceived; +} + +ZoneChangeDetails::ZoneChangeDetails(std::string peer_id, std::string peer_world_address, std::string peer_internal_world_address, int16 peer_world_port, + std::string peer_web_address, int16 peer_web_port, std::string zone_file_name, std::string zone_name, int32 zone_id, + int32 instance_id, float safe_x, float safe_y, float safe_z, float safe_heading, bool lock_state, sint16 min_status, + int16 min_level, int16 max_level, int16 min_version, int32 default_lockout_time, int32 default_reenter_time, int8 instance_type, int32 num_players) + : peerId(std::move(peer_id)), peerWorldAddress(peer_world_address), peerInternalWorldAddress(peer_internal_world_address), peerWorldPort(peer_world_port), + peerWebAddress(peer_web_address), peerWebPort(peer_web_port), zoneFileName(zone_file_name), zoneName(zone_name), zoneId(zone_id), instanceId(instance_id), + safeX(safe_x), safeY(safe_y), safeZ(safe_z), safeHeading(safe_heading), lockState(lock_state), minStatus(min_status), minLevel(min_level), + maxLevel(max_level), minVersion(min_version), defaultLockoutTime(default_lockout_time), defaultReenterTime(default_reenter_time), instanceType(instance_type), numPlayers(num_players) { + zonePtr = nullptr; +} + +ZoneChangeDetails::ZoneChangeDetails(ZoneChangeDetails* copy_details) : peerId(copy_details->peerId), peerWorldAddress(copy_details->peerWorldAddress), peerWorldPort(copy_details->peerWorldPort), +peerWebAddress(copy_details->peerWebAddress), peerWebPort(copy_details->peerWebPort), zoneFileName(copy_details->zoneFileName), zoneName(copy_details->zoneName), +zoneId(copy_details->zoneId), instanceId(copy_details->instanceId), safeX(copy_details->safeX), safeY(copy_details->safeY), safeZ(copy_details->safeZ), +safeHeading(copy_details->safeHeading), lockState(copy_details->lockState), minStatus(copy_details->minStatus), minLevel(copy_details->minLevel), +maxLevel(copy_details->maxLevel), minVersion(copy_details->minVersion), defaultLockoutTime(copy_details->defaultLockoutTime), +defaultReenterTime(copy_details->defaultReenterTime), instanceType(copy_details->instanceType), numPlayers(copy_details->numPlayers), +peerAuthorized(copy_details->peerAuthorized), zoneKey(copy_details->zoneKey), authDispatchedTime(copy_details->authDispatchedTime), +zoningPastAuth(copy_details->zoningPastAuth), zonePtr(copy_details->zonePtr) { + +} + +// PeerManager method definitions +void PeerManager::addPeer(std::string id, PeeringStatus status, std::string client_address, std::string client_internal_address, int16 client_port, std::string web_address, int16 web_port) { + std::shared_ptr peer = std::make_shared(id, PeeringStatus::SECONDARY, client_address, client_internal_address, client_port, web_address, web_port); + peers.emplace(id, peer); +} + +void PeerManager::updateHealth(const std::string& id, HealthStatus newStatus) { + if (peers.find(id) != peers.end()) { + peers[id]->healthCheck.updateStatus(newStatus); + } +} + +void PeerManager::updatePriority(const std::string& id, int16 priority) { + if (peers.find(id) != peers.end()) { + peers[id]->peerPriority = priority; + } +} + +void PeerManager::updateZoneTree(const std::string& id, const boost::property_tree::ptree& newTree) { + auto it = peers.find(id); + if (it != peers.end()) { + std::lock_guard lock(it->second->dataMutex); + *(it->second->zone_tree) = newTree; + } +} + +void PeerManager::updateClientTree(const std::string& id, const boost::property_tree::ptree& newTree) { + auto it = peers.find(id); + if (it != peers.end()) { + std::lock_guard lock(it->second->dataMutex); + *(it->second->client_tree) = newTree; + } +} + +void PeerManager::setZonePeerData(ZoneChangeDetails* opt_details, std::string peerId, std::string peerWorldAddress, std::string peerInternalWorldAddress, int16 peerWorldPort, + std::string peerWebAddress, int16 peerWebPort, std::string zoneFileName, + std::string zoneName, int32 zoneId, int32 instanceId, float safeX, float safeY, float safeZ, float safeHeading, bool lockState, sint16 minStatus, + int16 minLevel, int16 maxLevel, int16 minVersion, int32 defaultLockoutTime, int32 defaultReenterTime, int8 instanceType, int32 numPlayers) { + if (opt_details) { + opt_details->peerId = peerId; + opt_details->peerWorldAddress = peerWorldAddress; + opt_details->peerInternalWorldAddress = peerInternalWorldAddress; + opt_details->peerWorldPort = peerWorldPort; + opt_details->peerWebAddress = peerWebAddress; + opt_details->peerWebPort = peerWebPort; + opt_details->zoneFileName = zoneFileName; + opt_details->zoneName = zoneName; + opt_details->zoneId = zoneId; + opt_details->instanceId = instanceId; + opt_details->safeX = safeX; + opt_details->safeY = safeY; + opt_details->safeZ = safeZ; + opt_details->safeHeading = safeHeading; + opt_details->lockState = lockState; + opt_details->minStatus = minStatus; + opt_details->minLevel = minLevel; + opt_details->maxLevel = maxLevel; + opt_details->minVersion = minVersion; + opt_details->minVersion = minVersion; + opt_details->defaultLockoutTime = defaultLockoutTime; + opt_details->defaultReenterTime = defaultReenterTime; + opt_details->instanceType = instanceType; + opt_details->numPlayers = numPlayers; + opt_details->zonePtr = nullptr; + opt_details->peerAuthorized = false; + opt_details->zoneKey = 0; + opt_details->authDispatchedTime = 0; + opt_details->zoningPastAuth = false; + } +} + +void PeerManager::setZonePeerDataSelf(ZoneChangeDetails* opt_details, std::string zoneFileName, std::string zoneName, int32 zoneId, + int32 instanceId, float safeX, float safeY, float safeZ, float safeHeading, bool lockState, sint16 minStatus, + int16 minLevel, int16 maxLevel, int16 minVersion, int32 defaultLockoutTime, int32 defaultReenterTime, int8 instanceType, + int32 numPlayers, void* zonePtr) { + if (opt_details) { + opt_details->peerId = "self"; + opt_details->peerWorldAddress = net.GetWorldAddress(); + opt_details->peerInternalWorldAddress = net.GetInternalWorldAddress(); + opt_details->peerWorldPort = net.GetWorldPort(); + opt_details->peerWebAddress = net.GetWebWorldAddress(); + opt_details->peerWebPort = net.GetWebWorldPort(); + opt_details->zoneFileName = zoneFileName; + opt_details->zoneName = zoneName; + opt_details->zoneId = zoneId; + opt_details->instanceId = instanceId; + opt_details->safeX = safeX; + opt_details->safeY = safeY; + opt_details->safeZ = safeZ; + opt_details->safeHeading = safeHeading; + opt_details->lockState = lockState; + opt_details->minStatus = minStatus; + opt_details->minLevel = minLevel; + opt_details->maxLevel = maxLevel; + opt_details->minVersion = minVersion; + opt_details->defaultLockoutTime = defaultLockoutTime; + opt_details->defaultReenterTime = defaultReenterTime; + opt_details->instanceType = instanceType; + opt_details->numPlayers = numPlayers; + opt_details->zonePtr = zonePtr; + opt_details->peerAuthorized = true; + opt_details->zoneKey = 0; + opt_details->authDispatchedTime = 0; + opt_details->zoningPastAuth = true; + } +} + +std::string PeerManager::getZonePeerId(const std::string& inc_zone_name, int32 inc_zone_id, int32 inc_instance_id, ZoneChangeDetails* opt_details, bool only_always_loaded) { + for (auto& [peerId, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->zone_tree->get_child("Zones")) { + // Access each field within the current zone + std::string zone_name = zone.second.get("zone_name"); + std::string zone_file_name = zone.second.get("zone_file_name"); + int32 zone_id = zone.second.get("zone_id"); + int32 instance_id = zone.second.get("instance_id"); + bool shutting_down = zone.second.get("shutting_down") == "true"; + bool instance_zone = zone.second.get("instance_zone") == "true"; + int32 num_players = zone.second.get("num_players"); + bool city_zone = zone.second.get("city_zone") == "true"; + float safe_x = zone.second.get("safe_x"); + float safe_y = zone.second.get("safe_y"); + float safe_z = zone.second.get("safe_z"); + float safe_heading = zone.second.get("safe_heading"); + bool lock_state = zone.second.get("lock_state"); + sint16 min_status = zone.second.get("min_status"); + int16 min_level = zone.second.get("min_level"); + int16 max_level = zone.second.get("max_level"); + int16 min_version = zone.second.get("min_version"); + int32 default_lockout_time = zone.second.get("default_lockout_time"); + int32 default_reenter_time = zone.second.get("default_reenter_time"); + int8 instance_type = zone.second.get("instance_type"); + bool always_loaded = zone.second.get("always_loaded"); + + if (only_always_loaded && !always_loaded) + continue; + + if (!shutting_down) { + bool match = false; + if (instance_zone && inc_instance_id > 0 && instance_id == inc_instance_id) { + match = true; + } + else if (!instance_zone && inc_instance_id == 0 && inc_zone_id > 0 && zone_id == inc_zone_id) { + match = true; + } + else if (!instance_zone && inc_zone_name.length() > 0 && strncasecmp(zone_name.c_str(), inc_zone_name.c_str(), inc_zone_name.length()) == 0) { + match = true; + } + + if (match) { + setZonePeerData(opt_details, peerId, peer->worldAddr, peer->internalWorldAddr, peer->worldPort, peer->webAddr, peer->webPort, zone_file_name, zone_name, zone_id, instance_id, + safe_x, safe_y, safe_z, safe_heading, lock_state, min_status, min_level, max_level, min_version, default_lockout_time, default_reenter_time, instance_type, num_players); + return peerId; + } + } + } + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Error Parsing Zones for %s:%u", __FUNCTION__, peer->webAddr.c_str(), peer->webPort); + } + } + return ""; +} + +void PeerManager::handlePrimaryConflict(const std::string& reconnectingPeerId) { + // Compare IDs or priorities to decide on the primary role + auto currentPrimary = getCurrentPrimary(); + auto reconnectingPeer = getPeerById(reconnectingPeerId); + + if (currentPrimary && (currentPrimary->peerPriority > reconnectingPeer->peerPriority || currentPrimary->healthCheck.status != HealthStatus::OK)) { + // Demote the current primary + if (reconnectingPeer && currentPrimary->healthCheck.status == HealthStatus::OK) { + setPrimary(reconnectingPeerId); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s forced to primary", __FUNCTION__, reconnectingPeer->id); + if (currentPrimary) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: Demoted to secondary", __FUNCTION__); + } + } + } + else { + // Demote the reconnecting peer + if (currentPrimary && currentPrimary->healthCheck.status == HealthStatus::OK) { + setPrimary(currentPrimary->id); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s forced to primary", __FUNCTION__, currentPrimary->id); + } + } +} + +void PeerManager::setPrimary(const std::string& id) { + for (auto& [peerId, peer] : peers) { + peer->peeringStatus = (peerId == id) ? PeeringStatus::PRIMARY : PeeringStatus::SECONDARY; + } +} + +bool PeerManager::hasPrimary() { + for (auto& [peerId, peer] : peers) { + if (peer->peeringStatus == PeeringStatus::PRIMARY) + return true; + } + return false; +} + +bool PeerManager::hasPriorityPeer(int16 priority) { + for (auto& [peerId, peer] : peers) { + if (peer->peerPriority == priority && peer->healthCheck.status == HealthStatus::OK) + return true; + } + return false; +} + +std::string PeerManager::getPriorityPeer() { + int16 peerPriority = 65535; + std::string id = ""; + for (auto& [peerId, peer] : peers) { + if (peer->healthCheck.status > HealthStatus::ERROR && peer->healthCheck.status <= HealthStatus::OK && + peer->peerPriority > 0 && peer->peerPriority < peerPriority) { + peerPriority = peer->peerPriority; + id = peer->id; + } + } + return id; +} + +void PeerManager::updatePeer(const std::string& web_address, int16 web_port, const std::string& client_address, const std::string& client_internal_address, int16 client_port, bool is_primary) { + for (auto& [peerId, peer] : peers) { + if (peer->webAddr == web_address && peer->webPort == web_port) { + peer->worldAddr = client_address; + peer->worldPort = client_port; + peer->internalWorldAddr = client_internal_address; + if (is_primary) { + peer->peeringStatus = PeeringStatus::PRIMARY; + } + else { + peer->peeringStatus = PeeringStatus::SECONDARY; + } + break; + } + } +} + +std::string PeerManager::isPeer(const std::string& web_address, int16 web_port) { + for (auto& [peerId, peer] : peers) { + if (peer->webAddr == web_address && peer->webPort == web_port) { + return peerId; + } + } + return std::string(""); +} + + +HealthStatus PeerManager::getPeerStatus(const std::string& web_address, int16 web_port) { + for (auto& [peerId, peer] : peers) { + if (peer->webAddr == web_address && peer->webPort == web_port) { + return peer->healthCheck.status; + } + } + return HealthStatus::UNKNOWN; +} + +bool PeerManager::hasPeers() { + return (peers.size() > 0); +} + +std::string PeerManager::assignUniqueNameForSecondary(const std::string& baseName, std::string client_address, std::string client_internal_address, int16 client_port, std::string web_address, int16 web_port) { + int suffix = 1; + std::string uniqueName = baseName + std::to_string(suffix); + + while (peers.find(uniqueName) != peers.end()) { + uniqueName = baseName + std::to_string(suffix); + ++suffix; + } + + addPeer(uniqueName, PeeringStatus::SECONDARY, client_address, client_internal_address, client_port, web_address, web_port); + + updateHealth(uniqueName, HealthStatus::STARTUP); + + return uniqueName; +} + +std::optional PeerManager::getHealthyPeer() const { + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status == HealthStatus::OK) { + return id; + } + } + return std::nullopt; +} + +std::shared_ptr PeerManager::getHealthyPeerPtr() const { + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status == HealthStatus::OK) { + return peer; + } + } + return nullptr; +} + +std::shared_ptr PeerManager::getHealthyPrimaryPeerPtr() const { + for (const auto& [id, peer] : peers) { + if (peer->peeringStatus == PeeringStatus::PRIMARY && peer->healthCheck.status == HealthStatus::OK) { + return peer; + } + } + return nullptr; +} + +std::shared_ptr PeerManager::getHealthyPeerWithLeastClients() const { + std::vector> healthyPeers; + + // Seed random generator + std::srand(static_cast(std::time(nullptr))); + + // Step 1: Collect healthy peers + for (auto& [peerId, peer] : peers) { + + // if setup to distribute to peers only, skip primary + if (peer->peerPriority == 0 && peer->peeringStatus == PeeringStatus::PRIMARY) { + continue; + } + + if (peer->healthCheck.status == HealthStatus::OK) { + healthyPeers.push_back(peer); + } + } + + if (healthyPeers.empty()) { + return nullptr; // No healthy peers found + } + + std::string treeList("Zones"); + + // Step 2: Determine minimum number of "Clients" for healthy peers + size_t minClientCount = std::numeric_limits::max(); + for (const auto& peer : healthyPeers) { + std::lock_guard lock(peer->dataMutex); // Lock for thread-safe access + std::shared_ptr tree = peer->zone_tree; + if (auto clientOpt = tree->get_child_optional(treeList.c_str())) { + size_t clientCount = clientOpt.get().size(); + if (clientCount < minClientCount) { + minClientCount = clientCount; + } + } + else { + // Consider peers without "Clients" node as having 0 clients + minClientCount = 0; + } + } + + // Step 3: Collect all healthy peers with minClientCount + std::vector> minClientPeers; + for (const auto& peer : healthyPeers) { + std::lock_guard lock(peer->dataMutex); + std::shared_ptr tree = peer->zone_tree; + size_t clientCount = 0; + if (auto clientOpt = tree->get_child_optional(treeList.c_str())) { + clientCount = clientOpt.get().size(); + } + if (clientCount == minClientCount) { + minClientPeers.push_back(peer); + } + } + + // Step 4: Select a random peer from the minClientPeers + if (!minClientPeers.empty()) { + size_t randomIndex = std::rand() % minClientPeers.size(); + return minClientPeers[randomIndex]; + } + + return nullptr; // Fallback if no peers match the criteria +} + +std::shared_ptr PeerManager::getPeerById(const std::string& id) const { + auto it = peers.find(id); + if (it != peers.end()) { + return it->second; // Return the shared_ptr if found + } + return nullptr; // Return nullptr if the peerId doesn't exist +} +// Function to get a unique integer for a peer +int32 PeerManager::getUniqueGroupId() { + std::lock_guard lock(idMutex); + uniqueGroupID++; + if (uniqueGroupID == 0) + uniqueGroupID++; + + return uniqueGroupID; +} + +bool PeerManager::sendPrimaryNewGroupRequest(std::string leader, std::string member, int32 entity_id, GroupOptions* options) { + std::shared_ptr primary = getHealthyPrimaryPeerPtr(); + if (primary) { + boost::property_tree::ptree root; + + root.put("peer_web_address", std::string(net.GetWebWorldAddress())); + root.put("peer_web_port", std::to_string(net.GetWebWorldPort())); + root.put("group_id", 0); + root.put("leader_name", leader); + root.put("member_name", member); + root.put("member_entity_id", entity_id); + populateGroupOptions(root, options); + root.put("is_update", false); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Leader %s, Member %s(%u)", __FUNCTION__, leader.c_str(), member.c_str(), entity_id); + peer_https_pool.sendPostRequestToPeerAsync(primary->id, primary->webAddr, std::to_string(primary->webPort), "/newgroup", jsonPayload); + return true; + } + + return false; +} + +// Helper function to populate the ptree with GroupOptions +void PeerManager::populateGroupOptions(boost::property_tree::ptree& root, GroupOptions* options) { + if (!options) return; // Handle null pointer gracefully + root.put("loot_method", options->loot_method); + root.put("loot_items_rarity", options->loot_items_rarity); + root.put("auto_split", options->auto_split); + root.put("default_yell", options->default_yell); + root.put("group_lock_method", options->group_lock_method); + root.put("group_autolock", options->group_autolock); + root.put("solo_autolock", options->solo_autolock); + root.put("auto_loot_method", options->auto_loot_method); + root.put("last_looted_index", options->last_looted_index); +} + +void PeerManager::sendPeersNewGroupRequest(std::string peer_creation_address, int16 peer_creation_port, + int32 group_id, std::string leader, std::string member, GroupOptions* options, + std::string peerId, std::vector* raidGroups, bool is_update) { + boost::property_tree::ptree root; + root.put("group_id", group_id); + root.put("leader_name", leader); + root.put("member_name", member); + populateGroupOptions(root, options); + root.put("peer_web_address", peer_creation_address); + root.put("peer_web_port", peer_creation_port); + if (raidGroups) { + std::vector::iterator group_itr; + int8 group_count = 0; + for (group_itr = raidGroups->begin(); group_itr != raidGroups->end(); group_itr++) { + std::string fieldName("group_id_"); + fieldName.append(std::to_string(group_count)); + root.put(fieldName, (*group_itr)); + group_count++; + } + } + root.put("is_update", is_update); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: ToPeer: %s (Optional), NewGroup %u, IsUpdate: %u", __FUNCTION__, peerId.c_str(), group_id, is_update); + + if (peerId.size() > 0) { + std::shared_ptr peer = getPeerById(peerId); + + if (peer->healthCheck.status == HealthStatus::OK) { + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/newgroup", jsonPayload); + } + } + else { + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + if (peer->webAddr == peer_creation_address && peer->webPort == peer_creation_port) // skip peer it was created on + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/newgroup", jsonPayload); + } + } +} + +void PeerManager::sendPeersGroupMember(int32 group_id, GroupMemberInfo* info, bool is_update, std::string peerId) { + if (!info) { + return; + } + boost::property_tree::ptree root; + root.put("group_id", group_id); + root.put("member_name", info->name); + root.put("is_client", info->is_client); + root.put("zone", info->zone); + root.put("current_hp", info->hp_current); + root.put("max_hp", info->hp_max); + root.put("current_power", info->power_current); + root.put("max_power", info->power_max); + root.put("level_current", info->level_current); + root.put("level_max", info->level_max); + root.put("race_id", info->race_id); + root.put("class_id", info->class_id); + root.put("is_leader", info->leader); + root.put("mentor_target_char_id", info->mentor_target_char_id); + root.put("client_peer_address", info->client_peer_address); + root.put("client_peer_port", info->client_peer_port); + root.put("is_raid_looter", info->is_raid_looter); + root.put("is_update", is_update); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Send Member %s ToPeer: %s (Optional), NewGroup %u, IsUpdate: %u", __FUNCTION__, info->name.c_str(), peerId.c_str(), group_id, is_update); + + + if (peerId.size() > 0) { + std::shared_ptr peer = getPeerById(peerId); + + if (peer->healthCheck.status == HealthStatus::OK) { + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addgroupmember", jsonPayload); + } + } + else { + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addgroupmember", jsonPayload); + } + } +} + +void PeerManager::sendPeersRemoveGroupMember(int32 group_id, std::string name, int32 char_id, bool is_client) { + boost::property_tree::ptree root; + root.put("group_id", group_id); + root.put("member_name", name); + root.put("is_client", is_client); + root.put("character_id", char_id); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Remove Member %s ToPeer: %s (Optional), Group %u", __FUNCTION__, name.c_str(), group_id); + + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/removegroupmember", jsonPayload); + } +} + +void PeerManager::sendPeersDisbandGroup(int32 group_id) { + boost::property_tree::ptree root; + root.put("group_id", group_id); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Disband Group %u", __FUNCTION__, group_id); + + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/disbandgroup", jsonPayload); + } +} + +bool PeerManager::sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name) { + std::shared_ptr primary = getHealthyPrimaryPeerPtr(); + if (primary) { + boost::property_tree::ptree root; + + root.put("peer_web_address", std::string(net.GetWebWorldAddress())); + root.put("peer_web_port", std::to_string(net.GetWebWorldPort())); + root.put("guild_name", guild_name); + root.put("leader_name", leader_name); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Create Guild Request %s, Leader %s", __FUNCTION__, guild_name.c_str(), leader_name.c_str()); + peer_https_pool.sendPostRequestToPeerAsync(primary->id, primary->webAddr, std::to_string(primary->webPort), "/createguild", jsonPayload); + return true; + } + + return false; +} + + +void PeerManager::sendPeersAddGuildMember(int32 character_id, int32 guild_id, std::string invited_by, int32 join_timestamp, int8 rank) { + boost::property_tree::ptree root; + root.put("guild_id", guild_id); + root.put("character_id", character_id); + root.put("invited_by", invited_by); + root.put("join_timestamp", join_timestamp); + root.put("rank", rank); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Add Guild Member, Guild: %u, CharID: %u, InvitedBy: %s", __FUNCTION__, guild_id, character_id, invited_by.c_str()); + + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addguildmember", jsonPayload); + } +} + +void PeerManager::sendPeersRemoveGuildMember(int32 character_id, int32 guild_id, std::string removed_by) { + boost::property_tree::ptree root; + root.put("guild_id", guild_id); + root.put("character_id", character_id); + root.put("removed_by", removed_by); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Remove Guild Member, Guild: %u, CharID: %u, RemovedBy: %s", __FUNCTION__, guild_id, character_id, removed_by.c_str()); + + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/removeguildmember", jsonPayload); + } +} + +void PeerManager::sendPeersCreateGuild(int32 guild_id) { + boost::property_tree::ptree root; + root.put("guild_id", guild_id); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peers Guild Create, Guild: %u", __FUNCTION__, guild_id); + + for (const auto& [id, peer] : peers) { + // primary creates the guild, skip it + if (peer->healthCheck.status != HealthStatus::OK || peer->peeringStatus == PeeringStatus::PRIMARY) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/createguild", jsonPayload); + } +} + +void PeerManager::sendPeersGuildPermission(int32 guild_id, int8 rank, int8 permission, int8 value_) { + boost::property_tree::ptree root; + root.put("guild_id", guild_id); + root.put("rank", rank); + root.put("permission", permission); + root.put("value", value_); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peers Guild Permission, Guild: %u, Rank: %u, Permission: %u, Value: %u", __FUNCTION__, guild_id, rank, permission, value_); + for (const auto& [id, peer] : peers) { + // primary creates the guild, skip it + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/setguildpermission", jsonPayload); + } +} + +void PeerManager::sendPeersGuildEventFilter(int32 guild_id, int8 event_id, int8 category, int8 value_) { + boost::property_tree::ptree root; + root.put("guild_id", guild_id); + root.put("event_id", event_id); + root.put("category", category); + root.put("value", value_); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peers Guild Event Filter, Guild: %u, EventID: %u, Category: %u, Value: %u", __FUNCTION__, guild_id, event_id, category, value_); + + for (const auto& [id, peer] : peers) { + // primary creates the guild, skip it + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/setguildeventfilter", jsonPayload); + } +} + +void PeerManager::SetPeerErrorState(std::string address, std::string port) { + std::string id = isPeer(address, (int16)std::atol(port.c_str())); + if (id.size() > 0) + updateHealth(id, HealthStatus::ERROR); +} + +std::shared_ptr PeerManager::getCurrentPrimary() { + for (const auto& [id, peer] : peers) { + if (peer->peeringStatus == PeeringStatus::PRIMARY) { + return peer; + } + } + return nullptr; // Or throw an error if no primary found +} + +void PeerManager::sendPeersMessage(const std::string& endpoint, int32 command, int32 sub_command) { + + boost::property_tree::ptree root; + + root.put("reload_command", command); + root.put("sub_command", sub_command); + + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + for (const auto& [id, peer] : peers) { + if (peer->healthCheck.status != HealthStatus::OK) + continue; + + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), endpoint.c_str(), jsonPayload); + } +} \ No newline at end of file diff --git a/source/WorldServer/Web/PeerManager.h b/source/WorldServer/Web/PeerManager.h new file mode 100644 index 0000000..d154fd2 --- /dev/null +++ b/source/WorldServer/Web/PeerManager.h @@ -0,0 +1,227 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#ifndef PEERMANAGER_H +#define PEERMANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../common/types.h" + +class Client; + +enum HealthStatus { + UNKNOWN = 0, + WARN = 1, + ERROR = 2, + STARTUP = 3, + OK = 4, + SHUTDOWN = 5 +}; + +enum class PeeringStatus { + PRIMARY, + SECONDARY +}; + +struct HealthCheck { + HealthStatus status; + std::chrono::system_clock::time_point lastReceived; + + void updateStatus(HealthStatus newStatus); + std::chrono::duration timeSinceLastCheck() const; +}; + +struct GroupOptions; +struct GroupMemberInfo; +struct WhoAllPeerPlayer; +struct GuildMember; + +struct ZoneChangeDetails { + std::string peerId; + std::string peerWorldAddress; + std::string peerInternalWorldAddress; + int16 peerWorldPort; + std::string peerWebAddress; + int16 peerWebPort; + std::string zoneFileName; + std::string zoneName; + int32 zoneId; + int32 instanceId; + float safeX; + float safeY; + float safeZ; + float safeHeading; + bool lockState; + sint16 minStatus; + int16 minLevel; + int16 maxLevel; + int16 minVersion; + + int32 defaultLockoutTime; + int32 defaultReenterTime; + int8 instanceType; + int32 numPlayers; + void* zonePtr; + bool peerAuthorized; + int32 zoneKey; + int32 authDispatchedTime; + bool zoningPastAuth; + ZoneChangeDetails() = default; + ZoneChangeDetails(ZoneChangeDetails* copy_details); + ZoneChangeDetails(std::string peer_id, std::string peer_world_address, std::string peer_internal_world_address, int16 peer_world_port, + std::string peer_web_address, int16 peer_web_port, std::string zone_file_name, std::string zone_name, int32 zone_id, + int32 instance_id, float safe_x, float safe_y, float safe_z, float safe_heading, bool lock_state, sint16 min_status, + int16 min_level, int16 max_level, int16 min_version, int32 default_lockout_time, int32 default_reenter_time, int8 instance_type, int32 num_players); +}; + +struct Peer { + std::string id; + PeeringStatus peeringStatus; + HealthCheck healthCheck; + std::string worldAddr; + std::string internalWorldAddr; + int16 worldPort; + int16 peerPriority; + std::string webAddr; + int16 webPort; + std::shared_ptr zone_tree; + std::shared_ptr client_tree; + std::atomic sentInitialPeerData; + std::atomic wasOffline; + mutable std::mutex dataMutex; // Mutex to protect access to ptree + // Default constructor + Peer() + : zone_tree(std::make_shared()), + client_tree(std::make_shared()) { + healthCheck.status = HealthStatus::UNKNOWN; + peerPriority = 65535; + sentInitialPeerData = false; + wasOffline = false; + } + + Peer(std::string peerId, PeeringStatus status, std::string client_address, + std::string client_internal_address, int16 client_port, + std::string web_address, int16 web_port) + : id(std::move(peerId)), peeringStatus(status), worldAddr(std::move(client_address)), + internalWorldAddr(std::move(client_internal_address)), worldPort(client_port), + webAddr(std::move(web_address)), webPort(web_port), + zone_tree(std::make_shared()), + client_tree(std::make_shared()) { + healthCheck.status = HealthStatus::STARTUP; + peerPriority = 65535; + sentInitialPeerData = false; + wasOffline = false; + } + + // Example function to output data as JSON string (for debug or logging) + std::string getZoneDataAsJson() const { + std::lock_guard lock(dataMutex); + std::ostringstream oss; + boost::property_tree::write_json(oss, *zone_tree); // Dereference data + return oss.str(); + } + + // Example function to output data as JSON string (for debug or logging) + std::string getClientDataAsJson() const { + std::lock_guard lock(dataMutex); + std::ostringstream oss; + boost::property_tree::write_json(oss, *client_tree); // Dereference data + return oss.str(); + } +}; + +class PeerManager { +private: + std::map> peers; + std::atomic uniqueGroupID{ 1 }; // Shared counter for unique IDs + std::mutex idMutex; + +public: + void addPeer(std::string id, PeeringStatus status, std::string client_address, std::string client_internal_address, int16 client_port, std::string web_address, int16 web_port); + void updateHealth(const std::string& id, HealthStatus newStatus); + void updatePriority(const std::string& id, int16 priority); + void updateZoneTree(const std::string& id, const boost::property_tree::ptree& newTree); + void updateClientTree(const std::string& id, const boost::property_tree::ptree& newTree); + void setZonePeerData(ZoneChangeDetails* opt_details, std::string peerId, std::string peerWorldAddress, std::string peerInternalWorldAddress, int16 peerWorldPort, + std::string peerWebAddress, int16 peerWebPort, std::string zoneFileName, std::string zoneName, int32 zoneId, + int32 instanceId, float safeX, float safeY, float safeZ, float safeHeading, bool lockState, sint16 minStatus, + int16 minLevel, int16 maxLevel, int16 minVersion, int32 defaultLockoutTime, int32 defaultReenterTime, int8 instanceType, int32 numPlayers); + void setZonePeerDataSelf(ZoneChangeDetails* opt_details, std::string zoneFileName, std::string zoneName, int32 zoneId, + int32 instanceId, float safeX, float safeY, float safeZ, float safeHeading, bool lockState, sint16 minStatus, + int16 minLevel, int16 maxLevel, int16 minVersion, int32 defaultLockoutTime, int32 defaultReenterTime, int8 instanceType, int32 numPlayers, void* zonePtr = nullptr); + bool IsClientConnectedPeer(int32 account_id); + std::string GetCharacterPeerId(std::string charName); + void SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0); + void SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id = 0); + void sendZonePeerList(Client* client); + std::string getZonePeerId(const std::string& inc_zone_name, int32 inc_zone_id, int32 inc_instance_id, ZoneChangeDetails* opt_details = nullptr, bool only_always_loaded = false); + void setZonePeerData(ZoneChangeDetails* opt_details); + void setPrimary(const std::string& id); + bool hasPrimary(); + bool hasPriorityPeer(int16 priority); + std::string getPriorityPeer(); + void updatePeer(const std::string& web_address, int16 web_port, const std::string& client_address, const std::string& client_internal_address, int16 client_port, bool is_primary = false); + std::string isPeer(const std::string& web_address, int16 web_port); + HealthStatus getPeerStatus(const std::string& web_address, int16 web_port); + bool hasPeers(); + std::string assignUniqueNameForSecondary(const std::string& baseName, std::string client_address, std::string client_internal_address, int16 client_port, std::string web_address, int16 web_port); + std::optional getHealthyPeer() const; + std::shared_ptr getHealthyPeerPtr() const; + std::shared_ptr getHealthyPrimaryPeerPtr() const; + std::shared_ptr getHealthyPeerWithLeastClients() const; + std::shared_ptr getPeerById(const std::string& id) const; + int32 getUniqueGroupId(); + bool sendPrimaryNewGroupRequest(std::string leader, std::string member, int32 entity_id, GroupOptions* options); + void sendPeersGroupMember(int32 group_id, GroupMemberInfo* info, bool is_update = false, std::string peerId = ""); + void sendPeersRemoveGroupMember(int32 group_id, std::string name, int32 char_id, bool is_client); + void populateGroupOptions(boost::property_tree::ptree& root, GroupOptions* options); + void sendPeersNewGroupRequest(std::string peer_creation_address, int16 peer_creation_port, + int32 group_id, std::string leader, std::string member, GroupOptions* options, + std::string peerId = "", std::vector* raidGroups = nullptr, bool is_update = false); + + void sendPeersDisbandGroup(int32 group_id); + bool sendPrimaryCreateGuildRequest(std::string guild_name, std::string leader_name); + 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); + void sendPeersGuildPermission(int32 guild_id, int8 rank, int8 permission, int8 value_); + void sendPeersGuildEventFilter(int32 guild_id, int8 event_id, int8 category, int8 value_); + + void SetPeerErrorState(std::string address, std::string port); + void handlePrimaryConflict(const std::string& reconnectingPeerId); + std::shared_ptr getCurrentPrimary(); + + void sendPeersMessage(const std::string& endpoint, int32 command, int32 sub_command = 0); + + void sendZonePlayerList(std::vector* queries, std::vector* peer_list, bool isGM); + + bool GetClientGuildDetails(int32 matchCharID, GuildMember* member_details); + +}; + +#endif // PEERMANAGER_H diff --git a/source/WorldServer/Web/WorldWeb.cpp b/source/WorldServer/Web/WorldWeb.cpp index 7823f7a..f9d029e 100644 --- a/source/WorldServer/Web/WorldWeb.cpp +++ b/source/WorldServer/Web/WorldWeb.cpp @@ -1,6 +1,29 @@ +/* +EQ2Emu: Everquest II Server Emulator +Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) + +This file is part of EQ2Emu. + +EQ2Emu is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emu is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emu. If not, see . +*/ + +#include "HTTPSClientPool.h" #include "../World.h" #include "../WorldDatabase.h" #include "../LoginServer.h" +#include "../LuaInterface.h" +#include "../Guilds/Guild.h" #include #include @@ -11,29 +34,44 @@ extern World world; extern LoginServer loginserver; extern sint32 numclients; extern WorldDatabase database; +extern ZoneList zone_list; +extern ZoneAuth zone_auth; +extern LuaInterface* lua_interface; +extern ConfigReader configReader; + +extern MasterQuestList master_quest_list; +extern MasterSpellList master_spell_list; +extern MasterFactionList master_faction_list; +extern ClientList client_list; +extern GuildList guild_list; + +PeerManager peer_manager; +HTTPSClientPool peer_https_pool; void World::Web_worldhandle_status(const http::request& req, http::response& res) { - res.set(http::field::content_type, "application/json"); + res.set(http::field::content_type, "application/json; charset=utf-8"); boost::property_tree::ptree pt; - pt.put("web_status", "online"); + pt.put("web_status", "online"); bool world_online = world.world_loaded; - pt.put("world_status", world.world_loaded ? "online" : "offline"); - pt.put("world_uptime", (getCurrentTimestamp() - world.world_uptime)); + pt.put("world_status", world.world_loaded ? "online" : "offline"); + pt.put("world_uptime", (getCurrentTimestamp() - world.world_uptime)); auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - world.world_uptime)); std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); - pt.put("world_uptime_string", uptime_str); - pt.put("login_connected", loginserver.Connected() ? "connected" : "disconnected"); - pt.put("player_count", zone_list.GetZonesPlayersCount()); - pt.put("client_count", numclients); - pt.put("zones_connected", zone_list.Count()); - pt.put("world_reloading", world.IsReloadingSubsystems() ? "yes" : "no"); + pt.put("world_uptime_string", uptime_str); + pt.put("login_connected", loginserver.Connected() ? "connected" : "disconnected"); + pt.put("player_count", zone_list.GetZonesPlayersCount()); + pt.put("client_count", numclients); + pt.put("zones_connected", zone_list.Count()); + pt.put("world_reloading", world.IsReloadingSubsystems() ? "yes" : "no"); + pt.put("peer_primary", net.is_primary); + pt.put("peer_priority", net.GetPeerPriority()); - std::ostringstream oss; - boost::property_tree::write_json(oss, pt); - std::string json = oss.str(); - res.body() = json; - res.prepare_payload(); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); } void World::Web_worldhandle_clients(const http::request& req, http::response& res) { @@ -41,22 +79,24 @@ void World::Web_worldhandle_clients(const http::request& req, } void ZoneList::PopulateClientList(http::response& res) { - res.set(http::field::content_type, "application/json"); + res.set(http::field::content_type, "application/json; charset=utf-8"); boost::property_tree::ptree maintree; - std::ostringstream oss; + std::ostringstream oss; MClientList.lock(); - map::iterator itr; - for(itr = client_map.begin(); itr != client_map.end(); itr++){ - if(itr->second){ + map::iterator itr; + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + if (itr->second) { Client* cur = (Client*)itr->second; boost::property_tree::ptree pt; pt.put("character_id", cur->GetCharacterID()); - pt.put("character_name", cur->GetPlayer() ? cur->GetPlayer()->GetName() : "N/A"); + pt.put("character_name", cur->GetPlayer() ? cur->GetPlayer()->GetName() : ""); + pt.put("subtitle", cur->GetPlayer() ? cur->GetPlayer()->appearance.sub_title : ""); pt.put("class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class1() : 0); pt.put("class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class2() : 0); pt.put("class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class3() : 0); + pt.put("deity", cur->GetPlayer() ? cur->GetPlayer()->GetDeity() : 0); pt.put("tradeskill_class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class1() : 0); pt.put("tradeskill_class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class2() : 0); pt.put("tradeskill_class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class3() : 0); @@ -68,32 +108,50 @@ void ZoneList::PopulateClientList(http::response& res) { pt.put("account_id", cur->GetAccountID()); pt.put("version", cur->GetVersion()); pt.put("status", cur->GetAdminStatus()); + pt.put("guild_id", cur->GetPlayer()->GetGuild() != nullptr ? cur->GetPlayer()->GetGuild()->GetID() : 0); + pt.put("flags", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_flags() : 0); + pt.put("flags2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_flags2() : 0); + pt.put("adventure_class", cur->GetPlayer() ? cur->GetPlayer()->GetAdventureClass() : 0); + pt.put("tradeskill_class", cur->GetPlayer() ? cur->GetPlayer()->GetTradeskillClass() : 0); pt.put("is_zoning", (cur->IsZoning() || !cur->IsReadyForUpdates())); - + bool linkdead = cur->GetPlayer() ? (((cur->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)) : false; pt.put("is_linkdead", linkdead); pt.put("in_zone", cur->IsReadyForUpdates()); + pt.put("zone_id", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetZoneID() : 0); + pt.put("instance_id", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetInstanceID() : 0); pt.put("zonename", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetZoneName() : "N/A"); + pt.put("zonedescription", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetZoneDescription() : ""); + + GroupMemberInfo* gmi = cur->GetPlayer()->GetGroupMemberInfo(); + int32 group_id = 0; + bool group_leader = false; + if (gmi && gmi->group_id) { + group_id = gmi->group_id; + group_leader = gmi->leader; + } + pt.put("group_id", group_id); + pt.put("group_leader", group_leader); maintree.push_back(std::make_pair("", pt)); } } MClientList.unlock(); - + boost::property_tree::ptree result; result.add_child("Clients", maintree); - boost::property_tree::write_json(oss, result); - std::string json = oss.str(); - res.body() = json; - res.prepare_payload(); + boost::property_tree::write_json(oss, result); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); } void World::Web_worldhandle_setadminstatus(const http::request& req, http::response& res) { - res.set(http::field::content_type, "application/json"); + res.set(http::field::content_type, "application/json; charset=utf-8"); boost::property_tree::ptree pt, json_tree; - + std::istringstream json_stream(req.body()); - boost::property_tree::read_json(json_stream, json_tree); - + boost::property_tree::read_json(json_stream, json_tree); + sint16 status = 0; std::string charname(""); bool got_status_field = false; @@ -101,14 +159,14 @@ void World::Web_worldhandle_setadminstatus(const http::request("new_status")) { - status = new_status.get(); - got_status_field = true; - } - + status = new_status.get(); + got_status_field = true; + } + sint32 success = 0; - - if(got_status_field && charname.size() > 0 && database.UpdateAdminStatus((char*)charname.c_str(),status)) { - + + if (got_status_field && charname.size() > 0 && database.UpdateAdminStatus((char*)charname.c_str(), status)) { + Client* target = zone_list.GetClientByCharName(charname.c_str()); if (target) { target->SetAdminStatus(status); @@ -116,33 +174,1105 @@ void World::Web_worldhandle_setadminstatus(const http::request& req, http::response& res) { - res.set(http::field::content_type, "application/json"); + res.set(http::field::content_type, "application/json; charset=utf-8"); boost::property_tree::ptree pt, json_tree; - + std::istringstream json_stream(req.body()); - boost::property_tree::read_json(json_stream, json_tree); - + boost::property_tree::read_json(json_stream, json_tree); + database.LoadRuleSets(true); - pt.put("success", 1); + pt.put("success", 1); - std::ostringstream oss; - boost::property_tree::write_json(oss, pt); - std::string json = oss.str(); - res.body() = json; - res.prepare_payload(); -} \ No newline at end of file + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_reloadcommand(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + + int32 reload_command = 0, sub_command = 0; + if (auto command = json_tree.get_optional("reload_command")) { + reload_command = command.get(); + } + if (auto subcommand = json_tree.get_optional("sub_command")) { + sub_command = subcommand.get(); + } + int32 success = 1; + switch (reload_command) { + case COMMAND_RELOADSTRUCTS: { + world.SetReloadingSubsystem("Structs"); + configReader.ReloadStructs(); + world.RemoveReloadingSubSystem("Structs"); + break; + } + case COMMAND_RELOAD_QUESTS: { + world.SetReloadingSubsystem("Quests"); + master_quest_list.Reload(); + client_list.ReloadQuests(); + zone_list.ReloadClientQuests(); + world.RemoveReloadingSubSystem("Quests"); + break; + } + case COMMAND_RELOAD_SPELLS: { + if (sub_command == 1) { // npc only + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + } + else { + world.SetReloadingSubsystem("Spells"); + + zone_list.DeleteSpellProcess(); + if (lua_interface) + lua_interface->DestroySpells(); + master_spell_list.Reload(); + zone_list.LoadSpellProcess(); + world.RemoveReloadingSubSystem("Spells"); + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + } + break; + } + case COMMAND_RELOAD_ZONESCRIPTS: { + world.SetReloadingSubsystem("ZoneScripts"); + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + if (lua_interface) + lua_interface->DestroyZoneScripts(); + world.RemoveReloadingSubSystem("ZoneScripts"); + break; + } + case COMMAND_RELOAD_FACTIONS: { + world.SetReloadingSubsystem("Factions"); + master_faction_list.Clear(); + database.LoadFactionList(); + world.RemoveReloadingSubSystem("Factions"); + break; + } + case COMMAND_RELOAD_MAIL: { + zone_list.ReloadMail(); + break; + } + case COMMAND_RELOAD_GUILDS: { + world.ReloadGuilds(); + break; + } + case COMMAND_RELOAD_RULES: { + database.LoadRuleSets(true); + break; + } + case COMMAND_RELOAD_STARTABILITIES: { + world.PurgeStartingLists(); + world.LoadStartingLists(); + break; + } + case COMMAND_RELOAD_VOICEOVERS: { + world.PurgeVoiceOvers(); + world.LoadVoiceOvers(); + break; + } + case COMMAND_RELOADSPAWNSCRIPTS: { + if (lua_interface) + lua_interface->SetLuaSystemReloading(true); + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + if (lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->SetLuaSystemReloading(false); + } + break; + } + case COMMAND_RELOADREGIONSCRIPTS: { + if (lua_interface) { + lua_interface->DestroyRegionScripts(); + } + break; + } + case COMMAND_RELOADLUASYSTEM: { + + world.SetReloadingSubsystem("LuaSystem"); + + if (lua_interface) { + lua_interface->SetLuaSystemReloading(true); + } + + zone_list.DeleteSpellProcess(); + if (lua_interface) + lua_interface->DestroySpells(); + master_spell_list.Reload(); + zone_list.LoadSpellProcess(); + if (lua_interface) { + map debug_clients = lua_interface->GetDebugClients(); + map::iterator itr; + for (itr = debug_clients.begin(); itr != debug_clients.end(); itr++) { + if (lua_interface) + lua_interface->UpdateDebugClients(itr->first); + } + } + + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + + if (lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->DestroyRegionScripts(); + lua_interface->DestroyQuests(); + lua_interface->DestroyItemScripts(); + lua_interface->DestroyZoneScripts(); + } + + int32 quest_count = database.LoadQuests(); + + int32 spell_count = 0; + + if (lua_interface) { + spell_count = database.LoadSpellScriptData(); + lua_interface->SetLuaSystemReloading(false); + } + + world.RemoveReloadingSubSystem("LuaSystem"); + + break; + } + default: { + success = 0; + break; + } + } + + pt.put("success", success); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_addpeer(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + + int16 client_port = 0; + std::string client_addr(""); + std::string client_internal_addr(""); + + int16 web_port = 0; + std::string web_addr(""); + bool got_port = false; + bool got_addr = false; + if (auto client_address = json_tree.get_optional("client_address")) { + client_addr = client_address.get(); + got_addr = true; + } + + if (auto client_internal_address = json_tree.get_optional("client_internal_address")) { + client_internal_addr = client_internal_address.get(); + } + + if (auto json_port = json_tree.get_optional("client_port")) { + client_port = json_port.get(); + got_port = true; + } + + if (auto world_address = json_tree.get_optional("web_address")) { + web_addr = world_address.get(); + } + else if (got_addr) + got_addr = false; + + if (auto port = json_tree.get_optional("web_port")) { + web_port = port.get(); + } + else if (got_port) + got_port = false; + + sint32 success = 0; + std::string peerName = peer_manager.isPeer(web_addr, web_port); + if (got_port && got_addr && peerName.size() < 1) { + std::string name = peer_manager.assignUniqueNameForSecondary("eq2emu_", client_addr, client_internal_addr, client_port, web_addr, web_port); + peer_https_pool.addPeerClient(name, web_addr, std::to_string(web_port), "/addpeer"); + pt.put("assigned_peer_name", name.c_str()); + pt.put("peer_client_address", std::string(net.GetWorldAddress())); + pt.put("peer_client_internal_address", std::string(net.GetInternalWorldAddress())); + pt.put("peer_client_port", std::to_string(net.GetWorldPort())); + pt.put("peer_web_address", std::string(net.GetWebWorldAddress())); + pt.put("peer_web_port", std::to_string(net.GetWebWorldPort())); + if (!peer_manager.hasPeers() && !net.is_primary) { + net.SetPrimary(); + } + pt.put("peer_primary", net.is_primary); + } + else { + peer_manager.updatePeer(web_addr, web_port, client_addr, client_internal_addr, client_port, false); + pt.put("assigned_peer_name", peerName); + pt.put("peer_client_address", std::string(net.GetWorldAddress())); + pt.put("peer_client_internal_address", std::string(net.GetInternalWorldAddress())); + pt.put("peer_client_port", std::to_string(net.GetWorldPort())); + pt.put("peer_web_address", std::string(net.GetWebWorldAddress())); + pt.put("peer_web_port", std::to_string(net.GetWebWorldPort())); + pt.put("peer_primary", net.is_primary); + } + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_zones(const http::request& req, http::response& res) { + zone_list.PopulateZoneList(res); +} + +void ZoneList::PopulateZoneList(http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + int zonesListed = 0; + for (zone_iter = zlist.begin(); zone_iter != zlist.end(); zone_iter++) { + tmp = *zone_iter; + boost::property_tree::ptree pt; + pt.put("zone_name", tmp->GetZoneName()); + pt.put("zone_file_name", tmp->GetZoneFile()); + pt.put("zone_id", tmp->GetZoneID()); + pt.put("instance_id", tmp->GetInstanceID()); + pt.put("shutting_down", tmp->isZoneShuttingDown()); + pt.put("instance_zone", tmp->IsInstanceZone()); + pt.put("num_players", tmp->NumPlayers()); + pt.put("city_zone", tmp->IsCityZone()); + pt.put("safe_x", tmp->GetSafeX()); + pt.put("safe_y", tmp->GetSafeY()); + pt.put("safe_z", tmp->GetSafeZ()); + pt.put("safe_heading", tmp->GetSafeHeading()); + pt.put("lock_state", tmp->GetZoneLockState()); + pt.put("min_status", tmp->GetMinimumStatus()); + pt.put("min_level", tmp->GetMinimumLevel()); + pt.put("max_level", tmp->GetMaximumLevel()); + pt.put("min_version", tmp->GetMinimumVersion()); + pt.put("default_lockout_time", tmp->GetDefaultLockoutTime()); + pt.put("default_reenter_time", tmp->GetDefaultReenterTime()); + pt.put("instance_type", (int8)tmp->GetInstanceType()); + pt.put("always_loaded", tmp->AlwaysLoaded()); + zonesListed++; + maintree.push_back(std::make_pair("", pt)); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + boost::property_tree::ptree result; + result.add_child("Zones", maintree); + boost::property_tree::write_json(oss, result); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_addcharauth(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + + int32 account_id = 0; + int32 key = 0; + int32 zoneId, instanceId; + bool firstLogin = false; + + std::string clientIP(""); + std::string charName(""), zoneName(""); + int32 charID = 0; + int32 worldID = 0, fromID = 0; + if (auto acct_id = json_tree.get_optional("account_id")) { + account_id = acct_id.get(); + } + + if (auto char_name = json_tree.get_optional("character_name")) { + charName = char_name.get(); + } + + if (auto zone_name = json_tree.get_optional("zone_name")) { + zoneName = zone_name.get(); + } + + if (auto login_key = json_tree.get_optional("login_key")) { + key = login_key.get(); + } + + if (auto zone_id = json_tree.get_optional("zone_id")) { + zoneId = zone_id.get(); + } + + if (auto instance_id = json_tree.get_optional("instance_id")) { + instanceId = instance_id.get(); + } + + if (auto first_login = json_tree.get_optional("first_login")) { + firstLogin = first_login.get(); + } + + if (auto clientip = json_tree.get_optional("client_ip")) { + clientIP = clientip.get(); + } + if (auto character_id = json_tree.get_optional("character_id")) { + charID = character_id.get(); + } + if (auto world_id = json_tree.get_optional("world_id")) { + worldID = world_id.get(); + } + if (auto from_id = json_tree.get_optional("from_id")) { + fromID = from_id.get(); + } + + sint32 success = 0; + + ZoneChangeDetails details; + if (instanceId || zoneId || zoneName.length() > 0) { + if (!instanceId) { + if ((zone_list.GetZone(&details, zoneId, zoneName, firstLogin, false, true, false))) + success = 1; + } + else { + if ((zone_list.GetZoneByInstance(&details, instanceId, zoneId, firstLogin, false, true, false))) + success = 1; + } + } + if (!success) { + // failed to find zone requested by peer + } + + if (success && account_id && key && charName.length() > 0) { + ZoneAuthRequest* zar = new ZoneAuthRequest(account_id, (char*)charName.c_str(), key); + zar->setFirstLogin(firstLogin); + zone_auth.AddAuth(zar); + success = 1; + } + else { + success = 0; + } + + pt.put("character_name", charName); + pt.put("account_id", account_id); + pt.put("zone_name", zoneName); + pt.put("zone_id", zoneId); + pt.put("instance_id", instanceId); + pt.put("first_login", firstLogin); + pt.put("peer_client_address", std::string(net.GetWorldAddress())); + pt.put("peer_client_internal_address", std::string(net.GetInternalWorldAddress())); + pt.put("peer_client_port", std::to_string(net.GetWorldPort())); + pt.put("client_ip", clientIP); + pt.put("character_id", std::to_string(charID)); + pt.put("login_key", std::to_string(key)); + pt.put("world_id", std::to_string(worldID)); + pt.put("from_id", std::to_string(fromID)); + pt.put("success", success); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_startzone(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + + int32 instanceId = 0; + int32 zoneId = 0; + std::string zoneName(""); + bool alwaysLoaded = false; + if (auto inst_id = json_tree.get_optional("instance_id")) { + instanceId = inst_id.get(); + } + + if (auto zone = json_tree.get_optional("zone_name")) { + zoneName = zone.get(); + } + + if (auto zone_id = json_tree.get_optional("zone_id")) { + zoneId = zone_id.get(); + } + + if (auto always_loaded = json_tree.get_optional("always_loaded")) { + alwaysLoaded = always_loaded.get(); + } + + sint32 success = 0; + ZoneChangeDetails details; + if (instanceId || zoneId || zoneName.length() > 0) { + if (!instanceId) { + if ((zone_list.GetZone(&details, zoneId, zoneName, true, false, false, false, false, alwaysLoaded))) + success = 1; + } + else { + if ((zone_list.GetZoneByInstance(&details, instanceId, zoneId, true, false, false, false))) + success = 1; + } + } + + pt.put("success", success); + pt.put("peer_web_address", net.GetWebWorldAddress()); + pt.put("peer_web_port", net.GetWebWorldPort()); + if (success) { + pt.put("instance_id", details.instanceId); + pt.put("zone_id", details.zoneId); + pt.put("zone_name", details.zoneName); + } + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_sendglobalmessage(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + int32 success = 0; + int8 language = 0; + int16 in_channel = 0; + std::string toName(""), fromName(""), msg(""); + int32 group_id = 0; + int32 guild_id = 0; + if (auto name = json_tree.get_optional("to_name")) { + toName = name.get(); + } + if (auto name = json_tree.get_optional("from_name")) { + fromName = name.get(); + } + if (auto message = json_tree.get_optional("message")) { + msg = message.get(); + } + if (auto from_language = json_tree.get_optional("from_language")) { + language = from_language.get(); + } + if (auto channel = json_tree.get_optional("channel")) { + in_channel = channel.get(); + } + if (auto group = json_tree.get_optional("group_id")) { + group_id = group.get(); + } + if (auto guildID = json_tree.get_optional("guild_id")) { + guild_id = guildID.get(); + } + + Client* find_client = zone_list.GetClientByCharName(toName.c_str()); + if (find_client && find_client->GetPlayer()->IsIgnored(fromName.c_str())) + success = 0; + else { + switch (in_channel) { + case CHANNEL_PRIVATE_TELL: { + if (find_client && find_client->GetPlayer()) { + success = 1; + toName = std::string(find_client->GetPlayer()->GetName()); + find_client->HandleTellMessage(fromName.c_str(), msg.c_str(), toName.c_str(), language); + if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { + find_client->HandleTellMessage(toName.c_str(), find_client->GetPlayer()->GetAwayMessage().c_str(), fromName.c_str(), find_client->GetPlayer()->GetCurrentLanguage()); + pt.put("away_message", find_client->GetPlayer()->GetAwayMessage()); + pt.put("away_language", find_client->GetPlayer()->GetCurrentLanguage()); + } + } + break; + } + case CHANNEL_GROUP_SAY: + case CHANNEL_RAID_SAY: { + if (group_id) { + success = 1; + if (fromName.size() > 0) { + world.GetGroupManager()->GroupChatMessage(group_id, fromName, language, msg.c_str(), in_channel); + } + else { + world.GetGroupManager()->GroupMessage(group_id, msg.c_str()); + } + } + break; + } + case CHANNEL_OUT_OF_CHARACTER: { + success = 1; + zone_list.SendZoneWideChannelMessage(fromName, "", in_channel, msg.c_str(), 0, "", language); + break; + } + case CHANNEL_GUILD_SAY: { + if (!guild_id) + break; + + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + guild->HandleGuildSay(fromName, msg.c_str(), language); + } + break; + } + case CHANNEL_OFFICER_SAY: { + if (!guild_id) + break; + + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + guild->HandleGuildSay(fromName, msg.c_str(), language); + } + break; + } + case CHANNEL_GUILD_EVENT: { + if (!guild_id) + break; + + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + guild->SendMessageToGuild(CHANNEL_GUILD_EVENT, msg.c_str()); + } + break; + } + case CHANNEL_GUILD_CHAT: { + + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + guild->SendGuildChatMessage(msg.c_str()); + } + break; + } + } + } + + pt.put("success", success); + pt.put("from_name", fromName); + pt.put("to_name", toName); + pt.put("message", msg); + pt.put("from_language", language); + pt.put("channel", in_channel); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + + +void World::Web_worldhandle_newgroup(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + + GroupOptions options; + int32 group_id = 0; + bool is_client = false; + std::string leader(""), member(""); + std::string web_address(""); + int16 web_port = 0; + int32 success = 0; + bool is_update = false; + + if (auto address = json_tree.get_optional("peer_web_address")) { + web_address = address.get(); + } + if (auto webport = json_tree.get_optional("peer_web_port")) { + web_port = webport.get(); + } + if (auto leader_name = json_tree.get_optional("leader_name")) { + leader = leader_name.get(); + } + if (auto member_name = json_tree.get_optional("member_name")) { + member = member_name.get(); + } + if (auto isclient = json_tree.get_optional("is_client")) { + is_client = isclient.get(); + } + if (auto lootmethod = json_tree.get_optional("loot_method")) { + options.loot_method = lootmethod.get(); + } + if (auto itemrarity = json_tree.get_optional("loot_item_rarity")) { + options.loot_items_rarity = itemrarity.get(); + } + if (auto autosplit = json_tree.get_optional("auto_split")) { + options.auto_split = autosplit.get(); + } + if (auto defaultyell = json_tree.get_optional("default_yell")) { + options.default_yell = defaultyell.get(); + } + if (auto grouplockmethod = json_tree.get_optional("group_lock_method")) { + options.group_lock_method = grouplockmethod.get(); + } + if (auto groupautolock = json_tree.get_optional("group_auto_lock")) { + options.group_autolock = groupautolock.get(); + } + if (auto soloautolock = json_tree.get_optional("solo_auto_lock")) { + options.solo_autolock = soloautolock.get(); + } + if (auto autoloot = json_tree.get_optional("auto_loot_method")) { + options.auto_loot_method = autoloot.get(); + } + if (auto lastlootindex = json_tree.get_optional("last_looted_index")) { + options.last_looted_index = lastlootindex.get(); + } + if (auto groupid = json_tree.get_optional("group_id")) { + group_id = groupid.get(); + } + std::vector raidGroups; + for (int i = 0; i < 4; i++) { + std::string fieldName("group_id_"); + fieldName.append(std::to_string(i)); + if (auto raid_group_id = json_tree.get_optional(fieldName)) { + int32 group_id = raid_group_id.get(); + if (group_id) { + raidGroups.push_back(group_id); + } + } + } + + if (auto isupdate = json_tree.get_optional("is_update")) { + is_update = isupdate.get(); + } + + if (is_update) { + std::vector::iterator group_itr; + + std::vector emptyGroup; + bool self = false; + if (raidGroups.size() < 1) { + raidGroups.push_back(group_id); + self = true; + } + + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + world.GetGroupManager()->SetGroupOptions((*group_itr), &options); + if (self) { + world.GetGroupManager()->ClearGroupRaid((*group_itr)); + world.GetGroupManager()->SendGroupUpdate((*group_itr), nullptr, true); + } + else { + world.GetGroupManager()->ReplaceRaidGroups((*group_itr), &raidGroups); + } + } + success = 1; + } + else if (net.is_primary) { + group_id = world.GetGroupManager()->NewGroup(nullptr, &options); + peer_manager.sendPeersNewGroupRequest(web_address, web_port, group_id, leader, member, &options, "", &raidGroups); + success = 1; + } + else if (group_id) { + int32 result = world.GetGroupManager()->NewGroup(nullptr, &options, group_id); + if (result) { + success = 1; + } + } + + pt.put("success", success); + pt.put("group_id", group_id); + pt.put("leader_name", leader); + pt.put("member_name", member); + pt.put("loot_method", options.loot_method); + pt.put("loot_items_rarity", options.loot_items_rarity); + pt.put("auto_split", options.auto_split); + pt.put("default_yell", options.default_yell); + pt.put("group_lock_method", options.group_lock_method); + pt.put("group_autolock", options.group_autolock); + pt.put("solo_autolock", options.solo_autolock); + pt.put("auto_loot_method", options.auto_loot_method); + pt.put("last_looted_index", options.last_looted_index); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + + +void World::Web_worldhandle_addgroupmember(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + bool is_update = false; + int32 group_id = 0; + GroupMemberInfo info; + if (auto member_name = json_tree.get_optional("member_name")) { + info.name = member_name.get(); + } + if (auto isclient = json_tree.get_optional("is_client")) { + info.is_client = isclient.get(); + } + if (auto groupid = json_tree.get_optional("group_id")) { + group_id = groupid.get(); + } + if (auto zone = json_tree.get_optional("zone")) { + info.zone = zone.get(); + } + + if (auto hp = json_tree.get_optional("current_hp")) { + info.hp_current = hp.get(); + } + if (auto hp = json_tree.get_optional("max_hp")) { + info.hp_max = hp.get(); + } + + if (auto power = json_tree.get_optional("current_power")) { + info.power_current = power.get(); + } + if (auto power = json_tree.get_optional("max_power")) { + info.power_max = power.get(); + } + + + if (auto level = json_tree.get_optional("level_current")) { + info.level_current = level.get(); + } + if (auto level = json_tree.get_optional("level_max")) { + info.level_max = level.get(); + } + + if (auto race = json_tree.get_optional("race_id")) { + info.race_id = race.get(); + } + if (auto class_ = json_tree.get_optional("class_id")) { + info.class_id = class_.get(); + } + + if (auto isleader = json_tree.get_optional("is_leader")) { + info.leader = isleader.get(); + } + + if (auto isupdate = json_tree.get_optional("is_update")) { + is_update = isupdate.get(); + } + + if (auto mentor_target = json_tree.get_optional("mentor_target_char_id")) { + info.mentor_target_char_id = mentor_target.get(); + } + + if (auto zoneID = json_tree.get_optional("zone_id")) { + info.zone_id = zoneID.get(); + } + + if (auto instanceID = json_tree.get_optional("instance_id")) { + info.zone_id = instanceID.get(); + } + + + if (auto clientPeerAddr = json_tree.get_optional("client_peer_address")) { + info.client_peer_address = clientPeerAddr.get(); + } + + if (auto clientPeerPort = json_tree.get_optional("client_peer_port")) { + info.client_peer_port = clientPeerPort.get(); + } + + if (auto raidLooter = json_tree.get_optional("is_raid_looter")) { + info.is_raid_looter = raidLooter.get(); + } + else { + info.is_raid_looter = false; + } + + bool success = false; + if (is_update) { + world.GetGroupManager()->UpdateGroupMemberInfoFromPeer(group_id, info.name, info.is_client, &info); + world.GetGroupManager()->SendGroupUpdate(group_id); + success = true; + } + else { + success = world.GetGroupManager()->AddGroupMemberFromPeer(group_id, &info); + } + + pt.put("success", success); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + + +void World::Web_worldhandle_removegroupmember(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + int32 group_id = 0; + int32 char_id = 0; + std::string name(""); + bool is_client = false; + if (auto member_name = json_tree.get_optional("member_name")) { + name = member_name.get(); + } + if (auto isclient = json_tree.get_optional("is_client")) { + is_client = isclient.get(); + } + if (auto groupid = json_tree.get_optional("group_id")) { + group_id = groupid.get(); + } + if (auto charid = json_tree.get_optional("character_id")) { + char_id = charid.get(); + } + + bool success = world.GetGroupManager()->RemoveGroupMember(group_id, name, is_client, char_id); + pt.put("success", success); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_disbandgroup(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + int32 group_id = 0; + if (auto groupid = json_tree.get_optional("group_id")) { + group_id = groupid.get(); + } + + world.GetGroupManager()->RemoveGroup(group_id); + pt.put("success", true); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_createguild(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + std::string newGuildName(""), leaderName(""); + int32 guildID = 0; + if (auto name = json_tree.get_optional("guild_name")) { + newGuildName = name.get(); + } + + if (auto name = json_tree.get_optional("leader_name")) { + leaderName = name.get(); + } + + if (auto guild_id = json_tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + + + + bool success = false; + if (net.is_primary) { + if (newGuildName.size() > 0) { + guildID = world.CreateGuild(newGuildName.c_str()); + if (guildID) { + peer_manager.sendPeersCreateGuild(guildID); + success = true; + } + } + } + else if (guildID) { + database.LoadGuild(guildID); + success = true; + } + + pt.put("success", success); + pt.put("guild_id", guildID); + pt.put("guild_name", newGuildName); + pt.put("leader_name", leaderName); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_addguildmember(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + std::string invitedBy(""); + int32 guildID = 0, charID = 0, joinTimestamp = 0; + int8 guildRank = 0; + if (auto character_id = json_tree.get_optional("character_id")) { + charID = character_id.get(); + } + if (auto name = json_tree.get_optional("invited_by")) { + invitedBy = name.get(); + } + if (auto guild_id = json_tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + if (auto join_timestamp = json_tree.get_optional("join_timestamp")) { + joinTimestamp = join_timestamp.get(); + } + + if (auto rank = json_tree.get_optional("rank")) { + guildRank = rank.get(); + } + + bool success = false; + if (guildID) { + Guild* guild = guild_list.GetGuild(guildID); + if (guild) { + guild->AddNewGuildMember(charID, invitedBy.c_str(), joinTimestamp, guildRank); + success = true; + } + } + pt.put("success", success); + pt.put("guild_id", guildID); + pt.put("invited_by", invitedBy); + pt.put("character_id", charID); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_removeguildmember(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + std::string removedBy(""); + int32 guildID = 0, charID = 0; + if (auto character_id = json_tree.get_optional("character_id")) { + charID = character_id.get(); + } + if (auto name = json_tree.get_optional("removed_by")) { + removedBy = name.get(); + } + if (auto guild_id = json_tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + bool success = false; + if (guildID) { + Guild* guild = guild_list.GetGuild(guildID); + if (guild) { + guild->RemoveGuildMember(charID, true); + success = true; + } + } + pt.put("success", success); + pt.put("guild_id", guildID); + pt.put("removed_by", removedBy); + pt.put("character_id", charID); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_setguildpermission(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + int32 guildID = 0; + int8 rank = 0, permission = 0, value_ = 0; + if (auto guild_id = json_tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + if (auto in_rank = json_tree.get_optional("rank")) { + rank = in_rank.get(); + } + if (auto in_permission = json_tree.get_optional("permission")) { + permission = in_permission.get(); + } + if (auto in_value = json_tree.get_optional("value")) { + value_ = in_value.get(); + } + bool success = false; + if (guildID) { + Guild* guild = guild_list.GetGuild(guildID); + if (guild) { + guild->SetPermission(rank, permission, value_, true, false); + success = true; + } + } + pt.put("success", success); + pt.put("guild_id", guildID); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_setguildeventfilter(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json; charset=utf-8"); + boost::property_tree::ptree pt, json_tree; + + std::istringstream json_stream(req.body()); + boost::property_tree::read_json(json_stream, json_tree); + int32 guildID = 0; + int8 eventID = 0, category = 0, value_ = 0; + if (auto guild_id = json_tree.get_optional("guild_id")) { + guildID = guild_id.get(); + } + if (auto event_id = json_tree.get_optional("event_id")) { + eventID = event_id.get(); + } + if (auto in_category = json_tree.get_optional("category")) { + category = in_category.get(); + } + if (auto in_value = json_tree.get_optional("value")) { + value_ = in_value.get(); + } + bool success = false; + if (guildID) { + Guild* guild = guild_list.GetGuild(guildID); + if (guild) { + guild->SetEventFilter(eventID, category, value_, true, false); + success = true; + } + } + pt.put("success", success); + pt.put("guild_id", guildID); + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} diff --git a/source/WorldServer/World.cpp b/source/WorldServer/World.cpp index f84875e..520403c 100644 --- a/source/WorldServer/World.cpp +++ b/source/WorldServer/World.cpp @@ -52,6 +52,9 @@ #include "Player.h" +#include "Web/PeerManager.h" +#include "Web/HTTPSClientPool.h" + #include #include #include @@ -88,14 +91,17 @@ WorldDatabase database; GuildList guild_list; Chat chat; Player player; - + extern ConfigReader configReader; extern LoginServer loginserver; extern World world; extern ZoneList zone_list; extern RuleManager rule_manager; extern LuaInterface* lua_interface; +extern PeerManager peer_manager; extern sint32 numclients; +extern PeerManager peer_manager; +extern HTTPSClientPool peer_https_pool; World::World() : save_time_timer(300000), time_tick_timer(3000), vitality_timer(3600000), player_stats_timer(60000), server_stats_timer(60000), /*remove_grouped_player(30000),*/ guilds_timer(60000), lotto_players_timer(500), watchdog_timer(10000) { save_time_timer.Start(); @@ -239,21 +245,68 @@ void World::init(std::string web_ipaddr, int16 web_port, std::string cert_file, //PopulateTOVStatMap(); group_buff_updates.Start(rule_manager.GetGlobalRule(R_Client, GroupSpellsTimer)->GetInt32()); + bool web_success = false; if(web_ipaddr.size() > 0 && web_port > 0) { try { world_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + // status providers world_webserver->register_route("/status", World::Web_worldhandle_status); world_webserver->register_route("/clients", World::Web_worldhandle_clients); + world_webserver->register_route("/zones", World::Web_worldhandle_zones); + + // administrative commands world_webserver->register_route("/setadminstatus", World::Web_worldhandle_setadminstatus); world_webserver->register_route("/reloadrules", World::Web_worldhandle_reloadrules); + world_webserver->register_route("/reloadcommand", World::Web_worldhandle_reloadcommand); + + // peering capabilities + world_webserver->register_route("/addpeer", World::Web_worldhandle_addpeer); + world_webserver->register_route("/addcharauth", World::Web_worldhandle_addcharauth); + world_webserver->register_route("/startzone", World::Web_worldhandle_startzone); + + world_webserver->register_route("/sendglobalmessage", World::Web_worldhandle_sendglobalmessage); + + world_webserver->register_route("/newgroup", World::Web_worldhandle_newgroup); + world_webserver->register_route("/addgroupmember", World::Web_worldhandle_addgroupmember); + world_webserver->register_route("/removegroupmember", World::Web_worldhandle_removegroupmember); + world_webserver->register_route("/disbandgroup", World::Web_worldhandle_disbandgroup); + + world_webserver->register_route("/createguild", World::Web_worldhandle_createguild); + world_webserver->register_route("/addguildmember", World::Web_worldhandle_addguildmember); + world_webserver->register_route("/removeguildmember", World::Web_worldhandle_removeguildmember); + world_webserver->register_route("/setguildpermission", World::Web_worldhandle_setguildpermission); + world_webserver->register_route("/setguildeventfilter", World::Web_worldhandle_setguildeventfilter); world_webserver->run(); LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + web_success = true; } catch (const std::exception& e) { LogWrite(INIT__ERROR, 0, "Init", "World Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); } } + if(!web_success) { + LogWrite(INIT__WARNING, 0, "Init", "World Web Server not started/configured, cannot attempt peering."); + return; + } + try { + std::map peers = net.GetWebPeers(); + std::map::iterator peer_itr; + if(peers.size() > 0) { + net.is_primary = false; + for(peer_itr = peers.begin(); peer_itr != peers.end(); peer_itr++) { + if(net.GetWebWorldAddress() == peer_itr->first && net.GetWebWorldPort() == peer_itr->second) + continue; // no good you can't add yourself + std::string portNum = std::to_string(peer_itr->second); + std::string peerName = "eq2emu_" + peer_itr->first + "_" + portNum; + peer_manager.addPeer(peerName, PeeringStatus::SECONDARY, "", "", 0, peer_itr->first, peer_itr->second); + peer_https_pool.addPeerClient(peerName, peer_itr->first, std::to_string(peer_itr->second), "/addpeer"); + } + } + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "World Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } } @@ -480,30 +533,59 @@ bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, co } if(channel == CHANNEL_PRIVATE_TELL){ Client* find_client = zone_list.GetClientByCharName(to); - if(!find_client || find_client->GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) + if(find_client && find_client->GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) return false; - else if(find_client == from) + + std::string peerId = peer_manager.GetCharacterPeerId(std::string(to)); + if(peerId.size() > 0) { + std::shared_ptr peer = peer_manager.getPeerById(peerId); + if(peer != nullptr) { + boost::property_tree::ptree root; + root.put("from_name", from->GetPlayer()->GetName()); + root.put("to_name", to); + root.put("message", message); + root.put("from_language", current_language_id); + root.put("channel", channel); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peer %s of Tell from %s to %s", __FUNCTION__, peerId.c_str(), from->GetPlayer()->GetName(), to); + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/sendglobalmessage", jsonPayload); + return true; + } + } + + if(!find_client) { + return false; + } + + if(find_client == from) { from->Message(CHANNEL_COLOR_RED,"You must be very lonely...(ERROR: Cannot send tell to self)"); } else { const char* whoto = find_client->GetPlayer()->GetName(); - find_client->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); - from->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); + find_client->HandleTellMessage(from->GetPlayer()->GetName(), message, whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(from->GetPlayer()->GetName(), message, whoto, from->GetPlayer()->GetCurrentLanguage()); if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { - find_client->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); - from->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + find_client->HandleTellMessage(find_client->GetPlayer()->GetName(), find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(find_client->GetPlayer()->GetName(), find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); } } } else if(channel == CHANNEL_GROUP_SAY) { GroupMemberInfo* gmi = from->GetPlayer()->GetGroupMemberInfo(); - if(gmi) + if(gmi) { world.GetGroupManager()->GroupMessage(gmi->group_id, message); + peer_manager.SendPeersChannelMessage(gmi->group_id, "", std::string(message), CHANNEL_GROUP_SAY, from->GetPlayer()->GetCurrentLanguage()); + } } else{ + if(channel == CHANNEL_OUT_OF_CHARACTER) { + peer_manager.SendPeersChannelMessage(0, std::string(from->GetPlayer()->GetName()), std::string(message), CHANNEL_OUT_OF_CHARACTER, from->GetPlayer()->GetCurrentLanguage()); + } list::iterator zone_iter; ZoneServer* zs = 0; MZoneList.readlock(__FUNCTION__, __LINE__); @@ -517,6 +599,18 @@ bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, co return true; } +void ZoneList::SendZoneWideChannelMessage(std::string fromName, const char* to, int16 channel, const char* message, float distance, const char* channel_name, int32 language) { + list::iterator zone_iter; + ZoneServer* zs = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zs = *zone_iter; + if(zs) + zs->HandleChatMessage(fromName, to, channel, message, distance, channel_name, language); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + void ZoneList::LoadSpellProcess(){ list::iterator zone_iter; ZoneServer* zone = 0; @@ -580,82 +674,487 @@ void ZoneList::Remove(ZoneServer* zone) { zlist.remove(zone); MZoneList.releasewritelock(__FUNCTION__, __LINE__); - ZoneServer* alternativeZone = Get(zoneName, false, false); + bool alternativeZone = GetZone(nullptr, 0, std::string(zoneName), false, false, false, false, true); if(!alternativeZone && !rule_manager.GetZoneRule(zone->GetZoneID(), R_World, MemoryCacheZoneMaps)->GetBool()) { world.RemoveMaps(std::string(zoneName)); } } -ZoneServer* ZoneList::Get(const char* zone_name, bool loadZone, bool skip_existing_zones, bool increment_zone) { - list::iterator zone_iter; - ZoneServer* tmp = 0; - ZoneServer* ret = 0; - if(!skip_existing_zones) { - MZoneList.readlock(__FUNCTION__, __LINE__); - for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ - tmp = *zone_iter; - if (!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && strlen(zone_name) == strlen(tmp->GetZoneName()) && - strncasecmp(tmp->GetZoneName(), zone_name, strlen(zone_name))==0){ - if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { - ret = tmp; - if(increment_zone) { - ret->IncrementIncomingClients(); - } - break; - } - } - } - - MZoneList.releasereadlock(__FUNCTION__, __LINE__); - } - - if(!ret ) - { - if ( loadZone ) - { - ret = new ZoneServer(zone_name); - database.LoadZoneInfo(ret); - ret->Init(); - } - } - return ret; -} - -ZoneServer* ZoneList::Get(int32 id, bool loadZone, bool skip_existing_zones, bool increment_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) { list::iterator zone_iter; ZoneServer* tmp = 0; ZoneServer* ret = 0; if(!skip_existing_zones) { - MZoneList.readlock(__FUNCTION__, __LINE__); - for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ - tmp = *zone_iter; - if(!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && tmp->GetZoneID() == id){ - if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { - ret = tmp; - if(increment_zone) { - ret->IncrementIncomingClients(); + if(!skip_self) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!check_instances && tmp->IsInstanceZone()) + continue; + + if(!tmp->isZoneShuttingDown() && ((opt_zone_id > 0 && tmp->GetZoneID() == opt_zone_id) || (opt_zone_name.length() > 0 && strncasecmp(tmp->GetZoneName(), opt_zone_name.c_str(), opt_zone_name.length())==0))){ + if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; } - break; } } + tmp = nullptr; + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(!ret && check_peers) { + std::string peerId = peer_manager.getZonePeerId(opt_zone_name, opt_zone_id, 0, zone_details, only_always_loaded); + if(peerId.size() > 0) { + LogWrite(WORLD__ERROR, 0, "World", "Peer %s is providing zone %s for zone %s id %u", peerId.c_str(), zone_details->zoneName.c_str(), opt_zone_name.c_str(), opt_zone_id); + return true; + } } - MZoneList.releasereadlock(__FUNCTION__, __LINE__); } if(ret) { tmp = ret; } else if (loadZone) { - string zonename = database.GetZoneName(id); - if(zonename.length() >0){ - tmp = new ZoneServer(zonename.c_str()); - database.LoadZoneInfo(tmp); - tmp->Init(); + if(opt_zone_name.length() < 1) { + opt_zone_name = database.GetZoneName(opt_zone_id); + } + if(opt_zone_name.length() > 0){ + std::shared_ptr peer = peer_manager.getHealthyPeerWithLeastClients(); + if(check_peers && peer != nullptr) { + tmp = new ZoneServer(opt_zone_name.c_str()); + database.LoadZoneInfo(tmp); + boost::property_tree::ptree root; + root.put("instance_id", 0); + root.put("zone_name", opt_zone_name); + root.put("zone_id", std::to_string(opt_zone_id)); + root.put("always_loaded", only_always_loaded); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peer %s StartZone %s (%u), always loaded %u", __FUNCTION__, peer->id.c_str(), opt_zone_name.c_str(), opt_zone_id, only_always_loaded); + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/startzone", jsonPayload); + peer_manager.setZonePeerData(zone_details, peer->id, peer->worldAddr, peer->internalWorldAddr, peer->worldPort, peer->webAddr, peer->webPort, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + tmp->GetInstanceID(), tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), tmp->GetMaximumLevel(), + tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers()); + safe_delete(tmp); + return true; + } + else { + tmp = new ZoneServer(opt_zone_name.c_str()); + database.LoadZoneInfo(tmp); + tmp->Init(); + tmp->SetAlwaysLoaded(only_always_loaded); + } } } - return tmp; + + if(tmp) { + peer_manager.setZonePeerDataSelf(zone_details, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + tmp->GetInstanceID(), tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), tmp->GetMaximumLevel(), + tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers(), tmp); + if(zone_details) { + zone_details->zonePtr = (void*)tmp; + } + } + 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) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!tmp->isZoneShuttingDown() && tmp->IsInstanceZone() && tmp->GetInstanceID() == instance_id){ + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + tmp = nullptr; + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + if(!ret && check_peers) { + std::string peerId = peer_manager.getZonePeerId("", 0, instance_id, zone_details); + if(peerId.size() > 0) { + LogWrite(WORLD__ERROR, 0, "World", "Peer %s is providing instanced zone %s for zone id %u instance id %u", peerId.c_str(), zone_details->zoneName, zone_id, instance_id); + return true; + } + } + } + + if(ret) { + tmp = ret; + } + else if ( loadZone && zone_id > 0 ){ + string zonename = database.GetZoneName(zone_id); + if(zonename.length() > 0){ + std::shared_ptr peer = peer_manager.getHealthyPeerWithLeastClients(); + if(check_peers && peer != nullptr && instance_id > 0) { + tmp = new ZoneServer(zonename.c_str()); + database.LoadZoneInfo(tmp); + boost::property_tree::ptree root; + root.put("instance_id", instance_id); + root.put("zone_name", zonename); + root.put("zone_id", std::to_string(zone_id)); + root.put("always_loaded", false); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Notify Peer %s StartZone %s (%u), instance %u", __FUNCTION__, peer->id.c_str(), zonename.c_str(), zone_id, instance_id); + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/startzone", jsonPayload); + peer_manager.setZonePeerData(zone_details, peer->id, peer->worldAddr, peer->internalWorldAddr, peer->worldPort, peer->webAddr, peer->webPort, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + instance_id, tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), tmp->GetMaximumLevel(), + tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers()); + safe_delete(tmp); + return true; + } + else { + tmp = new ZoneServer(zonename.c_str()); + + // the player is trying to preload an already existing instance but it isn't loaded + if ( instance_id > 0 ) + tmp->SetupInstance(instance_id); + + database.LoadZoneInfo(tmp); + tmp->Init(); + } + } + } + if(tmp) { + peer_manager.setZonePeerDataSelf(zone_details, std::string(tmp->GetZoneFile()), std::string(tmp->GetZoneName()), tmp->GetZoneID(), + tmp->GetInstanceID(), tmp->GetSafeX(), tmp->GetSafeY(), tmp->GetSafeZ(), tmp->GetSafeHeading(), + tmp->GetZoneLockState(), tmp->GetMinimumStatus(), tmp->GetMinimumLevel(), + tmp->GetMaximumLevel(), tmp->GetMinimumVersion(), tmp->GetDefaultLockoutTime(), tmp->GetDefaultReenterTime(), + tmp->GetInstanceType(), tmp->NumPlayers(), tmp); + zone_details->zonePtr = (void*)tmp; + } + return (tmp != nullptr) ? true : false; +} + +bool PeerManager::IsClientConnectedPeer(int32 account_id) { + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->client_tree->get_child("Clients")) { + // Access each field within the current zone + int32 client_acct_id = zone.second.get("account_id"); + bool is_linkdead = zone.second.get("is_linkdead"); + bool is_zoning = zone.second.get("is_zoning"); + bool in_zone = zone.second.get("in_zone"); + + if(client_acct_id == account_id) { + if(is_zoning) + return true; + else if(is_linkdead) + return false; + else if(in_zone) + return true; + } + } + } catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } + return false; +} + +std::string PeerManager::GetCharacterPeerId(std::string charName) { + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->client_tree->get_child("Clients")) { + // Access each field within the current zone + + std::string character_name = zone.second.get("character_name"); + + if(strncasecmp(character_name.c_str(), charName.c_str(), charName.length())==0) { + return peer->id; + } + } + } catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } + return ""; +} + +void PeerManager::SendPeersChannelMessage(int32 group_id, std::string fromName, std::string message, int16 channel, int32 language_id) { + boost::property_tree::ptree root; + root.put("message", message); + root.put("channel", channel); + root.put("group_id", group_id); + root.put("from_language", language_id); + root.put("from_name", fromName); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + std::vector raidGroups; + world.GetGroupManager()->GetRaidGroups(group_id, &raidGroups); + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->client_tree->get_child("Clients")) { + // Access each field within the current zone + + int32 player_group_id = zone.second.get("group_id"); + + + if(group_id == 0 || group_id == player_group_id || (std::find(raidGroups.begin(), raidGroups.end(), player_group_id) != raidGroups.end())) { + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/sendglobalmessage", jsonPayload); + break; + } + } + } catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } +} + +void PeerManager::SendPeersGuildChannelMessage(int32 guild_id, std::string fromName, std::string message, int16 channel, int32 language_id) { + boost::property_tree::ptree root; + root.put("message", message); + root.put("channel", channel); + root.put("guild_id", guild_id); + root.put("from_language", language_id); + root.put("from_name", fromName); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->client_tree->get_child("Clients")) { + // Access each field within the current zone + + int32 player_guild_id = zone.second.get("guild_id"); + + + if(guild_id == 0 || guild_id == player_guild_id) { + peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/sendglobalmessage", jsonPayload); + break; + } + } + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } +} + +void PeerManager::sendZonePeerList(Client* client) { + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& zone : peer->zone_tree->get_child("Zones")) { + // Access each field within the current zone + std::string zone_name = zone.second.get("zone_name"); + std::string zone_file_name = zone.second.get("zone_file_name"); + int32 zone_id = zone.second.get("zone_id"); + int32 instance_id = zone.second.get("instance_id"); + bool shutting_down = zone.second.get("shutting_down") == "true"; + bool instance_zone = zone.second.get("instance_zone") == "true"; + int32 num_players = zone.second.get("num_players"); + bool city_zone = zone.second.get("city_zone") == "true"; + float safe_x = zone.second.get("safe_x"); + float safe_y = zone.second.get("safe_y"); + float safe_z = zone.second.get("safe_z"); + float safe_heading = zone.second.get("safe_heading"); + bool lock_state = zone.second.get("lock_state"); + sint16 min_status = zone.second.get("min_status"); + int16 min_level = zone.second.get("min_level"); + int16 max_level = zone.second.get("max_level"); + int16 min_version = zone.second.get("min_version"); + int32 default_lockout_time = zone.second.get("default_lockout_time"); + int32 default_reenter_time = zone.second.get("default_reenter_time"); + int8 instance_type = zone.second.get("instance_type"); + + client->Message(CHANNEL_COLOR_YELLOW,"Zone (ID) (InstanceID): %s (%u) (%u), Peer: %s, NumPlayers: %u, Locked: %s, ShuttingDown: %s.",zone_name.c_str(),zone_id, + instance_id,peer->id.c_str(), num_players, lock_state ? "true" : "false", shutting_down ? "true" : "false"); + } + } catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Zones Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } +} +void PeerManager::sendZonePlayerList(std::vector* queries, std::vector* peer_list, bool isGM) { + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + + bool add_player = true; + bool found_match = false; + int8 lower = 0; + int8 upper = 0; + std::lock_guard lock(peer->dataMutex); + for (const auto& player : peer->client_tree->get_child("Clients")) { + std::string char_name = player.second.get("character_name"); + std::string subtitle = player.second.get("subtitle"); + std::string zone_name = player.second.get("zonedescription"); + int8 adventure_class = player.second.get("adventure_class"); + int8 tradeskill_class = player.second.get("tradeskill_class"); + int8 deity = player.second.get("deity"); + int8 race = player.second.get("race"); + sint16 status = player.second.get("status"); + int flags = player.second.get("flags"); + int flags2 = player.second.get("flags2"); + int16 level = player.second.get("level"); + found_match = false; + add_player = true; + + for(int32 i=0;add_player && queries && isize();i++){ + if(queries->at(i) == "ALL") + continue; + if(queries->at(i).length() > 3 && classes.GetClassID(queries->at(i).c_str()) > 0){ + if(adventure_class != classes.GetClassID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + else if(queries->at(i).length() > 2 && races.GetRaceID(queries->at(i).c_str()) > 0){ + if(race != races.GetRaceID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + if(!found_match && queries->at(i) == "GOOD"){ + if(deity != 1) + add_player = false; + found_match = true; + } + else if(!found_match && queries->at(i) == "EVIL"){ + if(deity == 1) + add_player = false; + found_match = true; + } + if((queries->at(i) == "GUIDE") && (status > 0) && ((status >> 4) < 5)) + found_match = true; + else if((queries->at(i) == "GM") && ((status >> 4) > 4)) + found_match = true; + else if((queries->at(i) == "LFG") && (flags & (1 << CF_LFG))) + found_match = true; + else if((queries->at(i) == "LFW") && (flags & (1 << CF_LFW))) + found_match = true; + else if((queries->at(i) == "ROLEPLAYING") && (flags & (1 << CF_ROLEPLAYING))) + found_match = true; + else if(strspn(queries->at(i).c_str(),"0123456789") == queries->at(i).length()){ + try{ + if(lower == 0) + lower = atoi(queries->at(i).c_str()); + else + upper = atoi(queries->at(i).c_str()); + } + catch(...){} + found_match = true; + } + if(!found_match){ + std::string name = ToUpper(char_name); + if(name.find(queries->at(i)) == name.npos) + add_player = false; + } + } + if(lower > 0 && upper > 0){ + if(level < lower || level > upper) + add_player = false; + } + else if(lower > 0){ + if(level != lower) + add_player = false; + } + if((flags2 & (1 << (CF_GM_HIDDEN - 32))) && !isGM) { + add_player = false; + found_match = true; + } + if(add_player) + peer_list->push_back(WhoAllPeerPlayer(char_name, subtitle, zone_name, adventure_class, tradeskill_class, deity, race, + status, flags, flags2, level)); + } + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } +} + +bool PeerManager::GetClientGuildDetails(int32 matchCharID, GuildMember* member_details) { + if(!member_details) + return false; + + for (auto& [peerId, peer] : peers) { + if(peer->healthCheck.status != HealthStatus::OK) + continue; + try { + std::lock_guard lock(peer->dataMutex); + for (const auto& player : peer->client_tree->get_child("Clients")) { + std::string char_name = player.second.get("character_name"); + std::string subtitle = player.second.get("subtitle"); + std::string zone_name = player.second.get("zonedescription"); + int8 adventure_class = player.second.get("adventure_class"); + int8 tradeskill_class = player.second.get("tradeskill_class"); + int8 deity = player.second.get("deity"); + int8 race = player.second.get("race"); + sint16 status = player.second.get("status"); + int flags = player.second.get("flags"); + int flags2 = player.second.get("flags2"); + int16 level = player.second.get("level"); + int16 tradeskill_level = player.second.get("tradeskill_level"); + int32 character_id = player.second.get("character_id"); + int32 account_id = player.second.get("account_id"); + + if(character_id == matchCharID) { + member_details->account_id = account_id; + member_details->character_id = character_id; + strncpy(member_details->name, char_name.c_str(), sizeof(member_details->name)); + member_details->guild_status = 0; + member_details->points = 0.0; + member_details->adventure_class = adventure_class; + member_details->adventure_level = level; + member_details->tradeskill_class = tradeskill_class; + member_details->tradeskill_level = tradeskill_level; + //gm->rank = rank; ?? not sure how yet + member_details->zone = zone_name; + //gm->join_date = Timer::GetUnixTimeStamp(); + //gm->last_login_date = gm->join_date; + member_details->recruiter_id = 0; + member_details->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + member_details->recruiting_show_adventure_class = 1; + member_details->recruiter_picture_data_size = 0; + member_details->recruiter_picture_data = 0; + return true; + } + } + } + catch (const std::exception& e) { + LogWrite(PEERING__ERROR, 0, "Peering", "%s: Clients Parsing Error %s for %s:%u/%s", __FUNCTION__, e.what() ? e.what() : "??", peer->webAddr.c_str(), peer->webPort); + } + } + return false; +} + + void ZoneList::SendZoneList(Client* client) { list::iterator zone_iter; @@ -664,57 +1163,12 @@ void ZoneList::SendZoneList(Client* client) { int zonesListed = 0; for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ tmp = *zone_iter; - if ( zonesListed > 20 ) - { - client->Message(CHANNEL_COLOR_YELLOW,"Reached max zone list of 20."); - break; - } - zonesListed++; - client->Message(CHANNEL_COLOR_YELLOW,"Zone(ID): %s(%i), Instance ID: %i, Description: %s.",tmp->GetZoneName(),tmp->GetZoneID(), - tmp->GetInstanceID(),tmp->GetZoneDescription()); + client->Message(CHANNEL_COLOR_YELLOW,"Zone (ID) (InstanceID): %s (%u) (%u), Description: %s, NumPlayers: %u, Locked: %s, ShuttingDown: %s.",tmp->GetZoneName(),tmp->GetZoneID(), + tmp->GetInstanceID(),tmp->GetZoneDescription(), tmp->NumPlayers(), tmp->GetZoneLockState() ? "true" : "false", tmp->isZoneShuttingDown() ? "true" : "false"); } MZoneList.releasereadlock(__FUNCTION__, __LINE__); -} - -ZoneServer* ZoneList::GetByInstanceID(int32 id, int32 zone_id, bool skip_existing_zones, bool increment_zone) { - list::iterator zone_iter; - ZoneServer* tmp = 0; - ZoneServer* ret = 0; - if(!skip_existing_zones) { - MZoneList.readlock(__FUNCTION__, __LINE__); - if ( id > 0 ) - { - for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ - tmp = *zone_iter; - if(!tmp->isZoneShuttingDown() && tmp->GetInstanceID() == id){ - ret = tmp; - if(increment_zone) { - ret->IncrementIncomingClients(); - } - break; - } - } - } - MZoneList.releasereadlock(__FUNCTION__, __LINE__); - } - - if(ret) { - tmp = ret; - } - else if ( zone_id > 0 ){ - string zonename = database.GetZoneName(zone_id); - if(zonename.length() > 0){ - tmp = new ZoneServer(zonename.c_str()); - - // the player is trying to preload an already existing instance but it isn't loaded - if ( id > 0 ) - tmp->SetupInstance(id); - - database.LoadZoneInfo(tmp); - tmp->Init(); - } - } - return tmp; + + peer_manager.sendZonePeerList(client); } ZoneServer* ZoneList::GetByLowestPopulation(int32 zone_id) { @@ -781,6 +1235,9 @@ bool ZoneList::ClientConnected(int32 account_id){ break; } } + if(!ret) { + ret = peer_manager.IsClientConnectedPeer(account_id); + } MClientList.unlock(); return ret; } @@ -911,6 +1368,8 @@ void ZoneList::ProcessWhoQuery(vector* queries, ZoneServer* zone, vector void ZoneList::ProcessWhoQuery(const char* query, Client* client){ list::iterator zone_iter; vector players; + vector peer_players; + vector::iterator peer_iter; vector::iterator spawn_iter; Entity* player = 0; //for now display all clients @@ -931,6 +1390,9 @@ void ZoneList::ProcessWhoQuery(const char* query, Client* client){ ProcessWhoQuery(queries, tmp, &players, isGM); } MZoneList.releasereadlock(__FUNCTION__, __LINE__); + if(queries) { + peer_manager.sendZonePlayerList(queries, &peer_players, isGM); + } } else{ ProcessWhoQuery(queries, client->GetCurrentZone(), &players, isGM); @@ -940,7 +1402,9 @@ void ZoneList::ProcessWhoQuery(const char* query, Client* client){ if(packet){ packet->setDataByName("account_id", client->GetAccountID()); packet->setDataByName("unknown", 0xFFFFFFFF); - int8 num_characters = players.size(); + int16 num_characters = players.size(); + int16 num_players_peers = peer_players.size(); + int16 total_results = num_characters + num_players_peers; int8 max_who_results = 10; int8 max_who_results_status_override = 100; @@ -956,17 +1420,24 @@ void ZoneList::ProcessWhoQuery(const char* query, Client* client){ Variable* var1 = variables.FindVariable("max_who_results"); if ( var1 ){ - max_who_results = atoi(var1->GetValue()); + max_who_results = atoul(var1->GetValue()); } - if(num_characters > max_who_results && client->GetAdminStatus() < max_who_results_status_override){ - num_characters = max_who_results; + if(total_results > max_who_results && client->GetAdminStatus() < max_who_results_status_override){ + total_results = max_who_results; + if(num_characters > total_results) + num_characters = total_results; + if((num_characters+num_players_peers) > max_who_results) { + int16 max_num_players_peers = max_who_results - num_characters; + if(num_players_peers > max_num_players_peers) + num_players_peers = max_num_players_peers; + } packet->setDataByName("response", 3); //response 1 = error message, 3 == capped } else packet->setDataByName("response", 2); - packet->setArrayLengthByName("num_characters", num_characters); - packet->setDataByName("unknown10", 1); + packet->setArrayLengthByName("num_characters", (int8)total_results); + packet->setDataByName("display_zone", 1); int i=0; for(spawn_iter = players.begin(); spawn_iter!=players.end(); spawn_iter++, i++){ if(i == num_characters) @@ -1007,6 +1478,41 @@ void ZoneList::ProcessWhoQuery(const char* query, Client* client){ packet->setArrayDataByName("guild", tmp_title, i); } } + + int8 count = 0; + for(peer_iter = peer_players.begin(); peer_iter!=peer_players.end(); peer_iter++, i++, count++){ + if(count == num_players_peers) + break; + int flags = (*peer_iter).flags; + int flags2 = (*peer_iter).flags2; + sint16 status = (*peer_iter).status; + packet->setArrayDataByName("char_name", (*peer_iter).name.c_str(), i); + packet->setArrayDataByName("level", (*peer_iter).level, i); + packet->setArrayDataByName("admin_level", ((flags2 & (1 << (CF_HIDE_STATUS - 32))) && !isGM)?0:(status >> 4), i); + packet->setArrayDataByName("class", (*peer_iter).adventureClass, i); + packet->setArrayDataByName("unknown4", 0xFF, i); //probably tradeskill class + packet->setArrayDataByName("flags", (((flags >> CF_ANONYMOUS) & 1) << 0 ) | + (((flags >> CF_LFG) & 1) << 1 ) | + (((flags >> CF_ANONYMOUS) & 1) << 2 ) | + /*(((flags >> CF_HIDDEN) & 1) << 3 ) |*/ + (((flags >> CF_ROLEPLAYING) & 1) << 4 ) | + (((flags >> CF_AFK) & 1) << 5 ) | + (((flags >> CF_LFW) & 1) << 6 ) /*| + (((flags >> CF_NOTA) & 1) << 7 )*/, i); + packet->setArrayDataByName("race", (*peer_iter).race, i); + packet->setArrayDataByName("zone", (*peer_iter).zoneName.c_str(), i); + if((*peer_iter).subtitle.size() > 0) { + size_t start = (*peer_iter).subtitle.find('<'); + size_t end = (*peer_iter).subtitle.find('>'); + std::string result = (*peer_iter).subtitle; + // Check if both '<' and '>' are found and in the correct order + if (start != std::string::npos && end != std::string::npos && start < end) { + result = (*peer_iter).subtitle.substr(start + 1, end - start - 1); + } + packet->setArrayDataByName("guild", result.c_str(), i); + } + } + client->QueuePacket(packet->serialize()); safe_delete(packet); } @@ -1595,7 +2101,7 @@ bool World::RejoinGroup(Client* client, int32 group_id){ info = *itr; - if (info && info->name == name) + if (info && info->name == name && info->is_client) { info->client = client; info->member = client->GetPlayer(); @@ -1795,7 +2301,7 @@ void World::AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 v } } -void World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) { +int32 World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) { deque::iterator itr; GroupMemberInfo* gmi; Guild *guild; @@ -1807,7 +2313,7 @@ void World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) guild->SetFormedDate(Timer::GetUnixTimeStamp()); database.LoadGuildDefaultRanks(guild); database.LoadGuildDefaultEventFilters(guild); - database.SaveGuild(guild, true); + database.SaveGuild(guild, true); // sets the guild id database.SaveGuildEvents(guild); database.SaveGuildRanks(guild); database.SaveGuildEventFilters(guild); @@ -1834,6 +2340,8 @@ void World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); } + + return guild->GetID(); } void World::SaveGuilds() { @@ -2988,4 +3496,40 @@ void World::PurgeNPCSpells() { } npc_spell_list.clear(); +} + +void World::ClientAuthApproval(int32 success, std::string charName, int32 account_id, std::string zone_name, int32 zone_id, int32 instance_id, bool first_login) { + Client* find_client = zone_list.GetClientByCharName(charName.c_str()); + if(find_client) { + if(success) { + find_client->ApproveZone(); + } + else { + int32 zone_success = 0; + ZoneChangeDetails details; + if(instance_id || zone_id || zone_name.length() > 0) { + if(!instance_id) { + if((zone_list.GetZone(&details, zone_id, zone_name, true, true, false, false))) + zone_success = 1; + } + else { + if((zone_list.GetZoneByInstance(&details, instance_id, zone_id, true, true, false, false))) + zone_success = 1; + } + } + if(zone_success) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending to zone_auth.AddAuth...", __FUNCTION__); + int32 key = static_cast(MakeRandomFloat(0.01,1.0) * UINT32_MAX); + + details.zoneKey = key; + details.authDispatchedTime = key; + zone_auth.AddAuth(new ZoneAuthRequest(find_client->GetAccountID(), find_client->GetPlayer()->GetName(), key)); + find_client->SetZoningDetails(&details); + find_client->ApproveZone(); + } + } + } + else { + // can't find client + } } \ No newline at end of file diff --git a/source/WorldServer/World.h b/source/WorldServer/World.h index 5ef5261..70a5e2c 100644 --- a/source/WorldServer/World.h +++ b/source/WorldServer/World.h @@ -43,6 +43,8 @@ #include "./Zone/region_map.h" #include "./Zone/map.h" +#include "./Web/PeerManager.h" + using namespace std; struct MerchantInfo{ vector inventory_ids; @@ -381,6 +383,35 @@ struct VoiceOverStruct{ int8 garble_link_id; }; +struct WhoAllPeerPlayer { + std::string name; + std::string subtitle; + std::string zoneName; + int8 adventureClass; + int8 tradeskillClass; + int8 deity; + int8 race; + sint16 status; + int flags; + int flags2; + int16 level; + + WhoAllPeerPlayer(std::string inName, std::string inSubtitle, std::string inZoneName, int8 inAdvClass, int8 inTradeskillClass, int8 inDeity, int8 inRace, + sint16 inStatus, int inFlags, int inFlags2, int16 inLevel) + { + name = inName; + subtitle = inSubtitle; + zoneName = inZoneName; + adventureClass = inAdvClass; + tradeskillClass = inTradeskillClass; + deity = inDeity; + race = inRace; + status = inStatus; + flags = inFlags; + flags2 = inFlags2; + level = inLevel; + } +}; class ZoneList { public: ZoneList(); @@ -388,9 +419,13 @@ class ZoneList { void Add(ZoneServer* zone); void Remove(ZoneServer* zone); - ZoneServer* Get(int32 id, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true); - ZoneServer* Get(const char* zone_name, bool loadZone=true, bool skip_existing_zones = false, bool increment_zone = true); - ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0, bool skip_existing_zones = false, bool increment_zone = 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); + 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 IsClientConnectedPeer(int32 account_id); + //ZoneServer* Get(int32 id, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true); + //ZoneServer* Get(const char* zone_name, bool loadZone=true, bool skip_existing_zones = false, bool increment_zone = true); + //ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0, bool skip_existing_zones = false, bool increment_zone = true); /// Get the instance for the given zone id with the lowest population /// The id of the zone to look up @@ -467,6 +502,7 @@ class ZoneList { void Depop(); void Repop(); void DeleteSpellProcess(); + void SendZoneWideChannelMessage(std::string fromName, const char* to, int16 channel, const char* message, float distance, const char* channel_name, int32 language); void LoadSpellProcess(); void ProcessWhoQuery(const char* query, Client* client); void ProcessWhoQuery(vector* queries, ZoneServer* zone, vector* players, bool isGM); @@ -481,6 +517,7 @@ class ZoneList { void SendTimeUpdate(); void PopulateClientList(http::response& res); + void PopulateZoneList(http::response& res); private: Mutex MClientList; Mutex MZoneList; @@ -564,7 +601,7 @@ public: //bool MakeLeader(Client* leader, string new_leader); void AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity); - void CreateGuild(const char* guild_name, Client* leader = 0, int32 group_id = 0); + int32 CreateGuild(const char* guild_name, Client* leader = 0, int32 group_id = 0); void SaveGuilds(); void PickRandomLottoDigits(int32* digits); void AddLottoPlayer(int32 character_id, int32 end_time); @@ -648,12 +685,28 @@ public: void PurgeNPCSpells(); + void ClientAuthApproval(int32 success, std::string charName, int32 account_id, std::string zone_name, int32 zoning_id, int32 instance_id, bool first_login); + static void Web_worldhandle_status(const http::request& req, http::response& res); static void Web_worldhandle_clients(const http::request& req, http::response& res); static void Web_worldhandle_setadminstatus(const http::request& req, http::response& res); static void Web_worldhandle_reloadrules(const http::request& req, http::response& res); - + static void Web_worldhandle_reloadcommand(const http::request& req, http::response& res); + static void Web_worldhandle_addpeer(const http::request& req, http::response& res); + static void Web_worldhandle_zones(const http::request& req, http::response& res); + static void Web_worldhandle_addcharauth(const http::request& req, http::response& res); + static void Web_worldhandle_startzone(const http::request& req, http::response& res); + static void Web_worldhandle_sendglobalmessage(const http::request& req, http::response& res); + static void Web_worldhandle_newgroup(const http::request& req, http::response& res); + static void Web_worldhandle_addgroupmember(const http::request& req, http::response& res); + static void Web_worldhandle_removegroupmember(const http::request& req, http::response& res); + static void Web_worldhandle_disbandgroup(const http::request& req, http::response& res); + static void Web_worldhandle_createguild(const http::request& req, http::response& res); + static void Web_worldhandle_addguildmember(const http::request& req, http::response& res); + static void Web_worldhandle_removeguildmember(const http::request& req, http::response& res); + static void Web_worldhandle_setguildpermission(const http::request& req, http::response& res); + static void Web_worldhandle_setguildeventfilter(const http::request& req, http::response& res); Mutex MVoiceOvers; static sint64 newValue; diff --git a/source/WorldServer/WorldDatabase.cpp b/source/WorldServer/WorldDatabase.cpp index 833a7e5..7b153d4 100644 --- a/source/WorldServer/WorldDatabase.cpp +++ b/source/WorldServer/WorldDatabase.cpp @@ -52,7 +52,7 @@ along with EQ2Emulator. If not, see . #include "../common/version.h" #include "SpellProcess.h" #include "races.h" - +#include "Web/PeerManager.h" extern Classes classes; extern Commands commands; @@ -74,6 +74,7 @@ extern MasterCollectionList master_collection_list; extern RuleManager rule_manager; extern MasterLanguagesList master_languages_list; extern ChestTrapList chest_trap_list; +extern PeerManager peer_manager; //devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp #if defined(__GNUC__) @@ -1256,9 +1257,9 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){ "ON ss.spawn_id = le.spawn_id\n" "INNER JOIN spawn_location_placement lp\n" "ON le.spawn_location_id = lp.spawn_location_id\n" - "WHERE lp.zone_id = %u\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" "GROUP BY s.id", - zone->GetZoneID()); + zone->GetZoneID(), zone->GetInstanceID()); while(result && (row = mysql_fetch_row(result))){ int32 signXpackFlag = atoul(row[28]); @@ -1352,9 +1353,9 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){ "ON sw.spawn_id = le.spawn_id\n" "INNER JOIN spawn_location_placement lp\n" "ON le.spawn_location_id = lp.spawn_location_id\n" - "WHERE lp.zone_id = %u\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" "GROUP BY s.id", - zone->GetZoneID()); + zone->GetZoneID(), zone->GetInstanceID()); while(result && (row = mysql_fetch_row(result))){ int32 widgetXpackFlag = atoul(row[33]); int32 widgetHolidayFlag = atoul(row[34]); @@ -1541,9 +1542,9 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){ "ON sg.spawn_id = le.spawn_id\n" "INNER JOIN spawn_location_placement lp\n" "ON le.spawn_location_id = lp.spawn_location_id\n" - "WHERE lp.zone_id = %u\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" "GROUP BY s.id", - zone->GetZoneID()); + zone->GetZoneID(), zone->GetInstanceID()); while(result && (row = mysql_fetch_row(result))){ int32 gsXpackFlag = atoul(row[21]); @@ -1839,7 +1840,9 @@ SOGA chars looked ok in LoginServer screen tho... odd. if ( LoadCharacterInstances(client) ) client->UpdateCharacterInstances(); - if ( instanceid > 0 ) + InstanceData* data = client->GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneid); + // housing doesn't have a data pointer here is why the data check was removed.. hmm + if (instanceid > 0) client->SetCurrentZoneByInstanceID(instanceid, zoneid); else client->SetCurrentZone(zoneid); @@ -1881,6 +1884,45 @@ SOGA chars looked ok in LoginServer screen tho... odd. LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name); return false; } +std::string WorldDatabase::loadCharacterFromLogin(ZoneChangeDetails* details, int32 char_id, int32 account_id) { + std::string name(""); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, current_zone_id, instance_id FROM characters where id=%u and account_id=%u AND deleted = 0", char_id, account_id); + // no character found + if ( result == NULL ) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character from login for char id '%u'", char_id); + return name; + } + + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + int32 current_zone_id = atoul(row[1]); + int32 instance_id = atoul(row[2]); + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading character from login for '%s' (char_id: %u)", row[0], char_id); + + int32 success = 0; + details->peerId = std::string(""); + details->instanceId = instance_id; + details->zoneId = current_zone_id; + if(instance_id || current_zone_id) { + if(!instance_id) { + if((zone_list.GetZone(details, current_zone_id, "", false, false, false, true))) + success = 1; + } + else { + if((zone_list.GetZoneByInstance(details, instance_id, current_zone_id, false, false, false, true))) + success = 1; + } + } + if(success) { + name = std::string(row[0]); + } + return name; + } + return name; +} void WorldDatabase::LoadCharacterQuestRewards(Client* client) { Query query; @@ -3179,17 +3221,57 @@ string WorldDatabase::GetExpansionIDByVersion(int16 version) void WorldDatabase::LoadSpecialZones(){ + LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); Query query; ZoneServer* zone = 0; - MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, always_loaded FROM zones where always_loaded = 1"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, always_loaded, peer_priority FROM zones where always_loaded = 1"); if(result && mysql_num_rows(result) > 0) { MYSQL_ROW row; while(result && (row = mysql_fetch_row(result))){ - zone = new ZoneServer(row[1]); - LoadZoneInfo(zone); - zone->Init(); + int16 peer_priority_req = 0; + if(row[3]) { + peer_priority_req = atoul(row[3]); + } + + ZoneChangeDetails zone_details_peer; + ZoneChangeDetails zone_details_self; + bool gotZonePeer = zone_list.GetZone(&zone_details_peer, 0, std::string(row[1]), /*loadZone*/false, /*skip_existing_zones*/false, /*increment_zone*/false, /*check_peers*/true, /*check_instances*/false, + /*only_always_loaded*/true, /*skip_self*/true); + bool gotZoneSelf = zone_list.GetZone(&zone_details_self, 0, std::string(row[1]), /*loadZone*/false, /*skip_existing_zones*/false, /*increment_zone*/false, /*check_peers*/false, /*check_instances*/false, + /*only_always_loaded*/true, /*skip_self*/false); + bool hasPriorityPeer = peer_manager.hasPriorityPeer(peer_priority_req); + if(!gotZonePeer && !gotZoneSelf && peer_priority_req == net.GetPeerPriority()) { + LogWrite(ZONE__INFO, 0, "Zone", "Starting static zone %s.", row[1]); + zone = new ZoneServer(row[1]); + LoadZoneInfo(zone); + zone->Init(); - zone->SetAlwaysLoaded(atoi(row[2]) == 1); + zone->SetAlwaysLoaded(atoul(row[2]) == 1); + } + else if(!gotZonePeer && gotZoneSelf && zone_details_self.zonePtr && !((ZoneServer*)zone_details_self.zonePtr)->AlwaysLoaded()) { + LogWrite(ZONE__INFO, 0, "Zone", "Making zone %s static as we lost peer to handle the zone.", row[1]); + ((ZoneServer*)zone_details_self.zonePtr)->SetAlwaysLoaded(true); + } + else if(gotZonePeer && gotZoneSelf) { + std::shared_ptr peer = peer_manager.getPeerById(zone_details_peer.peerId); + if(peer && peer->healthCheck.status == HealthStatus::OK) { + if(peer_priority_req == peer->peerPriority || peer->peerPriority < net.GetPeerPriority()) { + ZoneServer* zone = (ZoneServer*)zone_details_self.zonePtr; + if(zone) { + LogWrite(ZONE__INFO, 0, "Zone", "Static zone %s will be spundown due to another peer taking over.", row[1]); + zone->SetAlwaysLoaded(false); + } + } + } + } + else if(!gotZonePeer && !gotZoneSelf && net.is_primary && (!hasPriorityPeer || peer_priority_req == USHRT_MAX)) { + gotZonePeer = zone_list.GetZone(&zone_details_peer, 0, std::string(row[1]), /*loadZone*/true, /*skip_existing_zones*/true, /*increment_zone*/false, /*check_peers*/true, /*check_instances*/false, + /*only_always_loaded*/true, /*skip_self*/true); + if(!gotZonePeer) { + bool gotZoneSelf = zone_list.GetZone(&zone_details_self, 0, std::string(row[1]), /*loadZone*/true, /*skip_existing_zones*/true, /*increment_zone*/false, /*check_peers*/false, /*check_instances*/false, + /*only_always_loaded*/true, /*skip_self*/false); + } + } } } } @@ -3341,7 +3423,7 @@ int32 WorldDatabase::LoadSpawnLocationGroups(ZoneServer* zone){ int32 WorldDatabase::ProcessSpawnLocations(ZoneServer* zone, const char* sql_query, int8 type){ int32 number = 0; Query query; - MYSQL_RES* result = query.RunQuery2(Q_SELECT, sql_query, zone->GetZoneID()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, sql_query, zone->GetZoneID(), zone->GetInstanceID()); if(result && mysql_num_rows(result) > 0) { MYSQL_ROW row; int32 spawn_location_id = 0xFFFFFFFF; @@ -3432,16 +3514,28 @@ void WorldDatabase::LoadSpawns(ZoneServer* zone) LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter LoadSpawns"); - npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC); - objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT); - widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET); - signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN); - ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN); - spawn_groups = LoadSpawnLocationGroups(zone); - spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); - spawn_group_chances = LoadSpawnGroupChances(zone); - LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s)", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances); + if(zone->GetInstanceID() == 0) { + npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC); + objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT); + widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET); + signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN); + ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN); + spawn_groups = LoadSpawnLocationGroups(zone); + spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); + spawn_group_chances = LoadSpawnGroupChances(zone); + } + else { + npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC); + objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT); + widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET); + signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN); + ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN); + spawn_groups = LoadSpawnLocationGroups(zone); + spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); + spawn_group_chances = LoadSpawnGroupChances(zone); + } + LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s)", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances); LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit LoadSpawns"); } @@ -3985,13 +4079,15 @@ void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_i if(is_instance) // should only be true if we get a result { - // this will force a pre-load - ZoneServer* instance_zone = zone_list.GetByInstanceID(0, zone_id); - if (instance_zone) { + string zone_name = GetZoneName(zone_id); + if(zone_name.size() > 0) { + ZoneServer* tmp = new ZoneServer(zone_name.c_str()); instance_id = CreateNewInstance(zone_id); - AddCharacterInstance(char_id, instance_id, string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); - // make sure we inherit the instance id setup in the AddCharacterInstance - instance_zone->SetupInstance(instance_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); } } @@ -4246,12 +4342,20 @@ void WorldDatabase::Save(Client* client){ return; int32 instance_id = 0; - if ( client->GetCurrentZone ( ) != NULL ) + if(client->IsZoning() && client->GetZoningID()) { + instance_id = client->GetZoningInstanceID(); + } + else if ( client->GetCurrentZone ( ) != NULL ) instance_id = client->GetCurrentZone()->GetInstanceID(); - + int32 zone_id = 0; - if(client->GetCurrentZone()) + + if(client->IsZoning() && client->GetZoningID()) { + zone_id = client->GetZoningID(); + } + else if(client->GetCurrentZone()) zone_id = client->GetCurrentZone()->GetZoneID(); + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update characters set current_zone_id=%u, x=%f, y=%f, z=%f, heading=%f, level=%i,instance_id=%i,last_saved=%i, `class`=%i, `tradeskill_level`=%i, `tradeskill_class`=%i, `group_id`=%u, deity = %u, alignment = %u where id = %u", zone_id, player->GetX(), player->GetY(), player->GetZ(), player->GetHeading(), player->GetLevel(), instance_id, client->GetLastSavedTimeStamp(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetTSLevel(), client->GetPlayer()->GetTradeskillClass(), client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : client->GetRejoinGroupID(), client->GetPlayer()->GetDeity(), client->GetPlayer()->GetInfoStruct()->get_alignment(), client->GetCharacterID()); query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update character_details set hp=%u, power=%u, str=%i, sta=%i, agi=%i, wis=%i, intel=%i, heat=%i, cold=%i, magic=%i, mental=%i, divine=%i, disease=%i, poison=%i, coin_copper=%u, coin_silver=%u, coin_gold=%u, coin_plat=%u, max_hp = %u, max_power=%u, xp = %u, xp_needed = %u, xp_debt = %f, xp_vitality = %f, tradeskill_xp = %u, tradeskill_xp_needed = %u, tradeskill_xp_vitality = %f, bank_copper = %u, bank_silver = %u, bank_gold = %u, bank_plat = %u, status_points = %u, bind_zone_id=%u, bind_x = %f, bind_y = %f, bind_z = %f, bind_heading = %f, house_zone_id=%u, combat_voice = %i, emote_voice = %i, biography='%s', flags=%u, flags2=%u, last_name='%s', assigned_aa = %i, unassigned_aa = %i, tradeskill_aa = %i, unassigned_tradeskill_aa = %i, prestige_aa = %i, unassigned_prestige_aa = %i, tradeskill_prestige_aa = %i, unassigned_tradeskill_prestige_aa = %i, pet_name = '%s' where char_id = %u", player->GetHP(), player->GetPower(), player->GetStrBase(), player->GetStaBase(), player->GetAgiBase(), player->GetWisBase(), player->GetIntBase(), player->GetHeatResistanceBase(), player->GetColdResistanceBase(), player->GetMagicResistanceBase(), @@ -5084,9 +5188,11 @@ int32 WorldDatabase::LoadPlayerSkillbar(Client* client){ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){ Guild *guild; - if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id)))) + if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id)))) { guild->RemoveGuildMember(character_id); - + peer_manager.sendPeersRemoveGuildMember(character_id, guild->GetID(), "*deleted_character*"); + } + Query query; //devn00b: Update this whole thing we were missing many tables. This is ugly but swapped 99% of the delete to async to lighten server load. @@ -5706,10 +5812,9 @@ int32 WorldDatabase::CheckSpawnRemoveInfo(map* inmap, int32 spawn_l if ( inmap != NULL ) { - for(iter=inmap->begin();iter!=inmap->end();iter++) - { - if ( iter->first == spawn_location_entry_id ) - return (int32)iter->second; + iter = inmap->find(spawn_location_entry_id); + if(iter != inmap->end()) { + return (int32)iter->second; } } @@ -5977,6 +6082,84 @@ bool WorldDatabase::LoadCharacterInstances(Client* client) return addedInstance; } + +bool WorldDatabase::DeletePersistedRespawn(int32 zone_id, int32 spawn_location_entry_id) +{ + if( !database_new.Query("DELETE FROM persisted_respawns WHERE zone_id = %u AND spawn_location_entry_id = %u", zone_id, spawn_location_entry_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeletePersistedRespawn() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Deleted persisted spawn: %u for zone_id %u", spawn_location_entry_id, zone_id); + + return true; +} + +int32 WorldDatabase::CreatePersistedRespawn(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 zone_id) +{ + int32 ret = 0; + + LogWrite(ZONE__DEBUG, 5, "Instance", "-- Creating new persisted Location Entry ID: %u, Type: %u, Respawn: %u, ZoneID: %u", spawn_location_entry_id, spawn_type, respawn_time, zone_id); + + if( !database_new.Query("INSERT INTO persisted_respawns (spawn_location_entry_id, spawn_type, zone_id, respawn_time) values(%u, %u, %u, %u)", spawn_location_entry_id, spawn_type, zone_id, respawn_time) ) + LogWrite(ZONE__ERROR, 0, "Instance", "Error in CreatePersistedRespawn() query '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + else + ret = database_new.LastInsertID(); + + // potentially spammy, if it calls for every spawn added. Set to level 3 or 5? + if( ret > 0 ) + LogWrite(ZONE__DEBUG, 5, "Instance", "Created new persisted respawn entry: %u for zone_id %u", ret, zone_id); + + return ret; +} + +map* WorldDatabase::GetPersistedSpawns(int32 zone_id, int8 type) +{ + DatabaseResult result; + map* ret = NULL; + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Loading persisted spawns for zone_id: %u, spawn_type: %u", zone_id, type); + + if( !database_new.Select(&result, "SELECT spawn_location_entry_id, respawn_time FROM persisted_respawns WHERE zone_id = %u AND spawn_type = %u", zone_id, type) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in GetInstanceRemovedSpawns() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return ret; + } + else + { + if( result.GetNumRows() > 0 ) + { + ret = new map; + + while( result.Next() ) + { + int32 spawn_location_entry_id = result.GetInt32Str("spawn_location_entry_id"); + /* + respawnTime == 0 - never respawn + respawnTime = 1 - spawn now + respawnTime > 1 (continue timer) + */ + int32 respawntime = result.GetInt32Str("respawn_time"); + + LogWrite(INSTANCE__DEBUG, 5, "Instance", "Found persisted spawn point: %u, respawn time: %u", spawn_location_entry_id, respawntime); + + ret->insert(make_pair(spawn_location_entry_id, respawntime)); + } + } + else + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No persisted spawns found for zone_id: %u, spawn_type: %u", zone_id, type); + + } + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + + return ret; +} + + void WorldDatabase::UpdateLoginEquipment() { LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Updating `character_items` CRC in %s", __FUNCTION__); diff --git a/source/WorldServer/WorldDatabase.h b/source/WorldServer/WorldDatabase.h index 30e98cb..ee964ca 100644 --- a/source/WorldServer/WorldDatabase.h +++ b/source/WorldServer/WorldDatabase.h @@ -322,6 +322,7 @@ public: void LoadDataFromRow(DatabaseResult *result, Item* item); void LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16); bool loadCharacter(const char* name, int32 account_id, Client* client); + std::string loadCharacterFromLogin(ZoneChangeDetails* details, int32 char_id, int32 account_id); bool LoadCharacterStats(int32 id, int32 account_id, Client* client); void LoadCharacterQuestRewards(Client* client); void LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id); @@ -411,6 +412,10 @@ public: bool DeleteInstanceSpawnRemoved(int32 instance_id, int32 spawn_location_entry_id); bool DeleteCharacterFromInstance(int32 char_id, int32 instance_id); bool LoadCharacterInstances(Client* client); + + bool DeletePersistedRespawn(int32 zone_id, int32 spawn_location_entry_id); + int32 CreatePersistedRespawn(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 zone_id); + map* GetPersistedSpawns(int32 zone_id, int8 type); // MutexMap* GetEquipmentUpdates(); @@ -468,6 +473,7 @@ public: /* Guilds */ void LoadGuilds(); + void LoadGuild(int32 guild_id); int32 LoadGuildMembers(Guild* guild); void LoadGuildEvents(Guild* guild); void LoadGuildRanks(Guild* guild); diff --git a/source/WorldServer/client.cpp b/source/WorldServer/client.cpp index a92a952..f381e25 100644 --- a/source/WorldServer/client.cpp +++ b/source/WorldServer/client.cpp @@ -28,7 +28,10 @@ along with EQ2Emulator. If not, see . #include #include #include +#include + #include "Player.h" +#include "PlayerGroups.h" #include "Commands/Commands.h" #include "ClientPacketFunctions.h" #include "../common/ConfigReader.h" @@ -41,6 +44,9 @@ along with EQ2Emulator. If not, see . #include "Zone/ChestTrap.h" #include "../common/GlobalHeaders.h" +#include "./Web/PeerManager.h" +#include "./Web/HTTPSClientPool.h" + //#include "Quests.h" #ifdef WIN32 @@ -121,6 +127,8 @@ extern MasterAAList master_tree_nodes; extern ChestTrapList chest_trap_list; extern MasterRecipeBookList master_recipebook_list; extern VisualStates visual_states; +extern PeerManager peer_manager; +extern HTTPSClientPool peer_https_pool; using namespace std; @@ -153,13 +161,14 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125 camp_timer = 0; linkdead_timer = 0; client_zoning = false; + client_zoning_details_set = false; zoning_id = 0; zoning_x = 0; zoning_y = 0; zoning_z = 0; zoning_instance_id = 0; player_pos_changed = false; - player_pos_timer = Timer::GetCurrentTime2()+1000; + player_pos_timer = Timer::GetCurrentTime2() + 1000; enabled_player_pos_timer = true; ++numclients; if (world.GetServerStatisticValue(STAT_SERVER_MOST_CONNECTIONS) < numclients) @@ -251,7 +260,7 @@ Client::~Client() { safe_delete(queued_quest); } quest_queue.clear(); - + vector::iterator rwd_itr; QuestRewardData* quest_rwd_data = 0; for (rwd_itr = quest_pending_reward.begin(); rwd_itr != quest_pending_reward.end(); rwd_itr++) { @@ -259,7 +268,7 @@ Client::~Client() { safe_delete(quest_rwd_data); } quest_pending_reward.clear(); - + safe_delete(CLE_keepalive_timer); safe_delete(connect); --numclients; @@ -268,9 +277,9 @@ Client::~Client() { void Client::RemoveClientFromZone() { - if(player && player->GetZone()) + if (player && player->GetZone()) player->GetZone()->GetSpellProcess()->RemoveSpellTimersFromSpawn(player, true, false, true, true); - + if (GetTempPlacementSpawn() && GetCurrentZone()) { Spawn* tmp = GetTempPlacementSpawn(); SetTempPlacementSpawn(nullptr); @@ -279,7 +288,7 @@ void Client::RemoveClientFromZone() { if (current_zone && player) { if (player->GetGroupMemberInfo()) { - TempRemoveGroup(); + TempRemoveGroup(); } world.GetGroupManager()->ClearPendingInvite(player); } @@ -298,7 +307,7 @@ void Client::RemoveClientFromZone() { MDeletePlayer.writelock(__FUNCTION__, __LINE__); player = nullptr; MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__); - + deque::iterator itr; MBuyBack.writelock(__FUNCTION__, __LINE__); for (itr = buy_back_items.begin(); itr != buy_back_items.end();) { @@ -336,7 +345,7 @@ void Client::PopulateSkillMap() { } void Client::SendLoginInfo() { - if(GetPlayer()->IsReturningFromLD()) + if (GetPlayer()->IsReturningFromLD()) firstlogin = true; if (firstlogin) { @@ -353,7 +362,7 @@ void Client::SendLoginInfo() { int32 count = 0; - if(!GetPlayer()->IsReturningFromLD()) + if (!GetPlayer()->IsReturningFromLD()) { count = database.LoadCharacterTitles(GetCharacterID(), player); if (count == 0) { @@ -363,7 +372,7 @@ void Client::SendLoginInfo() { } } - if(!GetPlayer()->IsReturningFromLD()) + if (!GetPlayer()->IsReturningFromLD()) { count = database.LoadCharacterLanguages(GetCharacterID(), player); if (count == 0) @@ -376,7 +385,7 @@ void Client::SendLoginInfo() { ClientPacketFunctions::SendLoginAccepted(this); - ClientPacketFunctions::SendAbilities ( this ); + ClientPacketFunctions::SendAbilities(this); ClientPacketFunctions::SendCommandNamePacket(this); @@ -389,7 +398,7 @@ void Client::SendLoginInfo() { zone_list.CheckFriendList(this); } - if(!GetPlayer()->IsReturningFromLD()) + if (!GetPlayer()->IsReturningFromLD()) { database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion()); if (firstlogin && player->item_list.GetNumberOfItems() == 0 && player->GetEquipmentList()->GetNumberOfItems() == 0) //re-add starting items if missing @@ -456,7 +465,7 @@ void Client::SendLoginInfo() { ClientPacketFunctions::SendCommandList(this); LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Language Updates..."); - + // kos doesn't like one of these language or instance list SendLanguagesUpdate(database.GetCharacterCurrentLang(GetCharacterID(), player)); @@ -528,7 +537,7 @@ void Client::DisplayDeadWindow() player->SetPower(0); GetCurrentZone()->TriggerCharSheetTimer(); - if(GetVersion() <= 561) { + if (GetVersion() <= 561) { ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 1); ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 1); } @@ -551,7 +560,7 @@ void Client::DisplayDeadWindow() void Client::HandlePlayerRevive(int32 point_id) { - if(GetVersion() <= 561) { + if (GetVersion() <= 561) { ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 0); ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 0); } @@ -588,7 +597,7 @@ void Client::HandlePlayerRevive(int32 point_id) player->SetResurrecting(true); player->SetHP(player->GetTotalHP()); player->SetPower(player->GetTotalPower()); - + //revive at zone safe coords if (!revive_point) { @@ -647,7 +656,7 @@ void Client::HandlePlayerRevive(int32 point_id) LogWrite(CCLIENT__DEBUG, 0, "Client", "Sending player to revive zone ID '%u', using current zone's safe coords.", revive_point->zone_id); location_name = revive_point->location_name.c_str(); //player->ClearEverything(); - Save(); + Save(); Zone(zone_name.c_str(), false); } } @@ -683,19 +692,19 @@ void Client::HandlePlayerRevive(int32 point_id) QueuePacket(packet->serialize()); safe_delete(packet); } - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_Combat, EnableSpiritShards)->GetBool()) + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_Combat, EnableSpiritShards)->GetBool()) { NPC* shard = player->InstantiateSpiritShard(origX, origY, origZ, origHeading, origGridID, originalZone); - if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + if (shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_PRESPAWN); originalZone->RemoveSpawn(player, false, true, true, true, true); originalZone->AddSpawn(shard); - - if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + + if (shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_SPAWN); } @@ -721,7 +730,7 @@ void Client::SendControlGhost(int32 send_id, int8 unknown2) { void Client::SendCharInfo() { EQ2Packet* app; - + player->SetEquippedItemAppearances(); ClientPacketFunctions::SendCharacterData(this); @@ -729,13 +738,13 @@ void Client::SendCharInfo() { SendCharPOVGhost(); SendControlGhost(player->GetIDWithPlayerSpawn(player), 255); - + //sending bad spawn packet? //SendAchievementsList(); //if (version > 561) //ClientPacketFunctions::SendHousingList(this); - + ClientPacketFunctions::SendCharacterSheet(this); ClientPacketFunctions::SendTraitList(this);// moved from below ClientPacketFunctions::SendAbilities(this); @@ -749,14 +758,14 @@ void Client::SendCharInfo() { } GetCurrentZone()->AddSpawn(player); - if(IsReloadingZone() && (zoning_x || zoning_y || zoning_z)) { - GetPlayer()->SetX(zoning_x); - GetPlayer()->SetY(zoning_y); - GetPlayer()->SetZ(zoning_z); - GetPlayer()->SetHeading(zoning_h); + if (IsReloadingZone() && (zoning_x || zoning_y || zoning_z)) { + GetPlayer()->SetX(zoning_x); + GetPlayer()->SetY(zoning_y); + GetPlayer()->SetZ(zoning_z); + GetPlayer()->SetHeading(zoning_h); - EQ2Packet* packet = GetPlayer()->Move(zoning_x, zoning_y, zoning_z, GetVersion(), zoning_h); - QueuePacket(packet); + EQ2Packet* packet = GetPlayer()->Move(zoning_x, zoning_y, zoning_z, GetVersion(), zoning_h); + QueuePacket(packet); } //SendCollectionList(); Guild* guild = player->GetGuild(); @@ -805,7 +814,7 @@ void Client::SendCharInfo() { } GetPlayer()->UpdateWeapons(); - if(!GetPlayer()->IsReturningFromLD()) { + if (!GetPlayer()->IsReturningFromLD()) { database.LoadBuyBacks(this); } if (version > 561) @@ -820,10 +829,11 @@ void Client::SendCharInfo() { if (zone_script && lua_interface) lua_interface->RunZoneScript(zone_script, "player_entry", GetCurrentZone(), GetPlayer()); this->client_zoning = false; + this->client_zoning_details_set = false; this->zoning_id = 0; this->zoning_instance_id = 0; SetZoningDestination(nullptr); - + if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower()) GetCurrentZone()->AddDamagedSpawn(player); @@ -861,40 +871,40 @@ void Client::SendCharInfo() { SetHasOwnerOrEditAccess(true); } } - + bool groupMentor = false; GetPlayer()->group_id = rejoin_group_id; - if(!world.RejoinGroup(this, rejoin_group_id)) + if (!world.RejoinGroup(this, rejoin_group_id)) GetPlayer()->group_id = 0; else { Entity* ent = world.GetGroupManager()->IsPlayerInGroup(rejoin_group_id, GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id); - if(ent && ent->IsPlayer()) + if (ent && ent->IsPlayer()) { GetPlayer()->SetMentorStats(ent->GetLevel(), ent->GetID(), false); groupMentor = true; } } - if(!groupMentor) + if (!groupMentor) GetPlayer()->SetMentorStats(GetPlayer()->GetLevel(), 0, false); - if(!GetPlayer()->IsReturningFromLD()) { + if (!GetPlayer()->IsReturningFromLD()) { database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_MAINTAINEDEFFECTS); database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_SPELLEFFECTS); } else { Spawn* pet_spawn = nullptr; - if(GetPlayer()->GetPet()) + if (GetPlayer()->GetPet()) pet_spawn = GetPlayer()->GetPet(); - else if(GetPlayer()->GetCharmedPet()) + else if (GetPlayer()->GetCharmedPet()) pet_spawn = GetPlayer()->GetCharmedPet(); - else if(GetPlayer()->GetCosmeticPet()) + else if (GetPlayer()->GetCosmeticPet()) pet_spawn = GetPlayer()->GetCosmeticPet(); - else if(GetPlayer()->GetDeityPet()) + else if (GetPlayer()->GetDeityPet()) pet_spawn = GetPlayer()->GetDeityPet(); - - if(pet_spawn) { + + if (pet_spawn) { GetPlayer()->GetInfoStruct()->set_pet_id(GetPlayer()->GetIDWithPlayerSpawn(pet_spawn)); } } @@ -922,15 +932,15 @@ void Client::SendCharPOVGhost() { PacketStruct* set_pov = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion()); int32 ghost_id = 0; if (set_pov) { - if(pov_ghost_spawn_id) { + if (pov_ghost_spawn_id) { Spawn* spawn = GetCurrentZone()->GetSpawnByID(pov_ghost_spawn_id); ghost_id = player->GetIDWithPlayerSpawn(spawn); - if(spawn) { + if (spawn) { use_ghost_pov = true; } } - if(use_ghost_pov) { - set_pov->setDataByName("spawn_id", ghost_id); + if (use_ghost_pov) { + set_pov->setDataByName("spawn_id", ghost_id); } else { set_pov->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player)); @@ -1036,7 +1046,7 @@ void Client::SendDefaultGroupOptions() { default_options->setDataByName("default_yell_method", GetPlayer()->GetInfoStruct()->get_group_default_yell()); default_options->setDataByName("group_autolock", GetPlayer()->GetInfoStruct()->get_group_autolock()); default_options->setDataByName("default_group_lock_method", GetPlayer()->GetInfoStruct()->get_group_lock_method()); - if(GetVersion() > 561) { + if (GetVersion() > 561) { default_options->setDataByName("solo_autolock", GetPlayer()->GetInfoStruct()->get_group_solo_autolock()); default_options->setDataByName("auto_loot_method", GetPlayer()->GetInfoStruct()->get_group_auto_loot_method()); } @@ -1085,7 +1095,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { PacketStruct* request; request = configReader.getStruct("LoginByNumRequest", 1); if (request) { - if(request->LoadPacketData(app->pBuffer, app->size)) { + if (request->LoadPacketData(app->pBuffer, app->size)) { // test the original location of Version for clients older than 1212 version = request->getType_int16_ByName("version"); @@ -1107,7 +1117,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version); ClientPacketFunctions::SendLoginDenied(this); - + /* reset version and protect server from trying to send packets out to a bad client ** cause of Dec 6th/Dec 7th 2023 crash ** Client::MakeSpawnChangePacket @@ -1136,6 +1146,9 @@ bool Client::HandlePacket(EQApplicationPacket* app) { packet->PrintPacket(); int8 loot_method = packet->getType_int8_ByName("loot_method"); int8 loot_items_rarity = packet->getType_int8_ByName("loot_items_rarity"); + if (GetVersion() <= 561) + loot_items_rarity = 0; + int8 auto_split_coin = packet->getType_int8_ByName("auto_split_coin"); int8 default_yell_method = packet->getType_int8_ByName("default_yell_method"); int8 autolock = packet->getType_int8_ByName("group_autolock"); @@ -1172,15 +1185,32 @@ bool Client::HandlePacket(EQApplicationPacket* app) { PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); if (group) { - GroupOptions goptions; - goptions.loot_method = loot_method; - goptions.loot_items_rarity = loot_items_rarity; - goptions.auto_split = auto_split_coin; - goptions.default_yell = default_yell_method; - goptions.group_autolock = autolock; - goptions.solo_autolock = solo_autolock; - goptions.auto_loot_method = auto_loot_method; - group->SetDefaultGroupOptions(&goptions); + bool isLeadGroup = group->IsInRaidGroup(group->GetID(), true); + bool isInRaid = group->IsInRaidGroup(group->GetID()); + if (!isInRaid || isLeadGroup) { + GroupOptions goptions; + goptions.loot_method = loot_method; + goptions.loot_items_rarity = loot_items_rarity; + goptions.auto_split = auto_split_coin; + goptions.default_yell = default_yell_method; + goptions.group_autolock = autolock; + goptions.solo_autolock = solo_autolock; + goptions.auto_loot_method = auto_loot_method; + group->SetDefaultGroupOptions(&goptions); + // update group options with peers + std::vector raidGroups; + group->GetRaidGroups(&raidGroups); + std::vector::iterator group_itr; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + PlayerGroup* otherGroup = world.GetGroupManager()->GetGroup((*group_itr)); + if (otherGroup) + otherGroup->SetDefaultGroupOptions(&goptions); + } + peer_manager.sendPeersNewGroupRequest("", 0, group->GetID(), "", "", &goptions, "", &raidGroups, true); + } + else { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Group options may only be changed by raid leader."); + } } world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); } @@ -1193,7 +1223,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapRequest", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_MapRequest", GetVersion()); if (packet && app->size > 2 && GetCurrentZone()) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { PacketStruct* fog_packet = configReader.getStruct("WS_FogInit", GetVersion()); if (fog_packet) { LogWrite(PACKET__DEBUG, 0, "Packet", "In OP_MapRequest: Fog Packet"); @@ -1267,7 +1297,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { response->getType_int8_ByName("camp_desktop"), response->getType_int8_ByName("camp_char_select"), (response->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) ? response->getType_EQ2_16BitString_ByName("char_name").data.c_str() : ""); - + // JA: trying to recognize /camp vs LD (see ZoneServer::ClientProcess()) if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); @@ -1334,7 +1364,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalSetVisibleMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_QuestJournalVisible", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int32 quest_id = packet->getType_int32_ByName("quest_id"); bool hidden = packet->getType_int8_ByName("visible") == 1 ? false : true; GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); @@ -1357,7 +1387,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MacroUpdateMsg", opcode, opcode); PacketStruct* macro_update = configReader.getStruct("WS_MacroUpdate", GetVersion()); if (macro_update) { - if(macro_update->LoadPacketData(app->pBuffer, app->size)) { + if (macro_update->LoadPacketData(app->pBuffer, app->size)) { vector* update = new vector; int8 number = macro_update->getType_int8_ByName("number"); int16 icon = macro_update->getType_int16_ByName("icon"); @@ -1388,13 +1418,13 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DialogSelectMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_DialogSelect", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { - int32 conversation_id = packet->getType_int32_ByName("conversation_id"); - int32 response_index = packet->getType_int32_ByName("response"); - HandleDialogSelectMsg(conversation_id, response_index); - } + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int32 conversation_id = packet->getType_int32_ByName("conversation_id"); + int32 response_index = packet->getType_int32_ByName("response"); + HandleDialogSelectMsg(conversation_id, response_index); } - safe_delete(packet); + } + safe_delete(packet); break; } case OP_CancelMoveObjectModeMsg: { @@ -1416,9 +1446,11 @@ bool Client::HandlePacket(EQApplicationPacket* app) { PacketStruct* place_object = configReader.getStruct("WS_PlaceMoveableObject", GetVersion()); if (place_object && place_object->LoadPacketData(app->pBuffer, app->size)) { Spawn* spawn = 0; - - if (GetTempPlacementSpawn()) + bool was_temp_placement = false; + if (GetTempPlacementSpawn()) { spawn = GetTempPlacementSpawn(); + was_temp_placement = true; + } else spawn = GetPlayer()->GetSpawnWithPlayerID(place_object->getType_int32_ByName("spawn_id")); @@ -1431,12 +1463,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) { SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); break; } - - + + int32 uniqueID = spawn->GetPickupUniqueItemID(); - if(uniqueID) { + if (uniqueID) { Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); - if(uniqueItem && uniqueItem->CheckFlag2(HOUSE_LORE) && GetCurrentZone()->HouseItemSpawnExists(uniqueItem->details.item_id)) { + if (uniqueItem && uniqueItem->CheckFlag2(HOUSE_LORE) && GetCurrentZone()->HouseItemSpawnExists(uniqueItem->details.item_id)) { Message(CHANNEL_COLOR_RED, "Item %s is house lore and you cannot place another.", uniqueItem->name.c_str()); break; } @@ -1502,10 +1534,14 @@ bool Client::HandlePacket(EQApplicationPacket* app) { spawn->SetSpawnOrigY(spawn->GetY()); spawn->SetSpawnOrigZ(spawn->GetZ()); spawn->SetSpawnOrigHeading(spawn->GetHeading()); - if (spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) - SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information."); - else if (spawn->GetSpawnLocationID() > 0) + if (spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) { + if (!was_temp_placement) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information."); + } + } + else if (spawn->GetSpawnLocationID() > 0) { SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn information, see console window for details."); + } } } @@ -1530,11 +1566,11 @@ bool Client::HandlePacket(EQApplicationPacket* app) { break; } case OP_DoneLoadingUIResourcesMsg: { - if(GetVersion() <= 561) { + if (GetVersion() <= 561) { ClientPacketFunctions::SendUpdateSpellBook(this); } - // need to quickly flash the DoF client the rest of their inventory - if(GetVersion() <= 561) { + // need to quickly flash the DoF client the rest of their inventory + if (GetVersion() <= 561) { EQ2Packet* item_app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion()); if (item_app) { QueuePacket(item_app); @@ -1543,7 +1579,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { EQ2Packet* app = new EQ2Packet(OP_DoneLoadingUIResourcesMsg, 0, 0); QueuePacket(app); - if(!player_loading_complete) + if (!player_loading_complete) { const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); if (zone_script && lua_interface) @@ -1565,7 +1601,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { case OP_DoneLoadingEntityResourcesMsg: { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingEntityResourcesMsg", opcode, opcode); if (!IsReadyForSpawns()) { - if(GetPlayer()->GetMap()) { + if (GetPlayer()->GetMap()) { auto loc = glm::vec3(GetPlayer()->GetX(), GetPlayer()->GetZ(), GetPlayer()->GetY()); uint32 GridID = 0; float new_z = GetPlayer()->FindBestZ(loc, nullptr, &GridID); @@ -1589,11 +1625,11 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoppedLootingMsg", opcode, opcode); if (app->size < sizeof(int32)) break; - + int32 loot_id = 0; memcpy(&loot_id, app->pBuffer, sizeof(int32)); Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); - if(spawn) { + if (spawn) { spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); spawn->SetLooterSpawnID(0); } @@ -1650,41 +1686,13 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } else { - if(zoning_destination) { + if (zoning_destination) { SetCurrentZone(zoning_destination); } LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyToZoneMsg", opcode, opcode); - bool succeed_override_zone = true; - if(!GetCurrentZone()) { - LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone and zone is not there! Emergency boot!", player->GetName()); - if(zoning_instance_id) { - ZoneServer* zone = zone_list.GetByInstanceID(zoning_instance_id, zoning_id, true); - if(!zone) { - LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver instance id %u zone id %u.", player->GetName(), zoning_instance_id, zoning_id); - succeed_override_zone = false; - } - else { - SetCurrentZone(zone); - LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique instance has been created for %s through emergency boot!", player->GetName()); - } - } - else if(zoning_id) { - - ZoneServer* zone = zone_list.Get(zoning_id, true, true); - if(!zone) { - LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver zone id %u.", player->GetName(), zoning_id); - succeed_override_zone = false; - } - else { - SetCurrentZone(zone); - LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique zone has been created for %s through emergency boot!", player->GetName()); - } - } - } - if (client_zoning) - LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s zoning to %s", player->GetName(), GetCurrentZone()->GetZoneName()); + LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s zoning to %u, instance id %u", player->GetName(), zoning_id, zoning_instance_id); else LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone without server authorization.", player->GetName()); Disconnect(); @@ -1740,7 +1748,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } case OP_SendLatestRequestMsg: { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SendLatestRequestMsg", opcode, opcode); - if(GetVersion() < 60085) { + if (GetVersion() < 60085) { // this does not exist in newer clients like AoM, confirmed to exist in DoF, other clients will need review at a later time uchar blah25[] = { 0x01 }; EQ2Packet* app25 = new EQ2Packet(OP_ClearDataMsg, blah25, sizeof(blah25)); @@ -1751,7 +1759,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { case OP_RequestRecipeDetailsMsg: { PacketStruct* packet = configReader.getStruct("WS_RequestRecipeDetail", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { vector recipes; int32 recipe_id = 0; char recipe_prop_name[30]; @@ -1761,13 +1769,13 @@ bool Client::HandlePacket(EQApplicationPacket* app) { memset(recipe_prop_name, 0, 30); snprintf(recipe_prop_name, 30, "recipe_id_%i", i); recipe_id = packet->getType_int32_ByName(recipe_prop_name); - if(recipe_id > 0) { + if (recipe_id > 0) { recipes.push_back(recipe_id); } } SendRecipeDetails(&recipes); } - + safe_delete(packet); } break; @@ -1808,14 +1816,14 @@ bool Client::HandlePacket(EQApplicationPacket* app) { //DumpPacket(app->pBuffer, app->size); PacketStruct* packet = configReader.getStruct("WS_BeginItemCreation", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { - Recipe* recipe = master_recipe_list.GetRecipe(GetPlayer()->GetCurrentRecipe()); - if(recipe) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + Recipe* recipe = master_recipe_list.GetRecipe(GetPlayer()->GetCurrentRecipe()); + if (recipe) { int32 item = 0; int8 qty = 0; vector> items; char tmp_item_id[30]; - if(GetVersion() > 1193) { + if (GetVersion() > 1193) { int8 num_primary_selected_items = packet->getType_int8_ByName("num_primary_selected_items"); for (int8 i = 0; i < num_primary_selected_items; i++) { memset(tmp_item_id, 0, 30); @@ -1824,26 +1832,26 @@ bool Client::HandlePacket(EQApplicationPacket* app) { sprintf(tmp_item_id, "primary_selected_item_qty_%i", i); qty = packet->getType_int16_ByName(tmp_item_id); if (item > 0) - items.push_back(make_pair(item,qty)); + items.push_back(make_pair(item, qty)); item = 0; } } else { - item = packet->getType_int32_ByName("primary_component_id"); - qty = 1; - if (item > 0) - items.push_back(make_pair(item,qty)); + item = packet->getType_int32_ByName("primary_component_id"); + qty = 1; + if (item > 0) + items.push_back(make_pair(item, qty)); } int8 build_components = packet->getType_int8_ByName("num_build_components"); - - if(GetVersion() > 1193) { + + if (GetVersion() > 1193) { for (int8 i = 0; i < build_components; i++) { memset(tmp_item_id, 0, 30); sprintf(tmp_item_id, "num_selected_items_%i", i); int8 num_selected_items = packet->getType_int8_ByName(tmp_item_id); for (int8 j = 0; j < num_selected_items; j++) { memset(tmp_item_id, 0, 30); - sprintf(tmp_item_id, "selected_id%i_%i", i,j); + sprintf(tmp_item_id, "selected_id%i_%i", i, j); item = packet->getType_int32_ByName(tmp_item_id); sprintf(tmp_item_id, "selected_qty%i_%i", i, j); qty = packet->getType_int16_ByName(tmp_item_id); @@ -1865,7 +1873,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { items.push_back(make_pair(item, qty)); } } - if(GetVersion() > 1193) { + if (GetVersion() > 1193) { int8 num_fuel_items = packet->getType_int8_ByName("num_fuel_items"); for (int8 i = 0; i < num_fuel_items; i++) { memset(tmp_item_id, 0, 30); @@ -1879,13 +1887,13 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } } else { - - item = packet->getType_int32_ByName("fuel_id"); - qty = packet->getType_int16_ByName("fuel_qty"); - if (item > 0) - items.push_back(make_pair(item, qty)); + + item = packet->getType_int32_ByName("fuel_id"); + qty = packet->getType_int16_ByName("fuel_qty"); + if (item > 0) + items.push_back(make_pair(item, qty)); } - + GetCurrentZone()->GetTradeskillMgr()->BeginCrafting(this, items); } else { @@ -1908,45 +1916,45 @@ bool Client::HandlePacket(EQApplicationPacket* app) { PacketStruct* packet = configReader.getStruct("WS_Signal", 1); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size) && player->GetZone()) { EQ2_16BitString str = packet->getType_EQ2_16BitString_ByName("signal"); if (strcmp(str.data.c_str(), "sys_client_avatar_ready") == 0) { - LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); - SetReloadingZone(false); - if(GetPlayer()->IsDeletedSpawn()) { - GetPlayer()->SetDeletedSpawn(false); - } - ResetZoningCoords(); - SetReadyForUpdates(); - GetPlayer()->SendSpawnChanges(true); - ProcessStateCommands(); - GetPlayer()->changed = true; - GetPlayer()->info_changed = true; - GetPlayer()->vis_changed = true; - player_pos_changed = true; - GetPlayer()->AddChangedZoneSpawn(); - ProcessZoneIgnoreWidgets(); - if (version <= 561) { - master_trait_list.ChooseNextTrait(this); - } - - const char* zone_script = world.GetZoneScript(GetPlayer()->GetZone()->GetZoneID()); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + SetReloadingZone(false); + if (GetPlayer()->IsDeletedSpawn()) { + GetPlayer()->SetDeletedSpawn(false); + } + ResetZoningCoords(); + SetReadyForUpdates(); + GetPlayer()->SendSpawnChanges(true); + ProcessStateCommands(); + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + player_pos_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + ProcessZoneIgnoreWidgets(); + if (version <= 561) { + master_trait_list.ChooseNextTrait(this); + } - if (zone_script && lua_interface) { - lua_interface->RunZoneScript(zone_script, "enter_location", GetPlayer()->GetZone(), GetPlayer(), GetPlayer()->GetLocation()); - } - - if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) - GetCurrentZone()->AddDamagedSpawn(GetPlayer()); - } - else { - LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str()); - } - const char* zone_script = world.GetZoneScript(player->GetZone()->GetZoneID()); - if (zone_script && lua_interface) - { - lua_interface->RunZoneScript(zone_script, "signal_changed", player->GetZone(), player, 0, str.data.c_str()); + const char* zone_script = world.GetZoneScript(GetPlayer()->GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetPlayer()->GetZone(), GetPlayer(), GetPlayer()->GetLocation()); } + + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) + GetCurrentZone()->AddDamagedSpawn(GetPlayer()); + } + else { + LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str()); + } + const char* zone_script = world.GetZoneScript(player->GetZone()->GetZoneID()); + if (zone_script && lua_interface) + { + lua_interface->RunZoneScript(zone_script, "signal_changed", player->GetZone(), player, 0, str.data.c_str()); + } } safe_delete(packet); } @@ -1961,10 +1969,10 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsVerbMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_EntityVerbsVerb", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int32 spawn_id = packet->getType_int32_ByName("spawn_id"); - player->SetTarget(player->GetSpawnWithPlayerID(spawn_id)); - Spawn* spawn = player->GetTarget(); + Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); // fixed using GetTarget and the target was never set causing commands not to work + player->SetTarget(spawn); if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) { string command = packet->getType_EQ2_16BitString_ByName("command").data; @@ -1985,11 +1993,11 @@ bool Client::HandlePacket(EQApplicationPacket* app) { else { EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command"); if (command.size > 0) { - string command_name = command.data; + string command_name = command.data; if (command_name.find(" ") < 0xFFFFFFFF) { if (GetVersion() <= 561) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant string::size_type pos = command_name.find(" "); - while(pos != string::npos){ + while (pos != string::npos) { command_name.replace(pos, 1, "_"); pos = command_name.find(" "); } @@ -2048,10 +2056,10 @@ bool Client::HandlePacket(EQApplicationPacket* app) { GetPlayer()->SetTarget(0); else { Spawn* spawn = GetPlayer()->GetSpawnByIndex(index); - if(spawn) + if (spawn) GetPlayer()->SetTarget(spawn); else { - LogWrite(PLAYER__ERROR, 1, "Player", "Player %s tried to target %u index, but that index was not valid.", GetPlayer()->GetName(), index); + LogWrite(PLAYER__ERROR, 1, "Player", "Player %s tried to target %u index, but that index was not valid.", GetPlayer()->GetName(), index); } } if (GetPlayer()->GetTarget()) @@ -2073,7 +2081,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } case OP_PredictionUpdateMsg: { LogWrite(OPCODE__DEBUG, 7, "Opcode", "Opcode 0x%X (%i): OP_PredictionUpdateMsg from %s", opcode, opcode, GetPlayer()->GetName()); - if (version <= 561) { + if (version <= 561 && GetPlayer() && GetPlayer()->GetZone()) { int8 offset = 9; if (app->pBuffer[0] == 0xFF) offset += 2; @@ -2091,7 +2099,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { else player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); player_pos_changed = true; - + GetPlayer()->changed = true; GetPlayer()->info_changed = true; GetPlayer()->vis_changed = true; @@ -2146,7 +2154,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { else player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); player_pos_changed = true; - + GetPlayer()->changed = true; GetPlayer()->info_changed = true; GetPlayer()->vis_changed = true; @@ -2170,7 +2178,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BeginTrackingMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_BeginTracking", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int32 spawn_id = packet->getType_int32_ByName("spawn_id"); Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); if (spawn) { @@ -2187,7 +2195,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BioUpdateMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { player->SetBiography(packet->getType_EQ2_16BitString_ByName("biography").data); } safe_delete(packet); @@ -2207,7 +2215,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { //DumpPacket(app); PacketStruct* packet = configReader.getStruct("WS_RewardPackMsg", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { string recruiter_name = packet->getType_EQ2_16BitString_ByName("recruiter_name").data; /* Player has contacted a guild recruiter */ @@ -2253,7 +2261,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { PacketStruct* packet = configReader.getStruct("WS_PetOptions", GetVersion()); if (packet && target && (target == player->GetPet() || target == player->GetCharmedPet() || target == player->GetDeityPet() || target == player->GetCosmeticPet())) { bool change = false; - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { string name = packet->getType_EQ2_16BitString_ByName("pet_name").data; if (strlen(name.c_str()) != 0 && SetPetName(name.c_str())) { target->SetName(name.c_str()); @@ -2326,9 +2334,9 @@ bool Client::HandlePacket(EQApplicationPacket* app) { int64 bank_money = GetPlayer()->GetBankCoinsPlat(); PacketStruct* packet = configReader.getStruct("WS_BuyHouse", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int64 house_id = 0; - if(GetVersion() <= 561) { + if (GetVersion() <= 561) { house_id = packet->getType_int32_ByName("house_id"); } else { @@ -2345,7 +2353,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { safe_delete(packet); break; } - if(disable_alignment_req && hz->alignment > 0 && hz->alignment != GetPlayer()->GetAlignment()) + if (disable_alignment_req && hz->alignment > 0 && hz->alignment != GetPlayer()->GetAlignment()) { std::string req = "You must be of "; if (hz->alignment == 1) @@ -2357,40 +2365,54 @@ bool Client::HandlePacket(EQApplicationPacket* app) { safe_delete(packet); break; } + ZoneChangeDetails zone_details; int32 status_req = hz->cost_status; int32 available_status = player->GetInfoStruct()->get_status_points(); - if (status_req <= available_status && (!hz->cost_coin || (hz->cost_coin && player->RemoveCoins(hz->cost_coin)))) + if (status_req <= available_status && (!hz->cost_coin || (hz->cost_coin && player->RemoveCoins(hz->cost_coin)))) { player->GetInfoStruct()->subtract_status_points(status_req); - ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); - int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days - int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); - world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); - //ClientPacketFunctions::SendHousingList(this); - PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); - ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); - PlaySound("coin_cha_ching"); - } - else if (status_req <= available_status && got_bank_money == 1) { - player->GetInfoStruct()->subtract_status_points(status_req); - bool bankwithdrawl = BankWithdrawalNoBanker(hz->cost_coin); - - //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. - if (bankwithdrawl == 0) { - PlaySound("buy_failed"); - SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); - safe_delete(packet); - break; - } - - ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); + if (zone_list.GetZoneByInstance(&zone_details, 0, hz->zone_id, true, true)) { int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days - int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); - world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, zone_details.instanceId, upkeep_due); + 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) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, zone_details.instanceId, 0, 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime, zoneID, zone_details.instanceType, zone_details.zoneName); + + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, zone_details.instanceId, upkeep_due, 0, 0, GetPlayer()->GetName()); + //ClientPacketFunctions::SendHousingList(this); PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); PlaySound("coin_cha_ching"); + } + } + else if (status_req <= available_status && got_bank_money == 1) { + player->GetInfoStruct()->subtract_status_points(status_req); + bool bankwithdrawl = BankWithdrawalNoBanker(hz->cost_coin); + + //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. + if (bankwithdrawl == 0) { + PlaySound("buy_failed"); + SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); + safe_delete(packet); + break; + } + + if (zone_list.GetZoneByInstance(&zone_details, 0, hz->zone_id, true, true)) { + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, zone_details.instanceId, upkeep_due); + + 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) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, zone_details.instanceId, 0, 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime, zoneID, zone_details.instanceType, zone_details.zoneName); + + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, zone_details.instanceId, upkeep_due, 0, 0, GetPlayer()->GetName()); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } } else { @@ -2409,22 +2431,21 @@ bool Client::HandlePacket(EQApplicationPacket* app) { //DumpPacket(app); PacketStruct* packet = configReader.getStruct("WS_EnterHouse", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { PlayerHouse* ph = nullptr; HouseZone* hz = nullptr; int64 house_id = 0; int32 spawn_index = 0; - - if(GetVersion() <= 561) { + + if (GetVersion() <= 561) { spawn_index = packet->getType_int32_ByName("house_id"); } else { house_id = packet->getType_int64_ByName("house_id"); } - - ZoneServer* house = GetHouseZoneServer(spawn_index, house_id); - if (house) { - Zone(house, true); + ZoneChangeDetails zone_details; + if (GetHouseZoneServer(&zone_details, spawn_index, house_id)) { + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, true); } } } @@ -2434,14 +2455,14 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } case OP_PayHouseUpkeepMsg: { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PayHouseUpkeepMsg", opcode, opcode); - + PacketStruct* packet = configReader.getStruct("WS_PayUpkeep", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int64 house_id = 0; - - if(GetVersion() <= 561) { + + if (GetVersion() <= 561) { house_id = packet->getType_int32_ByName("house_id"); } else { @@ -2474,7 +2495,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { bool escrowChange = false; int64 statusReq = hz->upkeep_status; int64 tmpRecoverStatus = 0; - if(ph->escrow_status && statusReq >= ph->escrow_status ) + if (ph->escrow_status && statusReq >= ph->escrow_status) { escrowChange = true; tmpRecoverStatus = ph->escrow_status; @@ -2508,12 +2529,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) { } int32 available_status_points = player->GetInfoStruct()->get_status_points(); - if(!statusReq || (statusReq && statusReq <= available_status_points)) + if (!statusReq || (statusReq && statusReq <= available_status_points)) { - if(coinReq && player->RemoveCoins(coinReq)) + if (coinReq && player->RemoveCoins(coinReq)) coinReq = 0; - - if(!coinReq && statusReq && player->GetInfoStruct()->subtract_status_points(statusReq)) + + if (!coinReq && statusReq && player->GetInfoStruct()->subtract_status_points(statusReq)) statusReq = 0; } bool got_bank_money = BankHasCoin(hz->upkeep_coin); @@ -2556,7 +2577,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) { // recover the escrow we were going to use but could not spend due to lack of funds if (tmpRecoverCoins) ph->escrow_coins += tmpRecoverCoins; - if(tmpRecoverStatus) + if (tmpRecoverStatus) ph->escrow_status += tmpRecoverStatus; SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money or status to pay for upkeep."); @@ -2579,19 +2600,27 @@ bool Client::HandlePacket(EQApplicationPacket* app) { if (ph) { HouseZone* hz = world.GetHouseZone(ph->house_id); if (hz) { - ZoneServer* new_zone = zone_list.Get(hz->exit_zone_id); - // determine if this is an instanced zone that already exists - ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(hz->exit_zone_id); - if (instance_zone || new_zone) { + ZoneChangeDetails zone_details; + bool foundZone = world.GetGroupManager()->IdentifyMemberInGroupOrRaid(&zone_details, this, hz->exit_zone_id); + if (foundZone) { GetPlayer()->SetX(hz->exit_x); GetPlayer()->SetY(hz->exit_y); GetPlayer()->SetZ(hz->exit_z); GetPlayer()->SetHeading(hz->exit_heading); - if (instance_zone) - Zone(instance_zone->GetInstanceID(), false, true); - else - Zone(new_zone, false); + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false, true); + } + else { + if (zone_list.GetZone(&zone_details, hz->exit_zone_id)) { + GetPlayer()->SetX(hz->exit_x); + GetPlayer()->SetY(hz->exit_y); + GetPlayer()->SetZ(hz->exit_z); + GetPlayer()->SetHeading(hz->exit_heading); + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Failed to OP_ExitHouseMsg(exit_zone_id = %u) for Player %s.", hz->exit_zone_id, GetPlayer()->GetName()); + } } } } @@ -2603,16 +2632,16 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalWaypointMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_QuestJournalWaypoint", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { - if(GetVersion() <= 561) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + if (GetVersion() <= 561) { int32 quest_id = packet->getType_int32_ByName("quest_id"); GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); if (player->player_quests.count(quest_id) > 0 && player->player_quests[quest_id]) { - if(player->player_quests[quest_id]->GetTracked()) + if (player->player_quests[quest_id]->GetTracked()) player->player_quests[quest_id]->SetTracked(false); else player->player_quests[quest_id]->SetTracked(true); - + player->player_quests[quest_id]->SetSaveNeeded(true); } GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); @@ -2649,84 +2678,84 @@ bool Client::HandlePacket(EQApplicationPacket* app) { break; } case OP_PaperdollImage: { -/* PacketStruct* packet = configReader.getStruct("WS_PaperdollImage", version); - if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + /* PacketStruct* packet = configReader.getStruct("WS_PaperdollImage", version); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { - //First check if this is a new image... delete an existing partial image if one exists - int8 packet_index = packet->getType_int8_ByName("packetIndex"); - if (packet_index == 0) { - safe_delete_array(incoming_paperdoll.image_bytes); - incoming_paperdoll.last_received_packet_index = 0; - incoming_paperdoll.current_size_bytes = 0; - } - //return if this packet is not the one we are expecting... - else if (packet_index != incoming_paperdoll.last_received_packet_index + 1) { + //First check if this is a new image... delete an existing partial image if one exists + int8 packet_index = packet->getType_int8_ByName("packetIndex"); + if (packet_index == 0) { + safe_delete_array(incoming_paperdoll.image_bytes); + incoming_paperdoll.last_received_packet_index = 0; + incoming_paperdoll.current_size_bytes = 0; + } + //return if this packet is not the one we are expecting... + else if (packet_index != incoming_paperdoll.last_received_packet_index + 1) { + safe_delete(packet); + break; + } + + //Check how many packets we're supposed to be receiving for this/these images + incoming_paperdoll.image_num_packets = packet->getType_int8_ByName("totalNumPackets"); + + //Check the image type, if this is a new type in the same series of packets we have a new image + int8 img_type = packet->getType_int8_ByName("image_type"); + if (packet_index != 0 && img_type != incoming_paperdoll.image_type) { + //We have a new image. Save the old data and clear before continuing + SavePlayerImages(); + } + incoming_paperdoll.image_type = img_type; + + //Get the size of the image data in this packet + sint64 image_size = packet->getType_int32_ByName("imageSize"); + if (image_size <= 0 || image_size > 1048576) { + //If this packet is saying that the array is size <= 0 or > 1 MiB return out... it shouldn't be those sizes ever + safe_delete(packet); + break; + } + + //Create a new array + int32 new_image_size = image_size; + uchar* new_image = new uchar[incoming_paperdoll.current_size_bytes + new_image_size]; + if (incoming_paperdoll.image_bytes) { + memcpy(new_image, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + safe_delete_array(incoming_paperdoll.image_bytes); + } + + //variable i should be the index in the packet of the first PNG file byte + vector* d_structs = packet->getStructs(); + vector::iterator itr; + int32 i = 0; + for (itr = d_structs->begin(); itr != d_structs->end(); itr++) { + DataStruct* ds = (*itr); + if (strcmp(ds->GetName(), "pngData_0") != 0) + i += ds->GetDataSizeInBytes(); + else + break; + } + + //Return if this packet is bad and we would read out of bounds + if (app->size - i < new_image_size) { + safe_delete(packet); + safe_delete_array(new_image); + break; + } + + uchar* tmp = new_image + incoming_paperdoll.current_size_bytes; + memcpy(tmp, app->pBuffer + i, new_image_size); + + incoming_paperdoll.current_size_bytes += new_image_size; + incoming_paperdoll.image_bytes = new_image; + + //Check if this is the last packet we're expecting for this image. Create a final image if so + if (incoming_paperdoll.image_num_packets == 1 || + incoming_paperdoll.last_received_packet_index + 2 == incoming_paperdoll.image_num_packets) { + SavePlayerImages(); + } + + incoming_paperdoll.last_received_packet_index = packet_index; + } safe_delete(packet); - break; - } - - //Check how many packets we're supposed to be receiving for this/these images - incoming_paperdoll.image_num_packets = packet->getType_int8_ByName("totalNumPackets"); - - //Check the image type, if this is a new type in the same series of packets we have a new image - int8 img_type = packet->getType_int8_ByName("image_type"); - if (packet_index != 0 && img_type != incoming_paperdoll.image_type) { - //We have a new image. Save the old data and clear before continuing - SavePlayerImages(); - } - incoming_paperdoll.image_type = img_type; - - //Get the size of the image data in this packet - sint64 image_size = packet->getType_int32_ByName("imageSize"); - if (image_size <= 0 || image_size > 1048576) { - //If this packet is saying that the array is size <= 0 or > 1 MiB return out... it shouldn't be those sizes ever - safe_delete(packet); - break; - } - - //Create a new array - int32 new_image_size = image_size; - uchar* new_image = new uchar[incoming_paperdoll.current_size_bytes + new_image_size]; - if (incoming_paperdoll.image_bytes) { - memcpy(new_image, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); - safe_delete_array(incoming_paperdoll.image_bytes); - } - - //variable i should be the index in the packet of the first PNG file byte - vector* d_structs = packet->getStructs(); - vector::iterator itr; - int32 i = 0; - for (itr = d_structs->begin(); itr != d_structs->end(); itr++) { - DataStruct* ds = (*itr); - if (strcmp(ds->GetName(), "pngData_0") != 0) - i += ds->GetDataSizeInBytes(); - else - break; - } - - //Return if this packet is bad and we would read out of bounds - if (app->size - i < new_image_size) { - safe_delete(packet); - safe_delete_array(new_image); - break; - } - - uchar* tmp = new_image + incoming_paperdoll.current_size_bytes; - memcpy(tmp, app->pBuffer + i, new_image_size); - - incoming_paperdoll.current_size_bytes += new_image_size; - incoming_paperdoll.image_bytes = new_image; - - //Check if this is the last packet we're expecting for this image. Create a final image if so - if (incoming_paperdoll.image_num_packets == 1 || - incoming_paperdoll.last_received_packet_index + 2 == incoming_paperdoll.image_num_packets) { - SavePlayerImages(); - } - - incoming_paperdoll.last_received_packet_index = packet_index; - } - safe_delete(packet); -*/ + */ break; } @@ -2784,8 +2813,8 @@ bool Client::HandlePacket(EQApplicationPacket* app) { current_zone->SendSpawnChanges(player->custNPCTarget); } else {*/ - player->CustomizeAppearance(packet); - current_zone->SendSpawnChanges(player); + player->CustomizeAppearance(packet); + current_zone->SendSpawnChanges(player); //} } } @@ -2845,14 +2874,24 @@ bool Client::HandleLootItem(Spawn* entity, Item* item, Spawn* target, bool overr // needs to only be checked before expiration of loot restrictions if (entity && !overrideLootRestrictions) { - if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID())) { + std::vector raidGroups; + if (lootingPlayer && lootingPlayer->GetGroupMemberInfo()) + world.GetGroupManager()->GetRaidGroups(lootingPlayer->GetGroupMemberInfo()->group_id, &raidGroups); + + if (entity->GetLootGroupID() > 0 && std::find(raidGroups.begin(), raidGroups.end(), entity->GetLootGroupID()) == raidGroups.end() && (!lootingPlayer->GetGroupMemberInfo() || lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID())) { LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Group ID from %s did not match Item: %s (%u), expected group id %u.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id, entity->GetLootGroupID()); return false; } if (entity->GetLootMethod() != GroupLootMethod::METHOD_FFA) { switch (entity->GetLootMethod()) { case GroupLootMethod::METHOD_LEADER: { - if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || (lootingPlayer->GetGroupMemberInfo() && (lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID() || !lootingPlayer->GetGroupMemberInfo()->leader)))) { + bool inRaid = false; + bool isLeaderRaid = false; + if (GetPlayer()->GetGroupMemberInfo()) { + inRaid = world.GetGroupManager()->IsInRaidGroup(GetPlayer()->GetGroupMemberInfo()->group_id, GetPlayer()->GetGroupMemberInfo()->group_id, false); + isLeaderRaid = world.GetGroupManager()->IsInRaidGroup(GetPlayer()->GetGroupMemberInfo()->group_id, GetPlayer()->GetGroupMemberInfo()->group_id, true); + } + if (entity->GetLootGroupID() > 0 && (!GetPlayer()->GetGroupMemberInfo() || !lootingPlayer->GetGroupMemberInfo() || (inRaid && !isLeaderRaid) || ((std::find(raidGroups.begin(), raidGroups.end(), GetPlayer()->GetGroupMemberInfo()->group_id) == raidGroups.end()) && lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID()) || !GetPlayer()->GetGroupMemberInfo()->leader)) { LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Attempt from %s was not allowed with Item: %s (%u), must be group leader.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id); return false; } @@ -2984,7 +3023,7 @@ void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { int32 target_id = packet->getType_int32_ByName("target_id"); int8 button_clicked = packet->getType_int8_ByName("button_clicked"); Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); - if(!spawn) { + if (!spawn) { safe_delete(packet); return; } @@ -3007,7 +3046,7 @@ void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { if (outapp) QueuePacket(outapp); } - if(master_item->details.item_id == item_id) { + if (master_item->details.item_id == item_id) { break; } } @@ -3017,7 +3056,7 @@ void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { break; } } - if(((loot_all && !item_id) || (loot_all && item_id && items->size() == 1)) || items->size() < 1) { + if (((loot_all && !item_id) || (loot_all && item_id && items->size() == 1)) || items->size() < 1) { CloseLoot(loot_id); } else { @@ -3068,7 +3107,10 @@ void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { Spawn* target = this->GetPlayer(); if (target_id != 0xFFFFFFFF && GetPlayer()->GetGroupMemberInfo()) { Spawn* destTarget = GetPlayer()->GetSpawnWithPlayerID(target_id); - if (destTarget && (!destTarget->IsPlayer() || !world.GetGroupManager()->IsInGroup(GetPlayer()->GetGroupMemberInfo()->group_id, ((Player*)destTarget)))) { + std::vector raidGroups; + if (destTarget && destTarget->IsEntity() && ((Entity*)destTarget)->GetGroupMemberInfo()) + world.GetGroupManager()->GetRaidGroups(((Entity*)destTarget)->GetGroupMemberInfo()->group_id, &raidGroups); + if (destTarget && (!destTarget->IsPlayer() || (std::find(raidGroups.begin(), raidGroups.end(), GetPlayer()->GetGroupMemberInfo()->group_id) == raidGroups.end() && !world.GetGroupManager()->IsInGroup(GetPlayer()->GetGroupMemberInfo()->group_id, ((Player*)destTarget))))) { SimpleMessage(CHANNEL_COMMAND_TEXT, "HACKS!!"); safe_delete(packet); spawn->UnlockLoot(); @@ -3163,7 +3205,7 @@ void Client::HandleSkillInfoRequest(EQApplicationPacket* app) { case 0: { //items request = configReader.getStruct("WS_SkillInfoItemRequest", GetVersion()); if (request) { - if(request->LoadPacketData(app->pBuffer, app->size)) { + if (request->LoadPacketData(app->pBuffer, app->size)) { Item* item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(request->getType_int32_ByName("unique_id")); if (!item) item = GetPlayer()->item_list.GetItemFromUniqueID(request->getType_int32_ByName("unique_id"), true); @@ -3187,7 +3229,7 @@ void Client::HandleSkillInfoRequest(EQApplicationPacket* app) { case 2: {//spells request = configReader.getStruct("WS_SkillInfoSpellRequest", GetVersion()); if (request) { - if(request->LoadPacketData(app->pBuffer, app->size)) { + if (request->LoadPacketData(app->pBuffer, app->size)) { int32 id = request->getType_int32_ByName("id"); int8 tier = request->getType_int32_ByName("unique_id"); //on live this is really unique_id, but I'm going to make it tier instead :) Spell* spell = master_spell_list.GetSpell(id, tier); @@ -3236,21 +3278,21 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { if (type == 3) { Spell* spell = 0; bool trait_display; - + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); if (!request) { return; } - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } - + int32 id = request->getType_int32_ByName("id"); int32 tier = request->getType_int32_ByName("tier"); int32 trait_tier = request->getType_int32_ByName("unknown_id"); - - if(GetVersion() > 373 && GetVersion() <= 561) { + + if (GetVersion() > 373 && GetVersion() <= 561) { trait_tier = request->getType_int32_ByName("unique_id"); } bool display = true; @@ -3260,8 +3302,8 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { display = request->getType_int8_ByName("display"); else if (version > 561) display = false; // clients default is false otherwise it pops up a window when hovering over the knowledge book abilities - - LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)", GetPlayer()->GetName(), type, tier, trait_tier, id); if (trait_tier != 0xFFFFFFFF) { spell = master_spell_list.GetSpell(id, trait_tier); @@ -3284,13 +3326,13 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { spell = tmpSpell->spell; lua_interface->FindCustomSpellUnlock(); } - - if(!spell) { // fix ui timeout for classic, isle of refuge, dof, kos clients + + if (!spell) { // fix ui timeout for classic, isle of refuge, dof, kos clients int8 playerTier = GetPlayer()->GetSpellTier(id); spell = master_spell_list.GetSpell(id, playerTier); - LogWrite(CCLIENT__WARNING, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to find tier 1 spell. Last resort try to get the spell from the player book, spell %u, tier %u", GetPlayer()->GetName(),id,playerTier); + LogWrite(CCLIENT__WARNING, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to find tier 1 spell. Last resort try to get the spell from the player book, spell %u, tier %u", GetPlayer()->GetName(), id, playerTier); } - + if (spell && !CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { if (!spell->IsCopiedSpell()) SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); @@ -3299,13 +3341,13 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { //DumpPacket(app); QueuePacket(app); } - else if(spell && GetVersion() <=561 && CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { + else if (spell && GetVersion() <= 561 && CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { EQ2Packet* app = spell->SerializeSpell(this, display, trait_display, GetVersion() <= 561 ? true : false); //DumpPacket(app); QueuePacket(app); } else { - LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to successfully send Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to successfully send Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)", GetPlayer()->GetName(), type, tier, trait_tier, id); } } else if (type == 0) { @@ -3313,7 +3355,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { if (!request) { return; } - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } @@ -3350,7 +3392,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { sent_item_details[id] = true; MItemDetails.releasewritelock(__FUNCTION__, __LINE__); EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer()); - + QueuePacket(app); if (wasSpawn) delete item; @@ -3365,7 +3407,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { if (!request) { return; } - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } @@ -3397,13 +3439,13 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { return; } - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } int32 id = request->getType_int32_ByName("item_id"); - LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)", GetPlayer()->GetName(), type, id); //int32 unknown_0 = request->getType_int32_ByName("unknown",0); //int32 unknown_1 = request->getType_int32_ByName("unknown",1); //int8 unknown2 = request->getType_int8_ByName("unknown2"); @@ -3414,7 +3456,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { if (item) { //only display popup for non merchant links EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 561 ? true : false); - + QueuePacket(app); } else { @@ -3427,7 +3469,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { if (!request) { return; } - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } @@ -3436,7 +3478,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { SpellEffects* effect = player->GetSpellEffect(id); //printf("Type: (%i) Unknown5: (%i) Item ID: (%u)\n",type,unknown5,id); if (effect) { - LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)", GetPlayer()->GetName(), type, id); int8 tier = effect->tier; Spell* spell = master_spell_list.GetSpell(id, tier); @@ -3463,23 +3505,23 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { } } else { - LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Cannot Find Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Cannot Find Type: (%i) Item ID: (%u)", GetPlayer()->GetName(), type, id); } } else if (type == 5) { // recipe info - request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); if (!request) return; - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } - + int32 id = 0; - if(GetVersion() < 546) { + if (GetVersion() < 546) { id = request->getType_int32_ByName("id"); } - else if(GetVersion() <= 561) { + else if (GetVersion() <= 561) { id = request->getType_int32_ByName("unique_id"); } else { @@ -3500,7 +3542,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); if (!request) return; - if(!request->LoadPacketData(app->pBuffer, app->size)) { + if (!request->LoadPacketData(app->pBuffer, app->size)) { safe_delete(request); return; } @@ -3535,7 +3577,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); // EQ2Packet* app = spell->SerializeAASpell(this,tier, data, false, GetItemPacketType(GetVersion()), 0x04); LogWrite(WORLD__INFO, 0, "WORLD", "Examine Info Request-> Spell ID: %u", spell->GetSpellID()); - if(GetVersion() > 561) { + if (GetVersion() > 561) { EQ2Packet* app = master_spell_list.GetAASpellPacket(id, tier, this, false, 0x4F);//0x45 change version to match client /////////////////////////////////////////GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) { //DumpPacket(app); @@ -3554,7 +3596,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { void Client::HandleQuickbarUpdateRequest(EQApplicationPacket* app) { PacketStruct* request = configReader.getStruct("WS_QuickBarUpdateRequest", GetVersion()); if (request) { - if(request->LoadPacketData(app->pBuffer, app->size)) { + if (request->LoadPacketData(app->pBuffer, app->size)) { int32 id = request->getType_int32_ByName("id"); int32 bar = request->getType_int32_ByName("hotbar_number"); int32 slot = request->getType_int32_ByName("hotkey_slot"); @@ -3579,18 +3621,18 @@ void Client::HandleQuickbarUpdateRequest(EQApplicationPacket* app) { } bool Client::Process(bool zone_process) { - + bool ret = true; // EQS can become null if player is linkdead, we want to always be able to process the camp/linkdead timers when active if ((camp_timer && camp_timer->Check()) || (linkdead_timer && linkdead_timer->Check())) { ResetSendMail(); - if(getConnection()) + if (getConnection()) getConnection()->SendDisconnect(false); safe_delete(camp_timer); - if(linkdead_timer) { + if (linkdead_timer) { LogWrite(CCLIENT__DEBUG, 0, "Client", "Player %s triggered linkdead timer, disconnecting", GetPlayer()->GetName()); // we remove the linkdead status and force a camp out immediately - if((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + if ((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { GetPlayer()->SetActivityStatus(GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); } if ((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) { @@ -3600,7 +3642,13 @@ bool Client::Process(bool zone_process) { safe_delete(linkdead_timer); ret = false; } - + if (client_zoning_details_set) { + if (!zoning_details.zoningPastAuth && (zoning_details.authDispatchedTime + 5) <= Timer::GetUnixTimeStamp()) { + zoning_details.zoningPastAuth = true; // don't repeat + world.ClientAuthApproval(0, std::string(player->GetName()), GetAccountID(), zoning_details.zoneName, zoning_details.zoneId, zoning_details.instanceId, false); + } + } + if (!eqs) { return false; } @@ -3608,70 +3656,70 @@ bool Client::Process(bool zone_process) { return true; } - switch(new_client_login) { - case NewLoginState::LOGIN_SEND: { - LogWrite(CCLIENT__DEBUG, 0, "Client", "SendLoginInfo to new client..."); - SendLoginInfo(); - - new_client_login = NewLoginState::LOGIN_NONE; - break; - } - case NewLoginState::LOGIN_INITIAL_LOAD: { - bool isDBActive = database.IsActiveQuery(GetCharacterID()); - - // wait for starting skills/spells to load and reload from DB. - if(!isDBActive) { - if (GetPlayer()->GetInfoStruct()->get_reload_player_spells()) { - database.LoadCharacterSpells(GetCharacterID(), GetPlayer()); - GetPlayer()->GetInfoStruct()->set_reload_player_spells(0); - } - new_client_login = NewLoginState::LOGIN_SEND; - } - break; - } - case NewLoginState::LOGIN_ALLOWED: { - int32 count = 0; + switch (new_client_login) { + case NewLoginState::LOGIN_SEND: { + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendLoginInfo to new client..."); + SendLoginInfo(); - if(!GetPlayer()->IsReturningFromLD()) - { - LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName()); - count = database.LoadCharacterSkills(GetCharacterID(), player); - - LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName()); - count = database.LoadCharacterSpells(GetCharacterID(), player); - } - else - { - LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName()); - } - - // get the latest character starting skills / spells, may have been updated after character creation - if(GetPlayer()->GetInfoStruct()->get_first_world_login()) { - world.SyncCharacterAbilities(this); - Query query; - query.AddQueryAsync(GetCharacterID(), &database, Q_UPDATE, "UPDATE characters set first_world_login = 0 where id=%u", GetCharacterID()); - GetPlayer()->GetInfoStruct()->set_first_world_login(0); - } - - new_client_login = NewLoginState::LOGIN_INITIAL_LOAD; - break; - } - case NewLoginState::LOGIN_DELAYED: { - if(!delay_msg_timer.Enabled() || delay_msg_timer.Check()) { - LogWrite(CCLIENT__INFO, 0, "Client", "Wait for zone %s to load for new client %s...", GetCurrentZone()->GetZoneName(), GetPlayer()->GetName()); - delay_msg_timer.Start(1000, true); - } - if(!GetCurrentZone()->IsLoading()) { - new_client_login = NewLoginState::LOGIN_ALLOWED; - } - - return true; - break; - } + new_client_login = NewLoginState::LOGIN_NONE; + break; } - + case NewLoginState::LOGIN_INITIAL_LOAD: { + bool isDBActive = database.IsActiveQuery(GetCharacterID()); + + // wait for starting skills/spells to load and reload from DB. + if (!isDBActive) { + if (GetPlayer()->GetInfoStruct()->get_reload_player_spells()) { + database.LoadCharacterSpells(GetCharacterID(), GetPlayer()); + GetPlayer()->GetInfoStruct()->set_reload_player_spells(0); + } + new_client_login = NewLoginState::LOGIN_SEND; + } + break; + } + case NewLoginState::LOGIN_ALLOWED: { + int32 count = 0; + + if (!GetPlayer()->IsReturningFromLD()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName()); + count = database.LoadCharacterSkills(GetCharacterID(), player); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName()); + count = database.LoadCharacterSpells(GetCharacterID(), player); + } + else + { + LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName()); + } + + // get the latest character starting skills / spells, may have been updated after character creation + if (GetPlayer()->GetInfoStruct()->get_first_world_login()) { + world.SyncCharacterAbilities(this); + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_UPDATE, "UPDATE characters set first_world_login = 0 where id=%u", GetCharacterID()); + GetPlayer()->GetInfoStruct()->set_first_world_login(0); + } + + new_client_login = NewLoginState::LOGIN_INITIAL_LOAD; + break; + } + case NewLoginState::LOGIN_DELAYED: { + if (!delay_msg_timer.Enabled() || delay_msg_timer.Check()) { + LogWrite(CCLIENT__INFO, 0, "Client", "Wait for zone %s to load for new client %s...", GetCurrentZone()->GetZoneName(), GetPlayer()->GetName()); + delay_msg_timer.Start(1000, true); + } + if (!GetCurrentZone()->IsLoading()) { + new_client_login = NewLoginState::LOGIN_ALLOWED; + } + + return true; + break; + } + } + delay_msg_timer.Disable(); - + sockaddr_in to; memset((char*)&to, 0, sizeof(to)); @@ -3706,7 +3754,7 @@ bool Client::Process(bool zone_process) { should_load_spells = false; } - if(spawn_removal_timer.Check() && GetPlayer()) { + if (spawn_removal_timer.Check() && GetPlayer()) { GetPlayer()->ProcessSpawnRangeUpdates(); GetPlayer()->CheckSpawnStateQueue(); } @@ -3722,7 +3770,7 @@ bool Client::Process(bool zone_process) { ProcessQuestUpdates(); } int32 queue_timer_delay = rule_manager.GetZoneRule(GetCurrentZoneID(), R_Client, QuestQueueTimer)->GetInt32(); - if(queue_timer_delay < 10) { + if (queue_timer_delay < 10) { queue_timer_delay = 10; } if (last_update_time > 0 && last_update_time < (Timer::GetCurrentTime2() - queue_timer_delay)) { @@ -3731,7 +3779,7 @@ bool Client::Process(bool zone_process) { } MSaveSpellStateMutex.lock(); - if(save_spell_state_timer.Check()) + if (save_spell_state_timer.Check()) { save_spell_state_timer.Disable(); GetPlayer()->SaveSpellEffects(); @@ -3739,70 +3787,75 @@ bool Client::Process(bool zone_process) { MSaveSpellStateMutex.unlock(); if (temp_placement_timer.Check()) { - if(GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) { - SendMoveObjectMode(GetTempPlacementSpawn(), 0); + if (GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) { + int8 placement = 0; + int32 uniqueID = GetPlacementUniqueItemID(); + Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + if (uniqueItem && uniqueItem->houseitem_info) + placement = uniqueItem->houseitem_info->house_location; + SendMoveObjectMode(GetTempPlacementSpawn(), placement); hasSentTempPlacementSpawn = true; temp_placement_timer.Disable(); } } - if (pos_update.Check()) + if (GetCurrentZone() && pos_update.Check()) { ProcessStateCommands(); - + GetPlayer()->ResetMentorship(); // check if we need to asynchronously reset mentorship - if(GetPlayer()->GetRegionMap()) + if (GetPlayer()->GetRegionMap()) GetPlayer()->GetRegionMap()->TicRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); - - if(!player_pos_changed && IsReadyForUpdates() && player_pos_timer < Timer::GetCurrentTime2() && enabled_player_pos_timer) { - if(version > 373) { - GetPlayer()->info_changed = true; - GetPlayer()->vis_changed = true; - GetPlayer()->position_changed = true; - GetPlayer()->changed = true; - GetPlayer()->AddChangedZoneSpawn(); + + if (!player_pos_changed && IsReadyForUpdates() && player_pos_timer < Timer::GetCurrentTime2() && enabled_player_pos_timer) { + if (version > 373) { + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->position_changed = true; + GetPlayer()->changed = true; + GetPlayer()->AddChangedZoneSpawn(); } - player_pos_timer = Timer::GetCurrentTime2()+5000; + player_pos_timer = Timer::GetCurrentTime2() + 5000; enabled_player_pos_timer = false; } - if(player_pos_changed && IsReadyForUpdates()) { - player_pos_timer = Timer::GetCurrentTime2()+500; + if (player_pos_changed && IsReadyForUpdates()) { + player_pos_timer = Timer::GetCurrentTime2() + 500; enabled_player_pos_timer = true; - if(!underworld_cooldown_timer.Enabled() || (underworld_cooldown_timer.Enabled() && underworld_cooldown_timer.Check())) { + if (!underworld_cooldown_timer.Enabled() || (underworld_cooldown_timer.Enabled() && underworld_cooldown_timer.Check())) { bool underworld = false; - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, UseMapUnderworldCoords)->GetBool()) { - if(GetCurrentZone()->GetUnderWorld() != -1000000.0f) { - if(GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, UseMapUnderworldCoords)->GetBool()) { + if (GetCurrentZone() && GetCurrentZone()->GetUnderWorld() != -1000000.0f) { + if (GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) underworld = true; } - else if(GetPlayer()->GetMap() && GetPlayer()->GetMap()->GetMinY() != 9999999.0f && GetPlayer()->GetY() < (GetPlayer()->GetMap()->GetMinY() + rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, MapUnderworldCoordOffset)->GetFloat())) { + else if (GetPlayer()->GetMap() && GetPlayer()->GetMap()->GetMinY() != 9999999.0f && GetPlayer()->GetY() < (GetPlayer()->GetMap()->GetMinY() + rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, MapUnderworldCoordOffset)->GetFloat())) { underworld = true; } } - else if(GetPlayer()->GetMap() && GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) { - underworld = true; + else if (GetPlayer()->GetMap() && GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) { + underworld = true; } - if(underworld) { - player->SetX(GetCurrentZone()->GetSafeX()); - player->SetY(GetCurrentZone()->GetSafeY()); - player->SetZ(GetCurrentZone()->GetSafeZ()); - player->SetHeading(GetCurrentZone()->GetSafeHeading()); - EQ2Packet* app = GetPlayer()->Move(player->GetX(), player->GetY(), player->GetZ(), GetVersion(), player->GetHeading()); - if(app){ - QueuePacket(app); - } - SimpleMessage(CHANNEL_COLOR_RED, "You have been teleported to a safe location in the zone, because you appeared to have fallen through the world."); + if (underworld && GetCurrentZone()) { + player->SetX(GetCurrentZone()->GetSafeX()); + player->SetY(GetCurrentZone()->GetSafeY()); + player->SetZ(GetCurrentZone()->GetSafeZ()); + player->SetHeading(GetCurrentZone()->GetSafeHeading()); + EQ2Packet* app = GetPlayer()->Move(player->GetX(), player->GetY(), player->GetZ(), GetVersion(), player->GetHeading()); + if (app) { + QueuePacket(app); } + SimpleMessage(CHANNEL_COLOR_RED, "You have been teleported to a safe location in the zone, because you appeared to have fallen through the world."); + } underworld_cooldown_timer.Start(); } //GetPlayer()->CalculateLocation(); client_list.CheckPlayersInvisStatus(this); - + player_pos_changed = false; - + GetCurrentZone()->CheckTransporters(this); - - if(GetPlayer()->GetRegionMap()) + + if (GetPlayer()->GetRegionMap()) { GetPlayer()->GetRegionMap()->MapRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); } @@ -4084,9 +4137,9 @@ void ClientList::Remove(Client* client, bool remove_data) { } -void Client::SetCurrentZone(ZoneServer* zone) { +void Client::SetCurrentZone(ZoneServer* zone) { current_zone = zone; - if(player) { + if (player) { player->SetZone(zone, GetVersion()); } } @@ -4096,8 +4149,10 @@ void Client::SetCurrentZone(int32 id) { //current_zone->GetCombat()->RemoveHate(player); current_zone->RemoveSpawn(player, false, true, true, true, true); } - SetCurrentZone(zone_list.Get(id)); - + ZoneChangeDetails zone_details; + if (zone_list.GetZone(&zone_details, id, "", true, false, true, false)) { + SetCurrentZone((ZoneServer*)zone_details.zonePtr); + } } void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) { @@ -4105,8 +4160,13 @@ void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) { //current_zone->GetCombat()->RemoveHate(player); current_zone->RemoveSpawn(player, false, true, true, true, true); } - SetCurrentZone(zone_list.GetByInstanceID(id, zoneid)); - + ZoneChangeDetails zone_details; + if (zone_list.GetZoneByInstance(&zone_details, id, zoneid, true, false, true, false)) { + SetCurrentZone((ZoneServer*)zone_details.zonePtr); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Failed to Client::SetCurrentZoneByInstanceID(id = %u, zoneid = %u) for Player %s.", id, zoneid, GetPlayer()->GetName()); + } } ZoneServer* Client::GetCurrentZone() { @@ -4127,277 +4187,277 @@ int8 Client::GetMessageChannelColor(int8 channel_type) { return CHANNEL_COLOR_NEWEST_LOOT; } if (GetVersion() <= 283) { - if (channel_type <=12) + if (channel_type <= 12) return channel_type; switch (channel_type) { - case CHANNEL_GROUP_CHAT: - case CHANNEL_GROUP_SAY: - case CHANNEL_RAID_SAY: - case CHANNEL_GUILD_CHAT: - case CHANNEL_GUILD_SAY: - case CHANNEL_OFFICER_SAY: - case CHANNEL_GUILD_MOTD: - return channel_type - 1; - case CHANNEL_PRIVATE_CHAT: - case CHANNEL_NONPLAYER_TELL: - return channel_type - 5; - case CHANNEL_PRIVATE_TELL: - case CHANNEL_TELL_FROM_CS: - return channel_type - 6; - case CHANNEL_CHAT_CHANNEL_TEXT: - case CHANNEL_OUT_OF_CHARACTER: - case CHANNEL_AUCTION: - case CHANNEL_CUSTOM_CHANNEL: - case CHANNEL_CHARACTER_TEXT: - case CHANNEL_REWARD: - case CHANNEL_DEATH: - case CHANNEL_PET_CHAT: - case CHANNEL_SKILL: - return channel_type - 7; - case CHANNEL_SPELLS: - case CHANNEL_YOU_CAST: - case CHANNEL_YOU_FAIL: - return channel_type - 8; - case CHANNEL_FRIENDLY_CAST: - case CHANNEL_FRIENDLY_FAIL: - case CHANNEL_OTHER_CAST: - case CHANNEL_OTHER_FAIL: - case CHANNEL_HOSTILE_CAST: - case CHANNEL_HOSTILE_FAIL: - case CHANNEL_WORN_OFF: - case CHANNEL_SPELLS_OTHER: - return channel_type - 9; - case CHANNEL_COMBAT: - return channel_type - 15; - case CHANNEL_HEROIC_OPPORTUNITY: - case CHANNEL_NON_MELEE_DAMAGE: - case CHANNEL_DAMAGE_SHIELD: - return channel_type - 16; - case CHANNEL_MELEE_COMBAT: - case CHANNEL_WARNINGS: - case CHANNEL_YOU_HIT: - case CHANNEL_YOU_MISS: - case CHANNEL_ATTACKER_HITS: - case CHANNEL_ATTACKER_MISSES: - return channel_type - 18; - case CHANNEL_OTHER_HIT: - case CHANNEL_OTHER_MISSES: - case CHANNEL_CRITICAL_HIT: - return channel_type - 22; - case CHANNEL_OTHER: - case CHANNEL_MONEY_SPLIT: - case CHANNEL_LOOT: - return channel_type - 30; - case CHANNEL_COMMAND_TEXT: - case CHANNEL_BROADCAST: - case CHANNEL_WHO: - case CHANNEL_COMMANDS: - case CHANNEL_MERCHANT: - case CHANNEL_MERCHANT_BUY_SELL: - case CHANNEL_CONSIDER_MESSAGE: - case CHANNEL_CON_MINUS_2: - case CHANNEL_CON_MINUS_1: - case CHANNEL_CON_0: - case CHANNEL_CON_1: - case CHANNEL_CON_2: - return channel_type - 31; - default: { - return CHANNEL_DEFAULT; - } + case CHANNEL_GROUP_CHAT: + case CHANNEL_GROUP_SAY: + case CHANNEL_RAID_SAY: + case CHANNEL_GUILD_CHAT: + case CHANNEL_GUILD_SAY: + case CHANNEL_OFFICER_SAY: + case CHANNEL_GUILD_MOTD: + return channel_type - 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 5; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return channel_type - 6; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return channel_type - 7; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return channel_type - 31; + default: { + return CHANNEL_DEFAULT; + } } } else if (GetVersion() <= 373) { - if (channel_type <=18) + if (channel_type <= 18) return channel_type; switch (channel_type) { - case CHANNEL_PRIVATE_CHAT: - case CHANNEL_NONPLAYER_TELL: - return 22; - case CHANNEL_PRIVATE_TELL: - case CHANNEL_TELL_FROM_CS: - return 23; - case CHANNEL_CHAT_CHANNEL_TEXT: - case CHANNEL_OUT_OF_CHARACTER: - case CHANNEL_CUSTOM_CHANNEL: - case CHANNEL_CHARACTER_TEXT: - case CHANNEL_REWARD: - case CHANNEL_DEATH: - case CHANNEL_PET_CHAT: - case CHANNEL_SKILL: - return 26; - case CHANNEL_AUCTION: - return 27; - case CHANNEL_SPELLS: - case CHANNEL_YOU_CAST: - case CHANNEL_YOU_FAIL: - return channel_type - 8; - case CHANNEL_FRIENDLY_CAST: - case CHANNEL_FRIENDLY_FAIL: - case CHANNEL_OTHER_CAST: - case CHANNEL_OTHER_FAIL: - case CHANNEL_HOSTILE_CAST: - case CHANNEL_HOSTILE_FAIL: - case CHANNEL_WORN_OFF: - case CHANNEL_SPELLS_OTHER: - return channel_type - 9; - case CHANNEL_COMBAT: - return channel_type - 15; - case CHANNEL_HEROIC_OPPORTUNITY: - case CHANNEL_NON_MELEE_DAMAGE: - case CHANNEL_DAMAGE_SHIELD: - return channel_type - 16; - case CHANNEL_MELEE_COMBAT: - case CHANNEL_WARNINGS: - case CHANNEL_YOU_HIT: - case CHANNEL_YOU_MISS: - case CHANNEL_ATTACKER_HITS: - case CHANNEL_ATTACKER_MISSES: - return channel_type - 18; - case CHANNEL_OTHER_HIT: - case CHANNEL_OTHER_MISSES: - case CHANNEL_CRITICAL_HIT: - return channel_type - 22; - case CHANNEL_OTHER: - case CHANNEL_MONEY_SPLIT: - case CHANNEL_LOOT: - return channel_type - 30; - case CHANNEL_COMMAND_TEXT: - case CHANNEL_BROADCAST: - case CHANNEL_WHO: - case CHANNEL_COMMANDS: - case CHANNEL_MERCHANT: - case CHANNEL_MERCHANT_BUY_SELL: - case CHANNEL_CONSIDER_MESSAGE: - case CHANNEL_CON_MINUS_2: - case CHANNEL_CON_MINUS_1: - case CHANNEL_CON_0: - case CHANNEL_CON_1: - case CHANNEL_CON_2: - return 68; - default: { - return CHANNEL_DEFAULT; - } + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return 22; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return 23; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return 26; + case CHANNEL_AUCTION: + return 27; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return 68; + default: { + return CHANNEL_DEFAULT; + } } } else if (GetVersion() <= 561) { if (channel_type < 20) return channel_type; switch (channel_type) { - case CHANNEL_GUILD_MOTD: - case CHANNEL_GUILD_MEMBER_ONLINE: - case CHANNEL_GUILD_EVENT: - return channel_type + 1; - case CHANNEL_PRIVATE_CHAT: - case CHANNEL_NONPLAYER_TELL: - return channel_type - 1; - case CHANNEL_PRIVATE_TELL: - case CHANNEL_TELL_FROM_CS: - case CHANNEL_ARENA: - case CHANNEL_CHAT_CHANNEL_TEXT: - case CHANNEL_OUT_OF_CHARACTER: - case CHANNEL_AUCTION: - case CHANNEL_CUSTOM_CHANNEL: - case CHANNEL_CHARACTER_TEXT: - case CHANNEL_REWARD: - case CHANNEL_DEATH: - case CHANNEL_PET_CHAT: - case CHANNEL_SKILL: - case CHANNEL_FACTION: - case CHANNEL_SPELLS: - case CHANNEL_YOU_CAST: - case CHANNEL_YOU_FAIL: - return channel_type - 2; - case CHANNEL_FRIENDLY_CAST: - case CHANNEL_FRIENDLY_FAIL: - case CHANNEL_OTHER_CAST: - case CHANNEL_OTHER_FAIL: - case CHANNEL_HOSTILE_CAST: - case CHANNEL_HOSTILE_FAIL: - case CHANNEL_WORN_OFF: - case CHANNEL_SPELLS_OTHER: - case CHANNEL_HEAL_SPELLS: - case CHANNEL_HEALS: - case CHANNEL_FRIENDLY_HEALS: - case CHANNEL_OTHER_HEALS: - case CHANNEL_HOSTILE_HEALS: - return channel_type - 3; - case CHANNEL_COMBAT: - case CHANNEL_GENERAL_COMBAT: - case CHANNEL_HEROIC_OPPORTUNITY: - case CHANNEL_NON_MELEE_DAMAGE: - case CHANNEL_DAMAGE_SHIELD: - case CHANNEL_WARD: - return channel_type - 4; - case CHANNEL_MELEE_COMBAT: - case CHANNEL_WARNINGS: - case CHANNEL_YOU_HIT: - case CHANNEL_YOU_MISS: - case CHANNEL_ATTACKER_HITS: - case CHANNEL_ATTACKER_MISSES: - case CHANNEL_YOUR_PET_HITS: - case CHANNEL_YOUR_PET_MISSES: - case CHANNEL_ATTACKER_HITS_PET: - case CHANNEL_ATTACKER_MISSES_PET: - case CHANNEL_OTHER_HIT: - case CHANNEL_OTHER_MISSES: - return channel_type - 5; - case CHANNEL_OTHER: - case CHANNEL_MONEY_SPLIT: - case CHANNEL_LOOT: - return channel_type - 14; - case CHANNEL_COMMAND_TEXT: - case CHANNEL_BROADCAST: - case CHANNEL_WHO: - case CHANNEL_COMMANDS: - case CHANNEL_MERCHANT: - case CHANNEL_MERCHANT_BUY_SELL: - case CHANNEL_CONSIDER_MESSAGE: - case CHANNEL_CON_MINUS_2: - case CHANNEL_CON_MINUS_1: - case CHANNEL_CON_0: - case CHANNEL_CON_1: - case CHANNEL_CON_2: - case CHANNEL_TRADESKILLS: - case CHANNEL_HARVESTING: - case CHANNEL_HARVESTING_WARNINGS: - return channel_type - 15; - default: { - return CHANNEL_DEFAULT; - } + case CHANNEL_GUILD_MOTD: + case CHANNEL_GUILD_MEMBER_ONLINE: + case CHANNEL_GUILD_EVENT: + return channel_type + 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 1; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + case CHANNEL_ARENA: + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + case CHANNEL_FACTION: + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 2; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + case CHANNEL_HEAL_SPELLS: + case CHANNEL_HEALS: + case CHANNEL_FRIENDLY_HEALS: + case CHANNEL_OTHER_HEALS: + case CHANNEL_HOSTILE_HEALS: + return channel_type - 3; + case CHANNEL_COMBAT: + case CHANNEL_GENERAL_COMBAT: + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + case CHANNEL_WARD: + return channel_type - 4; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + case CHANNEL_YOUR_PET_HITS: + case CHANNEL_YOUR_PET_MISSES: + case CHANNEL_ATTACKER_HITS_PET: + case CHANNEL_ATTACKER_MISSES_PET: + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + return channel_type - 5; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 14; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + case CHANNEL_TRADESKILLS: + case CHANNEL_HARVESTING: + case CHANNEL_HARVESTING_WARNINGS: + return channel_type - 15; + default: { + return CHANNEL_DEFAULT; + } } } else { switch (channel_type) { - default: { - return channel_type; - } + default: { + return channel_type; } - } + } + } return channel_type; } -void Client::HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id) { - if (!from || GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) +void Client::HandleTellMessage(const char* fromName, const char* message, const char* to, int32 current_language_id) { + if (!fromName || !to || GetPlayer()->IsIgnored(fromName)) return; PacketStruct* packet = configReader.getStruct("WS_HearChat", GetVersion()); if (packet) { - packet->setDataByName("from", from->GetPlayer()->GetName()); + packet->setDataByName("from", fromName); packet->setDataByName("to", to); packet->setDataByName("channel", GetMessageChannelColor(CHANNEL_PRIVATE_TELL)); packet->setDataByName("from_spawn_id", 0xFFFFFFFF); packet->setDataByName("to_spawn_id", 0xFFFFFFFF); packet->setDataByName("unknown2", 1, 1); packet->setDataByName("show_bubble", 1); - + if (current_language_id == 0 || GetPlayer()->HasLanguage(current_language_id)) { packet->setDataByName("understood", 1); } - + packet->setDataByName("time", 2); packet->setDataByName("language", current_language_id); packet->setMediumStringByName("message", message); EQ2Packet* outpacket = packet->serialize(); - QueuePacket(outpacket); + QueuePacket(outpacket); safe_delete(packet); } } @@ -4422,9 +4482,9 @@ void Client::SendSpellUpdate(Spell* spell, bool add_silently, bool add_to_hotbar packet->setDataByName("spell_id", spell->GetSpellID()); packet->setDataByName("unique_id", spell->GetSpellData()->spell_name_crc); packet->setDataByName("spell_name", spell->GetName()); - if(add_silently) + if (add_silently) packet->setDataByName("add_silently", 1); - if(add_to_hotbar) + if (add_to_hotbar) packet->setDataByName("add_to_hotbar", 1); packet->setDataByName("unknown", xxx); packet->setDataByName("display_spell_tier", 1); @@ -4449,7 +4509,7 @@ void Client::Message(int8 type, const char* message, ...) { va_start(argptr, message); vsnprintf(buffer, sizeof(buffer), message, argptr); - va_end(argptr); + va_end(argptr); SimpleMessage(type, buffer); } @@ -4495,8 +4555,8 @@ bool Client::Summon(const char* search_name) { target->SetSpawnOrigHeading(target->GetHeading()); } target->SetLocation(GetPlayer()->GetLocation()); - if(target->IsNPC()) { - ((NPC*)target)->HaltMovement(); + if (target->IsNPC()) { + ((NPC*)target)->HaltMovement(); } } else if (target) @@ -4522,122 +4582,146 @@ bool Client::Summon(const char* search_name) { std::string Client::IdentifyInstanceLockout(int32 zoneID, bool displayClient) { int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); - if(instanceType < 1) + if (instanceType < 1) return std::string(""); - + ZoneServer* instance_zone = nullptr; InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); - if (data) { - // If lockout instances check to see if we are locked out - if (instanceType == SOLO_LOCKOUT_INSTANCE || instanceType == GROUP_LOCKOUT_INSTANCE || instanceType == RAID_LOCKOUT_INSTANCE) { - int32 time = 0; - // Check success timer - if (data->last_success_timestamp > 0) { - if (Timer::GetUnixTimeStamp() < data->last_success_timestamp + data->success_lockout_time) { - // Timer has not expired yet can't re enter - LogWrite(INSTANCE__DEBUG, 0, "Instance", "Success lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); - time = (data->last_success_timestamp + data->success_lockout_time) - Timer::GetUnixTimeStamp(); - } - } - - // Check failure timer - if (data->last_failure_timestamp > 0) { - if (Timer::GetUnixTimeStamp() < data->last_failure_timestamp + data->failure_lockout_time) { - // Timer has not expired yet - LogWrite(INSTANCE__DEBUG, 0, "Instance", "Failure lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); - time = (data->last_failure_timestamp + data->failure_lockout_time) - Timer::GetUnixTimeStamp(); - } - } - - // Time > 0 then we are locked out, make the message and send it and return true - if (time > 0) { - string time_msg = ""; - int16 hour; - int8 min; - int8 sec; - hour = time / 3600; - time = time % 3600; - min = time / 60; - time = time % 60; - sec = time; - - if (hour > 0) { - char temp[10]; - snprintf(temp, 9," %i", hour); - time_msg.append(temp); - time_msg.append(" hour"); - time_msg.append((hour > 1) ? "s" : ""); - } - if (min > 0) { - char temp[5]; - snprintf(temp, 4," %i", min); - time_msg.append(temp); - time_msg.append(" minute"); - time_msg.append((min > 1) ? "s" : ""); - } - // Only add seconds if minutes and hours are 0 - if (hour == 0 && min == 0 && sec > 0) { - char temp[5]; - snprintf(temp, 4," %i", sec); - time_msg.append(temp); - time_msg.append(" second"); - time_msg.append((sec > 1) ? "s" : ""); - } - - if(displayClient) - Message(CHANNEL_COLOR_YELLOW, "You may not enter again for%s.", time_msg.c_str()); - return time_msg; + if (data) { + // If lockout instances check to see if we are locked out + if (instanceType == SOLO_LOCKOUT_INSTANCE || instanceType == GROUP_LOCKOUT_INSTANCE || instanceType == RAID_LOCKOUT_INSTANCE) { + int32 time = 0; + // Check success timer + if (data->last_success_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_success_timestamp + data->success_lockout_time) { + // Timer has not expired yet can't re enter + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Success lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_success_timestamp + data->success_lockout_time) - Timer::GetUnixTimeStamp(); } } + + // Check failure timer + if (data->last_failure_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_failure_timestamp + data->failure_lockout_time) { + // Timer has not expired yet + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Failure lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_failure_timestamp + data->failure_lockout_time) - Timer::GetUnixTimeStamp(); + } + } + + // Time > 0 then we are locked out, make the message and send it and return true + if (time > 0) { + string time_msg = ""; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + snprintf(temp, 9, " %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + snprintf(temp, 4, " %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + snprintf(temp, 4, " %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + if (displayClient) + Message(CHANNEL_COLOR_YELLOW, "You may not enter again for%s.", time_msg.c_str()); + return time_msg; + } } + } return std::string(""); } -ZoneServer* Client::IdentifyInstance(int32 zoneID) { +bool Client::IdentifyInstance(ZoneChangeDetails* zone_details, int32 zoneID) { int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); - if(instanceType < 1) - return nullptr; + if (instanceType < 1) + return false; - ZoneServer* instance_zone = nullptr; + bool foundZone = false; InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); - if (data) { - std::string lockoutTime = IdentifyInstanceLockout(zoneID); - // If lockout instances check to see if we are locked out - if (lockoutTime.length() > 0) { - return nullptr; - } - - // Need to update `character_instances` table with new timestamps (for persistent) and instance id's - instance_zone = zone_list.GetByInstanceID(data->instance_id, zoneID, false, false); - - // 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 (instance_zone && (data->instance_id == 0 || data->instance_id != instance_zone->GetInstanceID())) { - if (instanceType == SOLO_PERSIST_INSTANCE || instanceType == GROUP_PERSIST_INSTANCE || instanceType == RAID_PERSIST_INSTANCE) { - database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID(), 1, Timer::GetUnixTimeStamp()); - data->last_success_timestamp = Timer::GetUnixTimeStamp(); - } - else - database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID()); - - data->instance_id = instance_zone->GetInstanceID(); - } + if (data) { + std::string lockoutTime = IdentifyInstanceLockout(zoneID); + // If lockout instances check to see if we are locked out + if (lockoutTime.length() > 0) { + return false; } - return instance_zone; + + // 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); + + // 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)) { + if (instanceType == SOLO_PERSIST_INSTANCE || instanceType == GROUP_PERSIST_INSTANCE || instanceType == RAID_PERSIST_INSTANCE) { + database.UpdateCharacterInstance(GetCharacterID(), zone_details->zoneName, zone_details->instanceId, 1, Timer::GetUnixTimeStamp()); + data->last_success_timestamp = Timer::GetUnixTimeStamp(); + } + else + database.UpdateCharacterInstance(GetCharacterID(), zone_details->zoneName, zone_details->instanceId); + + data->instance_id = zone_details->instanceId; + } + } + return foundZone; } bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { ZoneServer* instance_zone = NULL; int8 instanceType = 0; + bool foundZone = false; + ZoneChangeDetails zone_details; // determine if this is a group instanced zone that already exists - instance_zone = GetPlayer()->GetGroupMemberInZone(zoneID); + foundZone = world.GetGroupManager()->IdentifyMemberInGroupOrRaid(&zone_details, this, zoneID); - if (instance_zone != NULL) - Zone(instance_zone->GetInstanceID(), zone_coords_valid); + if (foundZone) { + InstanceData* data = nullptr; + if (zone_details.instanceId) + data = GetPlayer()->GetCharacterInstances()->FindInstanceByInstanceID(zone_details.instanceId); + + switch (zone_details.instanceType) { + case SOLO_LOCKOUT_INSTANCE: + case GROUP_LOCKOUT_INSTANCE: + case RAID_LOCKOUT_INSTANCE: + case SOLO_PERSIST_INSTANCE: + case GROUP_PERSIST_INSTANCE: + case RAID_PERSIST_INSTANCE: + { + if (!data && zone_details.instanceId) { + 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) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, zone_details.instanceId, Timer::GetUnixTimeStamp(), 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime, zoneID, zone_details.instanceType, zone_details.zoneName); + } + break; + } + } + Zone(&zone_details, instance_zone, zone_coords_valid); + } else if ((instanceType = database.GetInstanceTypeByZoneID(zoneID)) > 0) { // best to check if we already have our own instance! - if((instance_zone = IdentifyInstance(zoneID)) == nullptr) + if (!(foundZone = IdentifyInstance(&zone_details, zoneID))) { switch (instanceType) { @@ -4645,14 +4729,13 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { case GROUP_LOCKOUT_INSTANCE: case RAID_LOCKOUT_INSTANCE: { - instance_zone = zone_list.GetByInstanceID(0, zoneID); - if (instance_zone) { + if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID))) { // 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(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), zone_details.instanceId, zone_details.zoneName, zone_details.instanceType, 0, 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime); if (db_id > 0) - GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, zone_details.instanceId, 0, 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime, zoneID, zone_details.instanceType, zone_details.zoneName); } break; } @@ -4660,12 +4743,11 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { case GROUP_PERSIST_INSTANCE: case RAID_PERSIST_INSTANCE: { - instance_zone = zone_list.GetByInstanceID(0, zoneID); - if (instance_zone) { - int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + if ((foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID))) { + 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) - GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, zone_details.instanceId, Timer::GetUnixTimeStamp(), 0, zone_details.defaultLockoutTime, zone_details.defaultReenterTime, zoneID, zone_details.instanceType, zone_details.zoneName); } break; } @@ -4676,11 +4758,21 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { instance_zone = zone_list.GetByLowestPopulation(zoneID); 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()) - instance_zone = zone_list.GetByInstanceID(0, zoneID); + if (instance_zone->GetClientCount() >= rule_manager.GetZoneRule(GetCurrentZoneID(), R_Zone, MaxPlayers)->GetInt32()) { + foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID); + } + else { + peer_manager.setZonePeerDataSelf(&zone_details, std::string(instance_zone->GetZoneFile()), std::string(instance_zone->GetZoneName()), + instance_zone->GetZoneID(), instance_zone->GetInstanceID(), instance_zone->GetSafeX(), instance_zone->GetSafeY(), + instance_zone->GetSafeZ(), instance_zone->GetSafeHeading(), instance_zone->GetZoneLockState(), + instance_zone->GetMinimumStatus(), instance_zone->GetMinimumLevel(), instance_zone->GetMaximumLevel(), + instance_zone->GetMinimumVersion(), instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), + instance_zone->GetInstanceType(), instance_zone->NumPlayers(), instance_zone); + } + } + else { + foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID); } - else - instance_zone = zone_list.GetByInstanceID(0, zoneID); break; } @@ -4696,7 +4788,7 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { case QUEST_INSTANCE: { - instance_zone = zone_list.GetByInstanceID(0, zoneID); + foundZone = zone_list.GetZoneByInstance(&zone_details, 0, zoneID); 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`; @@ -4712,15 +4804,12 @@ bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { } - if (instance_zone != NULL) - Zone(instance_zone, zone_coords_valid); + if (foundZone) + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, zone_coords_valid); } - if (instance_zone != NULL) - return true; - else - return false; + return foundZone; } bool Client::GotoSpawn(const char* search_name, bool forceTarget) { @@ -4739,9 +4828,9 @@ bool Client::GotoSpawn(const char* search_name, bool forceTarget) { } else target = GetPlayer()->GetTarget(); - + float y = (target != nullptr) ? target->GetY() : 0.0f; - if(target && target->GetMap() != GetPlayer()->GetMap()) { + if (target && target->GetMap() != GetPlayer()->GetMap()) { auto loc = glm::vec3(target->GetX(), target->GetZ(), target->GetY()); y = GetPlayer()->FindBestZ(loc, nullptr); } @@ -4774,21 +4863,23 @@ bool Client::CheckZoneAccess(const char* zoneName) { LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone access check for %s (%u), client: %u", zoneName, database.GetZoneID(zoneName), GetVersion()); - ZoneServer* zone = zone_list.Get(zoneName, false, false, false); - - // JA: implemented /zone lock|unlock commands (2012.07.28) - if (zone && zone->GetZoneLockState()) - { - LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone currently LOCKED: '%s' (%ul)", zoneName, zone->GetZoneID()); - Message(CHANNEL_COLOR_RED, "This zone is locked, and you don't have the key! (%s).", zoneName); - return false; + bool zoneFound = false; + ZoneChangeDetails zone_details; + if (zoneFound = zone_list.GetZone(&zone_details, 0, std::string(zoneName), false, false, false)) { + // JA: implemented /zone lock|unlock commands (2012.07.28) + if (zone_details.lockState) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone currently LOCKED: '%s' (%ul)", zoneName, zone_details.zoneId); + Message(CHANNEL_COLOR_RED, "This zone is locked, and you don't have the key! (%s).", zoneName); + return false; + } } sint16 zoneMinStatus = 0; int16 zoneMinLevel = 0; int16 zoneMaxLevel = 0; int16 zoneMinVersion = 0; - if (!zone) + if (!zoneFound) { LogWrite(CCLIENT__DEBUG, 0, "Client", "Grabbing zone requirements for %s", zoneName); bool success = database.GetZoneRequirements(zoneName, &zoneMinStatus, &zoneMinLevel, &zoneMaxLevel, &zoneMinVersion); @@ -4799,10 +4890,10 @@ bool Client::CheckZoneAccess(const char* zoneName) { } else { - zoneMinStatus = zone->GetMinimumStatus(); - zoneMinLevel = zone->GetMinimumLevel(); - zoneMaxLevel = zone->GetMaximumLevel(); - zoneMinVersion = zone->GetMinimumVersion(); + zoneMinStatus = zone_details.minStatus; + zoneMinLevel = zone_details.minLevel; + zoneMaxLevel = zone_details.maxLevel; + zoneMinVersion = zone_details.minVersion; } LogWrite(CCLIENT__DEBUG, 0, "Client", "Access Requirements: status %i, level %i - %i, req >= %i version", zoneMinStatus, zoneMinLevel, zoneMaxLevel, zoneMinVersion); @@ -4855,54 +4946,75 @@ bool Client::CheckZoneAccess(const char* zoneName) { } void Client::Zone(int32 instanceid, bool set_coords, bool byInstanceID, bool is_spell) { - Zone(zone_list.GetByInstanceID(instanceid, 0, false, true), set_coords, is_spell); + ZoneChangeDetails zone_details; + if (zone_list.GetZoneByInstance(&zone_details, instanceid, 0)) { + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, set_coords, is_spell); + } } -void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) { - if (!new_zone) { - LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value"); - return; - } - - if(GetVersion() == 373 && GetAdminStatus() == 0) { - if(strncasecmp(new_zone->GetZoneFile(),"boat_06p_tutorial02",19) && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02",17) - && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02_epic",22)) { // accounts for 01 and 02 zones +void Client::Zone(ZoneChangeDetails* new_zone, ZoneServer* opt_zone, bool set_coords, bool is_spell) { + if (GetVersion() == 373 && GetAdminStatus() == 0) { + if (new_zone->zoneFileName.find("boat_06p_tutorial02") == std::string::npos && new_zone->zoneFileName.find("tutorial_island02") == std::string::npos + && new_zone->zoneFileName.find("tutorial_island02_epic") == std::string::npos) { // accounts for 01 and 02 zones SimpleMessage(CHANNEL_COLOR_RED, "This client does not currently support beyond the boat tutorial (farjourneyfreeport) or island of refuge (islerefuge1)"); return; } } - else if(GetVersion() < 546 && GetVersion() > 561) { - if(strncasecmp(new_zone->GetZoneFile(),"design_path_script",18) == 0) { + else if (GetVersion() < 546 && GetVersion() > 561) { + if (new_zone->zoneFileName.find("design_path_script") != std::string::npos) { SimpleMessage(CHANNEL_COLOR_RED, "This zone is only available for KoS and earlier clients."); return; } } - + if (client_zoning) { + return; + } + client_zoning = true; - zoning_id = new_zone->GetZoneID(); - zoning_instance_id = new_zone->GetInstanceID(); - + zoning_id = new_zone->zoneId; + zoning_instance_id = new_zone->instanceId; + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__); player->SetResurrecting(true); - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from fighting...", __FUNCTION__); - //GetCurrentZone()->GetCombat()->RemoveHate(player); - SaveSpells(); - + if (set_coords) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Zoning player to coordinates x: %2f, y: %2f, z: %2f, heading: %2f in zone '%s'...", + __FUNCTION__, + new_zone->safeX, + new_zone->safeY, + new_zone->safeZ, + new_zone->safeHeading, + new_zone->zoneName.c_str() + ); + player->SetX(new_zone->safeX); + player->SetY(new_zone->safeY); + player->SetZ(new_zone->safeZ); + player->SetHeading(new_zone->safeHeading); + + SetZoningCoords(new_zone->safeX, new_zone->safeY, + new_zone->safeZ, new_zone->safeHeading); + } + else { + ResetZoningCoords(); + } + + Save(); + ResetSendMail(); // Remove players pet from zone if there is one ((Entity*)player)->DismissAllPets(); LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__); GetCurrentZone()->RemoveSpawn(player, false, true, true, true, !is_spell); - + GetPlayer()->DeleteSpellEffects(true); - - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName()); - SetZoningDestination(new_zone); - SetCurrentZone(new_zone); - + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->zoneName.c_str()); + SetZoningDestination(opt_zone); + SetCurrentZone(opt_zone); + if (player->GetGroupMemberInfo()) { LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Player in group, updating group info...", __FUNCTION__); @@ -4915,44 +5027,69 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) { UpdateTimeStampFlag(ZONE_UPDATE_FLAG); - if (set_coords) - { - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Zoning player to coordinates x: %2f, y: %2f, z: %2f, heading: %2f in zone '%s'...", - __FUNCTION__, - GetCurrentZone()->GetSafeX(), - GetCurrentZone()->GetSafeY(), - GetCurrentZone()->GetSafeZ(), - GetCurrentZone()->GetSafeHeading(), - new_zone->GetZoneName() - ); - player->SetX(GetCurrentZone()->GetSafeX()); - player->SetY(GetCurrentZone()->GetSafeY()); - player->SetZ(GetCurrentZone()->GetSafeZ()); - player->SetHeading(GetCurrentZone()->GetSafeHeading()); - - SetZoningCoords(GetCurrentZone()->GetSafeX(), GetCurrentZone()->GetSafeY(), - GetCurrentZone()->GetSafeZ(), GetCurrentZone()->GetSafeHeading()); - } - else { - ResetZoningCoords(); - } - - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Saving Player info...", __FUNCTION__); - Save(); - - char* new_zone_ip = 0; - if (IsPrivateAddress(this->GetIP()) && strlen(net.GetInternalWorldAddress()) > 0) - new_zone_ip = net.GetInternalWorldAddress(); + const char* new_zone_ip = 0; + if (IsPrivateAddress(this->GetIP()) && new_zone->peerInternalWorldAddress.length() > 0) + new_zone_ip = new_zone->peerInternalWorldAddress.c_str(); else - new_zone_ip = net.GetWorldAddress(); + new_zone_ip = new_zone->peerWorldAddress.c_str(); LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: New Zone IP '%s'...", __FUNCTION__, new_zone_ip); - int32 key = Timer::GetUnixTimeStamp(); - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending ZoneChangeMsg...", __FUNCTION__); - ClientPacketFunctions::SendZoneChange(this, new_zone_ip, net.GetWorldPort(), key); + int32 key = static_cast(MakeRandomFloat(0.01, 1.0) * UINT32_MAX); - LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending to zone_auth.AddAuth...", __FUNCTION__); - zone_auth.AddAuth(new ZoneAuthRequest(GetAccountID(), player->GetName(), key)); + new_zone->zoneKey = key; + new_zone->authDispatchedTime = Timer::GetUnixTimeStamp(); + zoning_details = ZoneChangeDetails(new_zone); + client_zoning_details_set = true; + if (new_zone->peerId.length() > 0 && new_zone->peerId != "self") { + if (new_zone->peerAuthorized) { + LogWrite(PEERING__INFO, 0, "Peering", "%s: Peer %s authorized us to zone...", __FUNCTION__, new_zone->peerId.c_str()); + } + else { + boost::property_tree::ptree root; + struct in_addr in; + in.s_addr = GetIP(); + root.put("account_id", std::to_string(GetAccountID())); + root.put("character_name", std::string(player->GetName())); + root.put("zone_name", new_zone->zoneName); + root.put("zone_id", new_zone->zoneId); + root.put("instance_id", new_zone->instanceId); + root.put("login_key", std::to_string(key)); + root.put("client_ip", std::string(inet_ntoa(in))); + root.put("first_login", false); + std::ostringstream jsonStream; + boost::property_tree::write_json(jsonStream, root); + std::string jsonPayload = jsonStream.str(); + LogWrite(PEERING__INFO, 0, "Peering", "%s: Sending AddCharAuth for %s to peer %s:%u for zone %s", __FUNCTION__, player->GetName(), new_zone->peerWebAddress.c_str(), new_zone->peerWebPort, new_zone->zoneName.c_str()); + peer_https_pool.sendPostRequestToPeerAsync(new_zone->peerId, new_zone->peerWebAddress, std::to_string(new_zone->peerWebPort), "/addcharauth", jsonPayload); + + } + } + else { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending to zone_auth.AddAuth...", __FUNCTION__); + zone_auth.AddAuth(new ZoneAuthRequest(GetAccountID(), player->GetName(), key)); + new_zone->peerAuthorized = true; // local, we can bypass (should technically already be true) + zoning_details = ZoneChangeDetails(new_zone); + } + + if (new_zone->peerAuthorized) { + ApproveZone(); + } +} + +void Client::ApproveZone() { + if (!client_zoning_details_set) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: client zoning details not set to zone player %s...", __FUNCTION__, player->GetName()); + return; + } + zoning_details.zoningPastAuth = true; + zoning_details.peerAuthorized = true; + const char* new_zone_ip = 0; + if (IsPrivateAddress(this->GetIP()) && zoning_details.peerInternalWorldAddress.length() > 0) + new_zone_ip = zoning_details.peerInternalWorldAddress.c_str(); + else + new_zone_ip = zoning_details.peerWorldAddress.c_str(); + LogWrite(CCLIENT__INFO, 0, "Client", "%s: Sending ZoneChangeMsg %s:%u with key %u...", player->GetName(), (char*)new_zone_ip, zoning_details.peerWorldPort, zoning_details.zoneKey); + ClientPacketFunctions::SendZoneChange(this, (char*)new_zone_ip, zoning_details.peerWorldPort, zoning_details.zoneKey); if (version > 373) { PacketStruct* packet = configReader.getStruct("WS_CancelMoveObjectMode", version); if (packet) @@ -4966,7 +5103,15 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) { void Client::Zone(const char* new_zone, bool set_coords, bool is_spell) { LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request to '%s'", new_zone); - Zone(zone_list.Get(new_zone), set_coords, is_spell); + ZoneChangeDetails zone_details; + + int32 zone_id = database.GetZoneID(new_zone); + 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)) { + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, set_coords, is_spell); + } } float Client::DistanceFrom(Client* client) { @@ -5100,7 +5245,7 @@ void Client::UpdateCharacterInstances() { void Client::HandleVerbRequest(EQApplicationPacket* app) { PacketStruct* packet = configReader.getStruct("WS_EntityVerbsRequest", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { int32 spawn_id = packet->getType_int32_ByName("spawn_id"); PacketStruct* out = configReader.getStruct("WS_EntityVerbsResponse", GetVersion()); Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); @@ -5144,9 +5289,34 @@ void Client::HandleVerbRequest(EQApplicationPacket* app) { delete_commands.push_back(player->CreateEntityCommand("kick from group", 10000, "kickfromgroup", "", 0, 0)); delete_commands.push_back(player->CreateEntityCommand("make group leader", 10000, "makeleader", "", 0, 0)); } - if(spawn->IsPlayer() && player->GetGroupMemberInfo() && !player->GetGroupMemberInfo()->mentor_target_char_id) + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + int32 spawn_group_id = ((Player*)spawn)->GetGroupMemberInfo()->group_id; + PlayerGroup* spawn_group = world.GetGroupManager()->GetGroup(spawn_group_id); + + int32 player_group_id = player->GetGroupMemberInfo() ? player->GetGroupMemberInfo()->group_id : 0; + PlayerGroup* player_group = nullptr; + if (player_group_id) + player_group = world.GetGroupManager()->GetGroup(player_group_id); + + if (spawn_group && !player->IsGroupMember((Player*)spawn) && !spawn_group->IsGroupRaid() && player_group && player->GetGroupMemberInfo()->leader + && (!player_group->IsInRaidGroup(spawn_group_id) || player_group->IsInRaidGroup(player_group_id, true))) { + delete_commands.push_back(player->CreateEntityCommand("invite to raid", 10000, "raidinvite", "", 0, 0)); + } + else if (spawn_group && player_group && player_group->IsInRaidGroup(player_group_id, true) && player->GetGroupMemberInfo()->leader && player_group->IsInRaidGroup(spawn_group_id)) { + if (((Player*)spawn)->GetGroupMemberInfo()->is_raid_looter) { + delete_commands.push_back(player->CreateEntityCommand("remove looter", 10000, "raid_looter", "", 0, 0)); + } + else { + delete_commands.push_back(player->CreateEntityCommand("add looter", 10000, "raid_looter", "", 0, 0)); + } + delete_commands.push_back(player->CreateEntityCommand("disband from raid", 10000, "raiddisband", "", 0, 0)); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (spawn->IsPlayer() && player->GetGroupMemberInfo() && !player->GetGroupMemberInfo()->mentor_target_char_id && player_group_id == spawn_group_id) delete_commands.push_back(player->CreateEntityCommand("Mentor", 10000, "mentor", "", 0, 0)); - else if(spawn->IsPlayer() && player->GetGroupMemberInfo() && player->GetGroupMemberInfo()->mentor_target_char_id == ((Player*)spawn)->GetCharacterID()) + else if (spawn->IsPlayer() && player->GetGroupMemberInfo() && player->GetGroupMemberInfo()->mentor_target_char_id == ((Player*)spawn)->GetCharacterID()) delete_commands.push_back(player->CreateEntityCommand("Stop Mentoring", 10000, "unmentor", "", 0, 0)); } else if (!player->GetGroupMemberInfo() || (player->GetGroupMemberInfo()->leader && world.GetGroupManager()->GetGroupSize(player->GetGroupMemberInfo()->group_id) < 6)) @@ -5229,7 +5399,7 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) { } } - if (new_level > old_level) { + if (new_level > old_level) { player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_ADVENTURE, new_level, player->GetAdventureClass()); } @@ -5306,28 +5476,28 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) { player_skills->SetSkillCapsByType(SKILL_TYPE_ARMOR, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_SHIELD, new_skill_cap); - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_Player, AutoSkillUpBaseSkills)->GetBool()) { + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_Player, AutoSkillUpBaseSkills)->GetBool()) { //SKILL_TYPE_ARMOR/SKILL_TYPE_SHIELD always has the same current / max values player_skills->SetSkillValuesByType(SKILL_TYPE_ARMOR, new_skill_cap, false); player_skills->SetSkillValuesByType(SKILL_TYPE_SHIELD, new_skill_cap, false); } - + player_skills->SetSkillCapsByType(SKILL_TYPE_CLASS, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPON, new_skill_cap); - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_Player, AutoSkillUpBaseSkills)->GetBool()) { + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_Player, AutoSkillUpBaseSkills)->GetBool()) { //SKILL_TYPE_CLASS/SKILL_TYPE_WEAPON always has the same current/max values player_skills->SetSkillValuesByType(SKILL_TYPE_CLASS, new_skill_cap, false); player_skills->SetSkillValuesByType(SKILL_TYPE_WEAPON, new_skill_cap, false); } - + player_skills->SetSkillCapsByType(SKILL_TYPE_COMBAT, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_GENERAL, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_SPELLCASTING, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_AVOIDANCE, new_skill_cap); player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPONRY, new_skill_cap); - + if (new_level > player->GetTSLevel()) player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, new_skill_cap); @@ -5405,7 +5575,7 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) { ((Bot*)bot)->ChangeLevel(old_level, new_level); } } - + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) GetPlayer()->GetZone()->AddDamagedSpawn(GetPlayer()); } @@ -5439,10 +5609,10 @@ void Client::ChangeTSLevel(int16 old_level, int16 new_level) { QueuePacket(level_update->serialize()); safe_delete(level_update); } - + // provide new spells upon levelling SendNewTradeskillSpells(); - + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); if (command_packet) { command_packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); @@ -5618,8 +5788,8 @@ void Client::CloseLoot(int32 spawn_id) { safe_delete(packet); } } - - if(spawn_id > 0){ + + if (spawn_id > 0) { PacketStruct* packet = configReader.getStruct("WS_StoppedLooting", GetVersion()); if (packet) { packet->setDataByName("spawn_id", spawn_id); @@ -5628,9 +5798,9 @@ void Client::CloseLoot(int32 spawn_id) { QueuePacket(outapp); safe_delete(packet); } - + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); - if(spawn) { + if (spawn) { spawn->CloseLoot(GetPlayer()); } } @@ -5830,16 +6000,17 @@ void Client::SendLootResponsePacket(int32 total_coins, vector* items, Spa } safe_delete_array(data); safe_delete(packet); - + if (!items || items->size() == 0) CloseLoot(entity->GetID()); - + } } bool Client::LootSpawnByMethod(Spawn* entity) { bool sentLoot = false; + bool startWithLooter = true; world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo(); if (gmi && gmi->group_id) @@ -5848,15 +6019,61 @@ bool Client::LootSpawnByMethod(Spawn* entity) { if (group) { int8 auto_split_coin = group->GetGroupOptions()->auto_split; - group->MGroupMembers.readlock(__FUNCTION__, __LINE__); - deque* members = group->GetMembers(); + + bool isLeadGroup = group->IsInRaidGroup(group->GetID(), true); + bool isInRaid = group->IsInRaidGroup(group->GetID()); + std::vector raidGroups; + group->GetRaidGroups(&raidGroups); + + if (!isInRaid && raidGroups.size() < 1) { + raidGroups.push_back(group->GetID()); + } + std::vector::iterator group_itr; + + int32 split_coin_per_player = 0; int32 coins_remain_after_split = entity->GetLootCoins(); int32 total_coins = entity->GetLootCoins(); - if (auto_split_coin) { - int8 members_in_zone = 0; + bool foundLooterResetRaidRun = false; + int8 members_in_zone = 0; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) { + group = world.GetGroupManager()->GetGroup((*group_itr)); + if (!group) + continue; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + if (member->GetZone() != GetPlayer()->GetZone()) + continue; + + members_in_zone++; + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + if (auto_split_coin) { + if (members_in_zone < 1) // this should not happen, but divide by zero checked + members_in_zone = 1; + split_coin_per_player = entity->GetLootCoins() / members_in_zone; + coins_remain_after_split = entity->GetLootCoins() - (split_coin_per_player * members_in_zone); + entity->SetLootCoins(0, false); + } + int32 lootGroup = 0; + for (group_itr = raidGroups.begin(); group_itr != raidGroups.end();) { + group = world.GetGroupManager()->GetGroup((*group_itr)); + if (!group) + continue; + + isLeadGroup = group->IsInRaidGroup((*group_itr), true); + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + LogWrite(LOOT__INFO, 0, "Loot", "%s: Group LootSpawnByMethod %u, auto coin split %u, split coin per player %u, remaining coin after split %u", entity->GetName(), entity->GetLootMethod(), auto_split_coin, split_coin_per_player, coins_remain_after_split); for (int8 i = 0; i < members->size(); i++) { Entity* member = members->at(i)->member; if (!member || !member->IsPlayer()) @@ -5865,97 +6082,94 @@ bool Client::LootSpawnByMethod(Spawn* entity) { if (member->GetZone() != GetPlayer()->GetZone()) continue; - members_in_zone++; - } + // this will make sure we properly send the loot window to the initial requester if there is no item rarity matches + if (startWithLooter && member != GetPlayer()) + continue; + else if (!startWithLooter && member == GetPlayer()) + continue; - if (members_in_zone < 1) // this should not happen, but divide by zero checked - members_in_zone = 0; - - split_coin_per_player = entity->GetLootCoins() / members_in_zone; - coins_remain_after_split = entity->GetLootCoins() - (split_coin_per_player * members_in_zone); - entity->SetLootCoins(0, false); - } - - LogWrite(LOOT__INFO, 0, "Loot", "%s: Group LootSpawnByMethod %u, auto coin split %u, split coin per player %u, remaining coin after split %u", entity->GetName(), entity->GetLootMethod(), auto_split_coin, split_coin_per_player, coins_remain_after_split); - bool startWithLooter = true; - - for (int8 i = 0; i < members->size(); i++) { - Entity* member = members->at(i)->member; - if (!member || !member->IsPlayer()) - continue; - - if (member->GetZone() != GetPlayer()->GetZone()) - continue; - - // this will make sure we properly send the loot window to the initial requester if there is no item rarity matches - if (startWithLooter && member != GetPlayer()) - continue; - else if (!startWithLooter && member == GetPlayer()) - continue; - else if (startWithLooter) { - i = 0; - startWithLooter = false; - } - - if (auto_split_coin && (split_coin_per_player + coins_remain_after_split) > 0) { - player->AddCoins(split_coin_per_player + coins_remain_after_split); - if (((Player*)member)->GetClient()) { - ((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of %s from the corpse of %s is %s.", GetCoinMessage(total_coins).c_str(), entity->GetLootName(), GetCoinMessage(split_coin_per_player + coins_remain_after_split).c_str()); + if (auto_split_coin) { + int32 coin_recv = 0; + if (member == GetPlayer() && auto_split_coin && (split_coin_per_player + coins_remain_after_split) > 0) { + coin_recv = split_coin_per_player + coins_remain_after_split; + player->AddCoins(split_coin_per_player + coins_remain_after_split); + if (coins_remain_after_split > 0) // overflow of coin division went to the first player + coins_remain_after_split = 0; + } + else if (split_coin_per_player > 0) { + coin_recv = split_coin_per_player; + player->AddCoins(split_coin_per_player); + } + if (coin_recv && ((Player*)member)->GetClient()) { + ((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of %s from the corpse of %s is %s.", GetCoinMessage(total_coins).c_str(), entity->GetLootName(), GetCoinMessage(coin_recv).c_str()); + } } - if (coins_remain_after_split > 0) // overflow of coin division went to the first player - coins_remain_after_split = 0; - } - switch (entity->GetLootMethod()) { - case GroupLootMethod::METHOD_LOTTO: - case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { - if (((Player*)member)->GetClient()) { - switch (member->GetInfoStruct()->get_group_auto_loot_method()) { - case 1: { // lotto, need - if (entity->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { - entity->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + switch (entity->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (((Player*)member)->GetClient()) { + switch (member->GetInfoStruct()->get_group_auto_loot_method()) { + case 1: { // lotto, need + if (entity->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + entity->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + } + else { // *need* before greed + entity->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + } + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; } - else { // *need* before greed - entity->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + case 2: { // decline + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; } - entity->AddSpawnLootWindowCompleted(member->GetID(), true); - // if it already exists we have to override the setting - entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); - break; - } - case 2: { // decline - entity->AddSpawnLootWindowCompleted(member->GetID(), true); - // if it already exists we have to override the setting - entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); - break; - } - default: { // presume 0 or higher than 2 - ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity, true); - break; - } + default: { // presume 0 or higher than 2 + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity, true); + break; + } + } + sentLoot = true; } + break; + } + case GroupLootMethod::METHOD_ROUND_ROBIN: { + entity->AddSpawnLootWindowCompleted(member->GetID(), true); sentLoot = true; + break; } - break; - } - case GroupLootMethod::METHOD_ROUND_ROBIN: { - entity->AddSpawnLootWindowCompleted(member->GetID(), true); - sentLoot = true; - break; - } - case GroupLootMethod::METHOD_LEADER: { - if (member->GetGroupMemberInfo()->leader) - ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); - break; - } - case GroupLootMethod::METHOD_FFA: { - if(member == GetPlayer()) { - ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + case GroupLootMethod::METHOD_LEADER: { + if ((!isInRaid || (isInRaid && isLeadGroup)) && member->GetGroupMemberInfo()->leader) + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + break; + } + case GroupLootMethod::METHOD_FFA: { + if (member == GetPlayer()) { + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + } + break; + } + } + + if (startWithLooter) { + startWithLooter = false; + foundLooterResetRaidRun = true; // we got it, shouldn't hit this again + break; } - break; } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + if (foundLooterResetRaidRun) { + group_itr = raidGroups.begin(); + foundLooterResetRaidRun = false; // disable running it again + if (entity->GetLootMethod() == GroupLootMethod::METHOD_LEADER) + break; } + else + group_itr++; } - group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); } } world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); @@ -6101,9 +6315,9 @@ void Client::CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier, f for (int8 i = 0; i < members->size(); i++) { Entity* member = members->at(i)->member; - if(!member) + if (!member) continue; - + if (!member->Alive() || (member->GetZone() != source->GetZone())) continue; // if we have a radius provided then check if the group member is outside the radius or not @@ -6158,26 +6372,26 @@ void Client::Bank(Spawn* banker, bool cancel) { } -bool Client::BankHasCoin(int64 amount){ +bool Client::BankHasCoin(int64 amount) { int32 tmp = 0; - - if(amount <= 0) + + if (amount <= 0) return 0; //plat if (amount >= 1000000) { tmp = amount / 1000000; int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); - - if(bank_coins_plat >= tmp) + + if (bank_coins_plat >= tmp) return 1; } //gold - if (amount >= 10000) { + if (amount >= 10000) { tmp = amount / 10000; int32 bank_coins_gold = GetPlayer()->GetBankCoinsGold(); - - if(bank_coins_gold >= tmp) + + if (bank_coins_gold >= tmp) return 1; } //silver @@ -6185,18 +6399,18 @@ bool Client::BankHasCoin(int64 amount){ tmp = amount / 100; int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); - if(bank_coins_silver >= tmp) + if (bank_coins_silver >= tmp) return 1; } //copper if (amount > 0) { int32 bank_coins_copper = GetPlayer()->GetBankCoinsCopper(); - - if(bank_coins_copper >= amount) + + if (bank_coins_copper >= amount) return 1; } -return 0; + return 0; } bool Client::BankWithdrawalNoBanker(int64 amount) { @@ -6240,7 +6454,7 @@ bool Client::BankWithdrawalNoBanker(int64 amount) { } if (!cheater && amount >= 100) { tmp = amount / 100; - int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); if (tmp > bank_coins_silver) cheater = true; else { @@ -6282,7 +6496,7 @@ bool Client::BankWithdrawalNoBanker(int64 amount) { } else Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); - return 0; + return 0; } return 0; } @@ -6328,7 +6542,7 @@ void Client::BankWithdrawal(int64 amount) { } if (!cheater && amount >= 100) { tmp = amount / 100; - int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); if (tmp > bank_coins_silver) cheater = true; else { @@ -6465,19 +6679,19 @@ void Client::AddPendingQuestAcceptReward(Quest* quest) } void Client::AddPendingQuestReward(Quest* quest, bool update, bool is_temporary, std::string description) { - QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0), - (is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0); + QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0), + (is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0); quest_updates = update; - if(quest_updates) { + if (quest_updates) { SaveQuestRewardData(true); } } void Client::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, int32 index) { - if(HasQuestRewardQueued(quest_id, is_temporary, is_collection)) + if (HasQuestRewardQueued(quest_id, is_temporary, is_collection)) return; - + QuestRewardData* data = new QuestRewardData; data->quest_id = quest_id; data->is_temporary = is_temporary; @@ -6494,42 +6708,42 @@ void Client::QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collect } bool Client::HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection) { - + bool success = false; MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); if (quest_pending_reward.size() > 0) { vector::iterator itr; - + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { int32 questID = (*itr)->quest_id; bool temporary = (*itr)->is_temporary; bool collection = (*itr)->is_collection; - if( questID == quest_id && is_temporary == temporary && is_collection == collection ) { + if (questID == quest_id && is_temporary == temporary && is_collection == collection) { success = true; break; } } } MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); - + return success; } void Client::RemoveQueuedQuestReward() { MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); - if(quest_pending_reward.size() > 0) { + if (quest_pending_reward.size() > 0) { QuestRewardData* data = quest_pending_reward.at(0); - if(data->db_saved) { + if (data->db_saved) { Query query; query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_rewards where char_id = %u and indexed = %u", GetCharacterID(), data->db_index); - if(data->is_temporary && data->quest_id) { + if (data->is_temporary && data->quest_id) { query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", GetCharacterID(), data->quest_id); } } quest_pending_reward.erase(quest_pending_reward.begin()); } MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); - + SaveQuestRewardData(true); } @@ -6542,7 +6756,7 @@ void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress } void Client::ProcessQuestUpdates() { - if(!IsReadyForUpdates()) + if (!IsReadyForUpdates()) return; if (quest_pending_updates.size() > 0) { @@ -6567,48 +6781,50 @@ void Client::ProcessQuestUpdates() { MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); if (quest_pending_reward.size() > 0) { MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); - + // only able to display one reward at a time - if(GetPlayer()->IsActiveReward()) + if (GetPlayer()->IsActiveReward()) return; - + Query query; vector::iterator itr; vector tmp_quest_rewards; MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); - tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin()+1); + tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin() + 1); MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); - + bool delete_first = false; for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end();) { int32 questID = (*itr)->quest_id; - if((*itr)->is_collection && GetPlayer()->GetPendingCollectionReward()) { + if ((*itr)->is_collection && GetPlayer()->GetPendingCollectionReward()) { DisplayCollectionComplete(GetPlayer()->GetPendingCollectionReward()); GetPlayer()->SetActiveReward(true); (*itr)->has_displayed = true; - + UpdateCharacterRewardData((*itr)); break; } - else if(questID > 0 && GetPlayer()->UpdateQuestReward(questID, (*itr))) { + else if (questID > 0 && GetPlayer()->UpdateQuestReward(questID, (*itr))) { (*itr)->has_displayed = true; UpdateCharacterRewardData((*itr)); // only able to display one reward at a time break; - } else { + } + else { delete_first = true; LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, deleting quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName()); break; } } - - if(delete_first) { + + if (delete_first) { RemoveQueuedQuestReward(); } - } else { + } + else { MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); } - + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); if (quest_pending_reward.size() > 0) { quest_updates = true; @@ -6625,7 +6841,7 @@ void Client::CheckQuestQueue() { last_update_time = 0; vector::iterator itr; for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) { - if(!GetPlayer()->SendQuestStepUpdate((*itr)->quest_id, (*itr)->step, (*itr)->display_quest_helper)) { + if (!GetPlayer()->SendQuestStepUpdate((*itr)->quest_id, (*itr)->step, (*itr)->display_quest_helper)) { LogWrite(CCLIENT__ERROR, 0, "Client", "Queued Quest ID %u missing for Player %s, cannot send quest step update.", (*itr)->quest_id, GetPlayer()->GetName()); } safe_delete((*itr)); @@ -6747,7 +6963,7 @@ void Client::AcceptQuest(int32 quest_id) { MPendingQuestAccept.lock_shared(); if (player->pending_quests.count(quest_id) > 0) { Quest* quest = player->pending_quests[quest_id]; - if(quest) { + if (quest) { MPendingQuestAccept.unlock_shared(); MPendingQuestAccept.lock(); player->pending_quests.erase(quest->GetQuestID()); @@ -6765,28 +6981,28 @@ void Client::AcceptQuest(int32 quest_id) { void Client::RemovePendingQuest(int32 quest_id) { bool send_updates = false; MPendingQuestAccept.lock_shared(); - + if (player->pending_quests.count(quest_id) > 0) { Quest* quest = player->pending_quests[quest_id]; MPendingQuestAccept.unlock_shared(); MPendingQuestAccept.lock(); player->pending_quests.erase(quest_id); MPendingQuestAccept.unlock(); - - if(lua_interface) { + + if (lua_interface) { lua_interface->CallQuestFunction(quest, "Declined", GetPlayer()); lua_interface->SetLuaUserDataStale(quest); } - + safe_delete(quest); - + send_updates = true; } else { MPendingQuestAccept.unlock_shared(); } - if(send_updates) { + if (send_updates) { GetCurrentZone()->SendQuestUpdates(this); } } @@ -6807,7 +7023,7 @@ void Client::SetPlayerQuest(Quest* quest, map* progress) { } if (lua_interface && step) lua_interface->CallQuestFunction(quest, "CurrentStep", player, step->GetStepID()); - else if(!step) { + else if (!step) { LogWrite(QUEST__ERROR, 0, "Client", "Missing step for quest %s (ID %u), cannot CallQuestFunction for CurrentStep", quest->GetName(), quest->GetQuestID()); } } @@ -6824,13 +7040,13 @@ void Client::AddPlayerQuest(Quest* quest, bool call_accepted, bool send_packets) RemovePlayerQuest(questID, false, false); } player->player_quests[quest->GetQuestID()] = quest; - - if(!lockCleared) + + if (!lockCleared) GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); - + quest->SetPlayer(player); quest->SetSaveNeeded(true); - + current_quest_id = quest->GetQuestID(); if (send_packets && quest->GetQuestGiver() > 0) GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); @@ -6941,7 +7157,7 @@ void Client::SendQuestUpdate(Quest* quest) { if (step->WasUpdated()) { // reversing the order of SendQuestJournal and QueuePacket QuestJournalReply causes AoM client to crash! SendQuestJournal(false, 0, true); - if(!updated) + if (!updated) QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step)); updated = true; } @@ -7005,11 +7221,11 @@ Quest* Client::GetPendingQuestAcceptance(int32 item_id) { Quest* quest = nullptr; for (itr = pending_quest_accept.begin(); itr != pending_quest_accept.end();) { questID = *itr; - + bool quest_exists = false; quest = GetPlayer()->PendingQuestAcceptance(questID, item_id, &quest_exists); - - if(!quest_exists) { + + if (!quest_exists) { LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, removing quest id from pending_quest_accept.", questID, GetPlayer()->GetName()); itr = pending_quest_accept.erase(itr); quest = nullptr; @@ -7019,7 +7235,7 @@ Quest* Client::GetPendingQuestAcceptance(int32 item_id) { pending_quest_accept.erase(itr); break; } - + itr++; } @@ -7039,10 +7255,10 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) { vector* items = 0; vector* tmpItems = 0; - + bool isTempState = quest->GetQuestTemporaryState(); - - if(isTempState) + + if (isTempState) { tmpItems = quest->GetTmpRewardItems(); if (tmpItems && tmpItems->size() > 0) @@ -7060,11 +7276,11 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) { totalItems += items->size(); } } - + RemoveQueuedQuestReward(); - + GetPlayer()->SetActiveReward(false); - + if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) { if (master_item) AddItem(item_id); @@ -7089,8 +7305,8 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) { else player->GetFactions()->DecreaseFaction(faction_id, (amount * -1)); } - - if(quest->GetQuestTemporaryState()) + + if (quest->GetQuestTemporaryState()) { int64 total_coins = quest->GetCoinTmpReward(); if (total_coins > 0) @@ -7099,9 +7315,9 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) { player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward()); } else { - player->GetInfoStruct()->add_status_points(quest->GetStatusPoints()); + player->GetInfoStruct()->add_status_points(quest->GetStatusPoints()); } - + quest->SetQuestTemporaryState(false); player->SetCharSheetChanged(true); } @@ -7121,10 +7337,10 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector* reward else*/ return;//nothing to give } - + GetPlayer()->ClearPendingSelectableItemRewards(0, true); GetPlayer()->ClearPendingItemRewards(); - + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); if (packet2) { int32 source_id = 0; @@ -7158,38 +7374,38 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector* reward } packet2->setSubstructDataByName("reward_data", "status_points", status_points); } - if(text) + if (text) packet2->setSubstructDataByName("reward_data", "text", text); - - + + std::vector items; quest->GetTmpRewardItemsByID(&items); - if(rewards || items.size() > 0){ + if (rewards || items.size() > 0) { int32 item_count = items.size(); item_count += rewards ? rewards->size() : 0; packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); int i = 0; - if(rewards) { + if (rewards) { for (i = 0; i < rewards->size(); i++) { Item* item = rewards->at(i); if (item) { packet2->setArrayDataByName("reward_id", item->details.item_id, i); packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); } - if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it } } - + for (int j = 0; j < items.size(); j++) { Item* item = items.at(j); if (item) { packet2->setArrayDataByName("reward_id", item->details.item_id, i); packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); } - if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it - + i++; } } @@ -7234,17 +7450,17 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector* reward } } -void Client::PopulateQuestRewardItems(vector * items, PacketStruct* packet, - std::string num_rewards_str, std::string reward_id_str, std::string item_str) { - if(!items || !packet) +void Client::PopulateQuestRewardItems(vector * items, PacketStruct* packet, + std::string num_rewards_str, std::string reward_id_str, std::string item_str) { + if (!items || !packet) return; - + if (items) { int32 total_item_count = 0; - for(int s=0;ssize();s++) { + for (int s = 0; s < items->size(); s++) { Item* tmpItem = items->at(s); - if(tmpItem) { - if(tmpItem->details.count > 1) { + if (tmpItem) { + if (tmpItem->details.count > 1) { total_item_count += tmpItem->details.count; } else { @@ -7263,25 +7479,25 @@ void Client::PopulateQuestRewardItems(vector * items, PacketStruct* packe packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos); else packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos, 0, 2); - + pos++; - - if(count >= items->at(i)->details.count-1) { + + if (count >= items->at(i)->details.count - 1) { count = 0; } - else if(items->at(i)->details.count > 1) { + else if (items->at(i)->details.count > 1) { count++; continue; } - + i++; } } } -void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) { +void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) { if (!quest) return; - + if (GetVersion() <= 561) { DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), tempReward ? customDescription.c_str() : quest->GetCompletedDescription(), was_displayed); return; @@ -7290,13 +7506,13 @@ void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string cus if (packet) { packet->setDataByName("title", "Quest Reward!"); packet->setDataByName("name", quest->GetName()); - if(tempReward) + if (tempReward) { packet->setDataByName("description", customDescription.c_str()); } else packet->setDataByName("description", quest->GetCompletedDescription()); - + packet->setDataByName("level", quest->GetLevel()); packet->setDataByName("encounter_level", quest->GetEncounterLevel()); int8 difficulty = 0; @@ -7306,7 +7522,7 @@ void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string cus difficulty = player->GetArrowColor(quest->GetLevel()); packet->setDataByName("difficulty", difficulty); - if(tempReward) + if (tempReward) { packet->setDataByName("max_coin", quest->GetCoinTmpReward()); packet->setDataByName("min_coin", quest->GetCoinTmpReward()); @@ -7327,16 +7543,16 @@ void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string cus packet->setDataByName("status_points", quest->GetStatusPoints()); } - if(tempReward) { + if (tempReward) { PopulateQuestRewardItems(quest->GetTmpRewardItems(), packet); } else { vector* items2 = quest->GetSelectableRewardItems(); PopulateQuestRewardItems(quest->GetRewardItems(), packet); - PopulateQuestRewardItems(quest->GetSelectableRewardItems(), packet, std::string("num_select_rewards"), - std::string("select_reward_id"), std::string("select_item")); - + PopulateQuestRewardItems(quest->GetSelectableRewardItems(), packet, std::string("num_select_rewards"), + std::string("select_reward_id"), std::string("select_item")); + map* reward_factions = quest->GetRewardFactions(); if (reward_factions && reward_factions->size() > 0) { packet->setArrayLengthByName("num_factions", reward_factions->size()); @@ -7424,23 +7640,23 @@ void Client::DisplayRandomizeFeatures(int32 flags) { void Client::GiveQuestReward(Quest* quest, bool has_displayed) { current_quest_id = 0; - if(!quest->GetQuestTemporaryState() && !has_displayed) + if (!quest->GetQuestTemporaryState() && !has_displayed) { quest->IncrementCompleteCount(); player->AddCompletedQuest(quest); } - + AddPendingQuestAcceptReward(quest); - + DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription()); LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); SendQuestJournal(); - - if(quest->GetQuestTemporaryState()) { + + if (quest->GetQuestTemporaryState()) { return; } - if(!has_displayed) { + if (!has_displayed) { if (quest->GetExpReward() > 0) { int32 xp = quest->GetExpReward(); player->AddXP(xp); @@ -7458,15 +7674,15 @@ void Client::GiveQuestReward(Quest* quest, bool has_displayed) { int64 total_coins = quest->GetGeneratedCoin(); if (total_coins > 0) AwardCoins(total_coins, std::string("for completing ").append(quest->GetName())); - + player->RemoveQuest(quest->GetQuestID(), false); } - + if (quest->GetQuestGiver() > 0) GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); - - if(!has_displayed) { - RemovePlayerQuest(quest->GetQuestID(), true, false); + + if (!has_displayed) { + RemovePlayerQuest(quest->GetQuestID(), true, false); } } @@ -7554,14 +7770,14 @@ void Client::CloseDialog(int32 conversation_id) { } std::map::iterator itr; - while((itr = conversation_items.find(conversation_id)) != conversation_items.end()) + while ((itr = conversation_items.find(conversation_id)) != conversation_items.end()) { conversation_items.erase(itr); } - + std::map::iterator itr2 = conversation_spawns.find(conversation_id); - while((itr2 = conversation_spawns.find(conversation_id)) != conversation_spawns.end()) + while ((itr2 = conversation_spawns.find(conversation_id)) != conversation_spawns.end()) { conversation_spawns.erase(itr2); } @@ -7680,7 +7896,7 @@ bool Client::AddItem(int32 item_id, int16 quantity, AddItemType type) { if (item) { if (quantity > 0) item->details.count = quantity; - + return AddItem(item, nullptr, type); } else @@ -7717,7 +7933,7 @@ bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) { lua_interface->SetLuaUserDataStale(item); // likely lore conflict - if(item_deleted) + if (item_deleted) *item_deleted = true; return false; @@ -7776,11 +7992,11 @@ void Client::UnequipItem(int16 index, sint32 bag_id, int8 to_slot, int8 appearan vector packets = GetPlayer()->UnequipItem(index, bag_id, to_slot, GetVersion(), appearance_equip); EQ2Packet* outapp = 0; - for(int32 i=0;iSendInventoryUpdate(version))) { QueuePacket(outapp); if (item->GetItemScript() && lua_interface) @@ -7909,7 +8125,7 @@ float Client::CalculateSellMultiplier(int32 merchant_id) { void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { Spawn* spawn = GetMerchantTransaction(); Guild* guild = GetPlayer()->GetGuild(); - if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && spawn->IsClientInMerchantLevelRange(this)) { int32 total_sell_price = 0; int32 total_status_sell_price = 0; //for status @@ -7926,12 +8142,12 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { if (!item) item = player->item_list.GetItemFromID(item_id); if (item && master_item) { - if(item->details.item_locked || item->details.equip_slot_id) + if (item->details.item_locked || item->details.equip_slot_id) { SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use."); return; } - else if(item->CheckFlag(NO_VALUE)) + else if (item->CheckFlag(NO_VALUE)) { SimpleMessage(CHANNEL_COLOR_RED, "This item has no value."); return; @@ -7961,7 +8177,7 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { total_status_sell_price = status_sell_price * quantity; - if(total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + if (total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) total_status_sell_price = 0; player->GetInfoStruct()->add_status_points(total_status_sell_price); @@ -7975,21 +8191,21 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { } if (quantity > 1) { - if(total_status_sell_price) + if (total_status_sell_price) Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s and %u Status Points.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); else Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); } else { - if(total_status_sell_price) + if (total_status_sell_price) Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s and %u Status Points.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); else Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); } player->AddCoins(total_sell_price); - if(!item->no_buy_back && (total_status_sell_price == 0 || (total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))))) + if (!item->no_buy_back && (total_status_sell_price == 0 || (total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))))) AddBuyBack(unique_id, item_id, quantity, sell_price); if (quantity >= item->details.count) { @@ -8003,7 +8219,7 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); if (outapp) QueuePacket(outapp); - + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) SendBuyBackList(); } @@ -8013,7 +8229,7 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { void Client::BuyBack(int32 item_id, int16 quantity) { Spawn* spawn = GetMerchantTransaction(); - if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && spawn->IsClientInMerchantLevelRange(this)) { deque::iterator itr; BuyBackItem* buyback = 0; @@ -8063,19 +8279,19 @@ void Client::BuyBack(int32 item_id, int16 quantity) { closest->save_needed = true; } itemAdded = AddItem(item, &itemDeleted); - + if (removed) { database.DeleteBuyBack(GetCharacterID(), closest->item_id, closest->quantity, closest->price); safe_delete(closest); } - + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) SendBuyBackList(); } else SimpleMessage(CHANNEL_COLOR_RED, "You cannot afford this item."); - if(!itemAdded && !itemDeleted) { + if (!itemAdded && !itemDeleted) { lua_interface->SetLuaUserDataStale(item); safe_delete(item); } @@ -8126,7 +8342,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) { SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item."); return; } - if(quantity < 1) + if (quantity < 1) { SimpleMessage(CHANNEL_COLOR_RED, "Merchant does not have item for purchase (quantity < 1)."); return; @@ -8151,13 +8367,13 @@ void Client::BuyItem(int32 item_id, int16 quantity) { Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_buy_price).c_str()); bool itemDeleted = false; AddItem(item, &itemDeleted); - if(!itemDeleted) { + if (!itemDeleted) { CheckPlayerQuestsItemUpdate(item); if (item && total_available < 0xFF) { world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); SendBuyMerchantList(); } - + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) PlayLotto(total_buy_price, item->details.item_id); } @@ -8248,13 +8464,13 @@ void Client::BuyItem(int32 item_id, int16 quantity) { Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(ItemInfo->price_coins * quantity).c_str()); bool itemDeleted = false; AddItem(item, &itemDeleted); - if(!itemDeleted) { + if (!itemDeleted) { CheckPlayerQuestsItemUpdate(item); if (item && total_available < 0xFF) { world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); SendBuyMerchantList(); } - + SendSellMerchantList(); if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) PlayLotto(total_buy_price, item->details.item_id); @@ -8286,7 +8502,7 @@ void Client::RepairItem(int32 item_id) { if (!item) item = player->GetEquipmentList()->GetItemFromItemID(item_id); if (item) { - if(item->CheckFlag2(NO_REPAIR)) { + if (item->CheckFlag2(NO_REPAIR)) { Message(CHANNEL_MERCHANT, "The mender was unable to repair your items."); PlaySound("buy_failed"); } @@ -8495,7 +8711,7 @@ void Client::SendBuyMerchantList(bool sell) { else tmp_level = item->generic_info.tradeskill_default_level; packet->setArrayDataByName("level", tmp_level, i); - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { packet->setArrayDataByName("tier", item->details.tier, i); } packet->setArrayDataByName("item_id2", item->details.item_id, i); @@ -8510,7 +8726,7 @@ void Client::SendBuyMerchantList(bool sell) { item_difficulty -= 6; if (item_difficulty < 0) item_difficulty *= -1; - + packet->setArrayDataByName("item_difficulty", item_difficulty, i); packet->setArrayDataByName("quantity", ItemInfo.quantity, i); packet->setArrayDataByName("unknown5", 255, i); @@ -8519,12 +8735,12 @@ void Client::SendBuyMerchantList(bool sell) { sint64 dispFlags = 0; if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, nullptr, &dispFlags)) packet->setArrayDataByName("display_flags", (int8)dispFlags, i); - + std::string overrideValueStr; // classic client isn't properly tracking this field, DoF we don't have it identified yet, but no field to cause any issues (can add later if identified) if (GetVersion() >= 546 && item->GetItemScript() && lua_interface && lua_interface->RunItemScriptWithReturnString(item->GetItemScript(), "item_description", item, player, &overrideValueStr)) packet->setArrayDataByName("description", overrideValueStr.c_str(), i); - + // If no price set in the merchant_inventory table then use the old method if (ItemInfo.price_item_id == 0 && ItemInfo.price_item2_id == 0 && ItemInfo.price_coins == 0 && ItemInfo.price_status == 0 && ItemInfo.price_stationcash == 0) { sell_price = (int32)(item->sell_price * multiplier); @@ -8616,7 +8832,7 @@ void Client::SendSellMerchantList(bool sell) { Spawn* spawn = GetMerchantTransaction(); if (!spawn || (spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY) || (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO)) return; - + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { map* items = player->GetItemList(); if (items) { @@ -8628,7 +8844,7 @@ void Client::SendSellMerchantList(bool sell) { bool isbagwithitems = false; if (test_itr->second && test_itr->second->IsBag() && (test_itr->second->details.num_slots - test_itr->second->details.num_free_slots != test_itr->second->details.num_slots)) isbagwithitems = true; - + if (test_itr->second && !test_itr->second->CheckFlag(NO_VALUE) && (isbagwithitems == false) && (test_itr->second->details.inv_slot_id != -3) && (test_itr->second->details.inv_slot_id != -4)) sellable_items.push_back(test_itr->second); } @@ -8660,7 +8876,7 @@ void Client::SendSellMerchantList(bool sell) { int8 dispFlags = 0; // only city merchants allow selling for status - if(item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT)) + if (item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT)) { packet->setArrayDataByName("status2", item->sell_status, i); //this one is the main status int32 guildMaxLevel = 5 + item->details.recommended_level; // client hard codes +5 to the level @@ -8669,17 +8885,17 @@ void Client::SendSellMerchantList(bool sell) { } } - if(item->no_buy_back || (item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + if (item->no_buy_back || (item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) { - if(GetVersion() < 1188) + if (GetVersion() < 1188) dispFlags += DISPLAY_FLAG_RED_TEXT; // for older clients it isn't "no buy back", you can either have 1 for red text or 255 for 'not for sale' to be checked else dispFlags += DISPLAY_FLAG_NO_BUYBACK; } - if(item->no_sale) + if (item->no_sale) dispFlags += DISPLAY_FLAG_NOT_FOR_SALE; - + packet->setArrayDataByName("display_flags", dispFlags, i); packet->setArrayDataByName("item_id", item->details.item_id, i); packet->setArrayDataByName("unique_item_id", item->details.unique_id, i); @@ -8690,19 +8906,19 @@ void Client::SendSellMerchantList(bool sell) { else tmp_level = item->generic_info.tradeskill_default_level; packet->setArrayDataByName("level", item->details.recommended_level, i); - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { packet->setArrayDataByName("tier", item->details.tier, i); } packet->setArrayDataByName("item_id2", item->details.item_id, i); item_difficulty = player->GetArrowColor(tmp_level); if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) item_difficulty = ARROW_COLOR_WHITE; - + sint64 overrideValue = 0; if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue)) item_difficulty = (sint8)overrideValue; - + item_difficulty -= 6; if (item_difficulty < 0) item_difficulty *= -1; @@ -8719,8 +8935,8 @@ void Client::SendSellMerchantList(bool sell) { if (GetVersion() < 561) { packet->setDataByName("type", 1); } - else if(GetVersion() == 561) { - packet->setDataByName("type", 1); + else if (GetVersion() == 561) { + packet->setDataByName("type", 1); } else { if (sell) @@ -8773,14 +8989,14 @@ void Client::SendBuyBackList(bool sell) { else tmp_level = master_item->generic_info.tradeskill_default_level; packet->setArrayDataByName("level", tmp_level, i); - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { packet->setArrayDataByName("tier", master_item->details.tier, i); } packet->setArrayDataByName("item_id2", master_item->details.item_id, i); item_difficulty = player->GetArrowColor(tmp_level); if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) item_difficulty = ARROW_COLOR_WHITE; - + sint64 overrideValue = 0; if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, nullptr, &overrideValue)) item_difficulty = (sint8)overrideValue; @@ -8793,7 +9009,7 @@ void Client::SendBuyBackList(bool sell) { sint64 dispFlags = 0; if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buyback_display_flags", master_item, player, nullptr, &dispFlags)) packet->setArrayDataByName("display_flags", (int8)dispFlags, i); - + if (buyback->quantity == 1) packet->setArrayDataByName("quantity", 0xFFFF, i); else @@ -8831,21 +9047,21 @@ void Client::SendRepairList() { vector::iterator itr; for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++, i++) { item = *itr; - + packet->setArrayDataByName("item_name", item->name.c_str(), i); packet->setArrayDataByName("price", item->CalculateRepairCost(), i); packet->setArrayDataByName("item_id", item->details.item_id, i); packet->setArrayDataByName("stack_size", item->details.count, i); packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); - + /*if (item->generic_info.adventure_default_level > 0) tmp_level = item->generic_info.adventure_default_level; else tmp_level = item->generic_info.tradeskill_default_level; packet->setArrayDataByName("level", tmp_level, i);*/ packet->setArrayDataByName("level", item->generic_info.adventure_default_level, i); - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { packet->setArrayDataByName("tier", item->details.tier, i); } packet->setArrayDataByName("item_id2", item->details.item_id, i); @@ -8871,16 +9087,16 @@ void Client::SendRepairList() { packet->setDataByName("type", 96); } EQ2Packet* outapp = packet->serialize(); - + //DumpPacket(outapp); QueuePacket(outapp); - + /*if (GetVersion() <= 561) { packet->setDataByName("type", 16); EQ2Packet* outapp2 = packet->serialize(); QueuePacket(outapp2); }*/ - + safe_delete(packet); } safe_delete(repairable_items); @@ -8889,7 +9105,7 @@ void Client::SendRepairList() { } void Client::ShowLottoWindow() { - if(GetVersion() <= 373) { + if (GetVersion() <= 373) { SimpleMessage(CHANNEL_COLOR_RED, "This client does not support the gambler UI, only Desert of Flames or later client."); return; } @@ -8930,8 +9146,8 @@ void Client::ShowLottoWindow() { packet->setArrayDataByName("stack_size", item->details.count); packet->setArrayDataByName("icon", item->GetIcon(GetVersion())); packet->setArrayDataByName("level", item->generic_info.adventure_default_level); - - if(rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { + + if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) { packet->setArrayDataByName("tier", item->details.tier); } packet->setArrayDataByName("item_id2", item->details.item_id); @@ -9122,9 +9338,9 @@ vector* Client::GetRepairableItems() { vector* Client::GetItemsByEffectType(ItemEffectType type, ItemEffectType type2) { - if(type == NO_EFFECT_TYPE) + if (type == NO_EFFECT_TYPE) return nullptr; - + vector* return_items = new vector; vector* equipped_items = player->GetEquipmentList()->GetAllEquippedItems(); map* items = player->GetItemList(); @@ -9166,11 +9382,11 @@ void Client::SendMailList() { p->setArrayDataByName("player_from", mail->player_from.c_str(), i); p->setArrayDataByName("subject", mail->subject.c_str(), i); p->setArrayDataByName("already_read", mail->already_read, i); - if(mail->expire_time) + if (mail->expire_time) p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i); else p->setArrayDataByName("mail_deletion", 0, i); - + p->setArrayDataByName("mail_type", mail->mail_type, i); p->setArrayDataByName("mail_expire", 0xFFFFFFFF, i); p->setArrayDataByName("unknown1a", 0xFFFFFFFF, i); @@ -9182,10 +9398,10 @@ void Client::SendMailList() { //p->setArrayDataByName("unknown2", 0, i); bool successItemAdd = false; - if(mail->stack || mail->char_item_id) + if (mail->stack || mail->char_item_id) { Item* item = master_item_list.GetItem(mail->char_item_id); - if(item) + if (item) { item->stack_count = mail->stack > 1 ? mail->stack : 0; if (version < 860) @@ -9194,12 +9410,12 @@ void Client::SendMailList() { p->setItemArrayDataByName("item", item, player, i); else p->setItemArrayDataByName("item", item, player, i, 0, 2); - + successItemAdd = true; } } - if(!successItemAdd) + if (!successItemAdd) { p->setArrayDataByName("end_tag2", GetItemPacketType(GetVersion()), i); p->setArrayDataByName("end_tag3", 0xFF, i); @@ -9244,7 +9460,7 @@ void Client::DisplayMailMessage(int32 mail_id) { QueuePacket(update->serialize()); safe_delete(update); } - if(!mail->already_read) { + if (!mail->already_read) { mail->already_read = true; SendMailList(); } @@ -9265,7 +9481,7 @@ void Client::DisplayMailMessage(int32 mail_id) { packet->setDataByName("coin_silver", mail->coin_silver); packet->setDataByName("coin_gold", mail->coin_gold); packet->setDataByName("coin_plat", mail->coin_plat); - if(mail->stack || mail->char_item_id) + if (mail->stack || mail->char_item_id) { Item* item = master_item_list.GetItem(mail->char_item_id); item->stack_count = mail->stack > 1 ? mail->stack : 0; @@ -9300,7 +9516,7 @@ void Client::DisplayMailMessage(int32 mail_id) { void Client::HandleSentMail(EQApplicationPacket* app) { PacketStruct* packet = configReader.getStruct("WS_MailSendMessage", GetVersion()); if (packet) { - if(packet->LoadPacketData(app->pBuffer, app->size)) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { string player_to = packet->getType_EQ2_16BitString_ByName("player_to").data; PacketStruct* reply_packet = configReader.getStruct("WS_MailSendMessageReply", GetVersion()); vector* ids = 0; @@ -9357,7 +9573,7 @@ void Client::HandleSentMail(EQApplicationPacket* app) { } mail->time_sent = Timer::GetUnixTimeStamp(); mail->expire_time = mail->time_sent + 2592000; //30 days in seconds - + mail->save_needed = false; database.SavePlayerMail(mail); Client* to_client = zone_list.GetClientByCharID(player_to_id); @@ -9407,15 +9623,15 @@ void Client::DeleteMail(int32 mail_id, bool from_database) { } bool Client::AddMailItem(Item* item) { - if(item && (item->CheckFlag(LORE) || item->CheckFlag(STACK_LORE))) { + if (item && (item->CheckFlag(LORE) || item->CheckFlag(STACK_LORE))) { Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Lore items cannot be mailed."); return false; } - + bool ret = false; if (GetMailTransaction()) { MMailWindowMutex.lock(); - if(mail_window.char_item_id == 0) + if (mail_window.char_item_id == 0) { mail_window.item = item; mail_window.char_item_id = item->details.item_id; @@ -9426,8 +9642,8 @@ bool Client::AddMailItem(Item* item) packet->setDataByName("coin_silver", mail_window.coin_silver); packet->setDataByName("coin_gold", mail_window.coin_gold); packet->setDataByName("coin_plat", mail_window.coin_plat); - - if(item) + + if (item) { packet->setDataByName("stack", mail_window.stack); item->stack_count = mail_window.stack; @@ -9453,7 +9669,7 @@ bool Client::AddMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { bool ret = false; if (GetMailTransaction()) { - MMailWindowMutex.lock(); + MMailWindowMutex.lock(); PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); if (packet) { if (copper > 0) { @@ -9489,9 +9705,9 @@ bool Client::AddMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { packet->setDataByName("coin_silver", mail_window.coin_silver); packet->setDataByName("coin_gold", mail_window.coin_gold); packet->setDataByName("coin_plat", mail_window.coin_plat); - Item* item = master_item_list.GetItem(mail_window.char_item_id); - if(item) - { + Item* item = master_item_list.GetItem(mail_window.char_item_id); + if (item) + { packet->setDataByName("stack", mail_window.stack); item->stack_count = mail_window.stack; if (version < 860) @@ -9500,12 +9716,12 @@ bool Client::AddMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { packet->setItemByName("item", item, player, 0, 0); else packet->setItemByName("item", item, player, 0, 2); - } - else - { - packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); - packet->setDataByName("end_tag3", 0xFF); - } + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } //packet->PrintPacket(); QueuePacket(packet->serialize()); } @@ -9629,13 +9845,13 @@ void Client::TakeMailAttachments(int32 mail_id) { } void Client::ResetSendMail(bool cancel, bool needslock) { - if(cancel && mail_transaction) + if (cancel && mail_transaction) SimpleMessage(CHANNEL_NARRATIVE, "You cancel sending a letter."); - if(needslock) + if (needslock) MMailWindowMutex.lock(); - if(cancel) + if (cancel) player->AddCoins(mail_window.coin_copper + (mail_window.coin_silver * 100) + (mail_window.coin_gold * 10000) + (mail_window.coin_plat * 1000000)); - if(!cancel) + if (!cancel) mail_transaction = 0; mail_window.coin_copper = 0; mail_window.coin_silver = 0; @@ -9644,43 +9860,43 @@ void Client::ResetSendMail(bool cancel, bool needslock) { mail_window.char_item_id = 0; mail_window.stack = 0; - if(mail_window.item){ - if(cancel) + if (mail_window.item) { + if (cancel) AddItem(mail_window.item); else safe_delete(mail_window.item); } mail_window.item = nullptr; - if(needslock) + if (needslock) MMailWindowMutex.unlock(); } bool Client::GateAllowed() { ZoneServer* zone = GetCurrentZone(); - if (zone){ - bool cangate = zone->GetCanGate(); - return cangate; + if (zone) { + bool cangate = zone->GetCanGate(); + return cangate; } - + return false; } bool Client::BindAllowed() { ZoneServer* zone = GetCurrentZone(); - if (zone){ - bool canbind = zone->GetCanBind(); - return canbind; - } - return false; + if (zone) { + bool canbind = zone->GetCanBind(); + return canbind; + } + return false; } bool Client::Bind() { int canbind = BindAllowed(); - - if(canbind == 0) { - Message(CHANNEL_MERCHANT, "You cannot bind at this location."); + + if (canbind == 0) { + Message(CHANNEL_MERCHANT, "You cannot bind at this location."); return false; - } + } player->GetPlayerInfo()->SetBindZone(GetCurrentZone()->GetZoneID()); player->GetPlayerInfo()->SetBindX(player->GetX()); player->GetPlayerInfo()->SetBindY(player->GetY()); @@ -9696,10 +9912,10 @@ bool Client::Gate(bool is_spell) { return false; } - ZoneServer* zone = zone_list.Get(player->GetPlayerInfo()->GetBindZoneID()); - if (zone) { + ZoneChangeDetails zone_details; + if (zone_list.GetZone(&zone_details, player->GetPlayerInfo()->GetBindZoneID())) { int cangate = GateAllowed(); - if(cangate == 0) { + if (cangate == 0) { SimpleMessage(CHANNEL_MERCHANT, "You can not cast recall spells in this zone."); return false; } @@ -9707,7 +9923,7 @@ bool Client::Gate(bool is_spell) { player->SetY(player->GetPlayerInfo()->GetBindZoneY()); player->SetZ(player->GetPlayerInfo()->GetBindZoneZ()); player->SetHeading(player->GetPlayerInfo()->GetBindZoneHeading()); - Zone(zone, false, is_spell); + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false, is_spell); return true; } @@ -9746,20 +9962,29 @@ void Client::ProcessTeleport(Spawn* spawn, vector* destin } } else { - ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); // determine if this is an instanced zone that already exists - ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(destination->destination_zone_id); - - if (instance_zone || new_zone) { + ZoneChangeDetails zone_details; + bool foundZone = world.GetGroupManager()->IdentifyMemberInGroupOrRaid(&zone_details, this, destination->destination_zone_id); + if (foundZone) { GetPlayer()->SetX(destination->destination_x); GetPlayer()->SetY(destination->destination_y); GetPlayer()->SetZ(destination->destination_z); GetPlayer()->SetHeading(destination->destination_heading); - if (instance_zone) - Zone(instance_zone->GetInstanceID(), false, true, is_spell); - else - Zone(new_zone, false, is_spell); + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false, is_spell); + } + else { + bool isZone = zone_list.GetZone(&zone_details, destination->destination_zone_id); + if (isZone) { + GetPlayer()->SetX(destination->destination_x); + GetPlayer()->SetY(destination->destination_y); + GetPlayer()->SetZ(destination->destination_z); + GetPlayer()->SetHeading(destination->destination_heading); + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false, is_spell); + } + else { + SimpleMessage(CHANNEL_COLOR_RED, "Error establishing a zone destination"); + } } } if (destination->message.length() > 0) @@ -9878,8 +10103,10 @@ void Client::ProcessTeleportLocation(EQApplicationPacket* app) { // Test if where we're going is an Instanced zone if (!TryZoneInstance(destination->destination_zone_id, false)) { LogWrite(INSTANCE__DEBUG, 0, "Instance", "Attempting to zone normally"); - ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); - Zone(new_zone, false); + ZoneChangeDetails zone_details; + if (zone_list.GetZone(&zone_details, destination->destination_zone_id)) { + Zone(&zone_details, (ZoneServer*)zone_details.zonePtr, false); + } } } if (destination->message.length() > 0) @@ -9967,7 +10194,7 @@ void Client::SearchStore(int32 page) { for (int32 i = 0; i < limit; i++, x++) { if (x >= search_items->size()) break; - + item = search_items->at(x); std::string teststr("test "); teststr.append(std::to_string(i)); @@ -9984,7 +10211,7 @@ void Client::SearchStore(int32 page) { else packet->setArrayDataByName("quantity", item->stack_count, i); packet->setArrayDataByName("stack_size", item->stack_count, i); - + packet->setArrayDataByName("sell_price", item->sell_price, i); @@ -10015,8 +10242,8 @@ void Client::SetReadyForUpdates() { } ready_for_updates = true; - - if(GetVersion() <= 561) { + + if (GetVersion() <= 561) { SendRecipeList(); } } @@ -10111,10 +10338,10 @@ void Client::SendIgnoreList() { } -void Client::AddWaypoint(string name, int8 type) { +void Client::AddWaypoint(string name, int8 type) { waypoint_id++; WaypointInfo info; - info.id = waypoint_id; + info.id = waypoint_id; info.type = type; waypoints[name] = info; } @@ -10128,7 +10355,7 @@ void Client::SendWaypoints() { for (itr = waypoints.begin(); itr != waypoints.end(); itr++) { packet->setArrayDataByName("waypoint_name", itr->first.c_str(), i); packet->setArrayDataByName("waypoint_category", itr->second.type, i); - packet->setArrayDataByName("spawn_id", itr->second.id, i); + packet->setArrayDataByName("spawn_id", itr->second.id, i); i++; } packet->setDataByName("unknown", 0xFFFFFFFF); @@ -10162,7 +10389,7 @@ void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int3 packet->setArrayDataByName("spawn_id", spawn_id, 0); packet->setArrayDataByName("waypoint_category2", waypoint_category, 0); packet->setArrayDataByName("spawn_id2", spawn_id, 0); - packet->setDataByName("unknown", 0xFFFFFFFF); + packet->setDataByName("unknown", 0xFFFFFFFF); QueuePacket(packet->serialize()); safe_delete(packet); } @@ -10173,7 +10400,7 @@ void Client::ClearWaypoint() { PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); if (packet) { QueuePacket(packet->serialize()); - safe_delete(packet); + safe_delete(packet); } } @@ -10222,7 +10449,7 @@ bool Client::ShowPathToTarget(float x, float y, float z, float y_offset) { bool Client::ShowPathToTarget(Spawn* spawn) { if (spawn) { - return ShowPathToTarget(spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetYOffset()); + return ShowPathToTarget(spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetYOffset()); } return false; } @@ -10252,13 +10479,13 @@ void Client::InspectPlayer(Player* player_to_inspect) { int target_pvp_alignment = player_to_inspect->GetPVPAlignment(); bool pvp_allowed = rule_manager.GetZoneRule(GetCurrentZoneID(), R_PVP, AllowPVP)->GetBool(); - if(pvp_allowed == true){ - if(source_pvp_alignment != target_pvp_alignment){ + if (pvp_allowed == true) { + if (source_pvp_alignment != target_pvp_alignment) { Message(CHANNEL_COLOR_RED, "You can not inspect players of different alignments."); return; } } - + if (player_to_inspect && player_to_inspect->GetClient()) { PacketStruct* packet = configReader.getStruct("WS_InspectPlayer", GetVersion()); if (packet) { @@ -10281,7 +10508,7 @@ void Client::InspectPlayer(Player* player_to_inspect) { packet->setDataByName("power_base", player_to_inspect->GetTotalPowerBase()); packet->setDataByName("mitigation", player_to_inspect->GetInfoStruct()->get_cur_mitigation()); packet->setDataByName("unknown1", 0); - packet->setDataByName("avoidance", player_to_inspect->GetInfoStruct()->get_avoidance_display()*10.0f); + packet->setDataByName("avoidance", player_to_inspect->GetInfoStruct()->get_avoidance_display() * 10.0f); packet->setDataByName("unknown2", 0); packet->setDataByName("mitigation_percentage", 0); packet->setDataByName("strength", player_to_inspect->GetStr()); @@ -10328,43 +10555,43 @@ void Client::InspectPlayer(Player* player_to_inspect) { string biography = player_to_inspect->GetBiography(); for (size_t i = 0; i < biography.length(); i++) packet->setArrayDataByName("biography_char", biography[i], i); - - if(GetVersion() <= 373) { - for(int32 s=0;s<20;s++) { + + if (GetVersion() <= 373) { + for (int32 s = 0; s < 20; s++) { int32 slot = s; - + char item_slot_name[64], item_slot_name_appearance[64]; - _snprintf(item_slot_name,64,"slot_%u",slot); + _snprintf(item_slot_name, 64, "slot_%u", slot); Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 7, true, true, true); } } - else if(GetVersion() <= 561) { - for(int32 s=0;s<22;s++) { + else if (GetVersion() <= 561) { + for (int32 s = 0; s < 22; s++) { int32 slot = s; - + char item_slot_name[64], item_slot_name_appearance[64]; - _snprintf(item_slot_name,64,"slot_%u",slot); + _snprintf(item_slot_name, 64, "slot_%u", slot); Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 5, true, true); } } else { - for(int32 s=0;sGetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, player_to_inspect->GetClient()->GetVersion())); packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 13, false, true); - if(s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) { + if (s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) { pw = player_to_inspect->GetAppearanceEquipmentList()->GetItem(s); - packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13, false, true); + packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13, false, true); } else { - packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13, false, true); + packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13, false, true); } } } @@ -10749,55 +10976,55 @@ void Client::DisplayCollectionComplete(Collection* collection) { int32 i; assert(collection); - + reward_items = collection->GetRewardItems(); selectable_reward_items = collection->GetSelectableRewardItems(); if (GetVersion() <= 561) { - int32 source_id = collection->GetID(); - PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); - if (packet2) { - packet2->setSubstructDataByName("reward_data", "unknown1", 255); - packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); - packet2->setSubstructDataByName("reward_data", "max_coin", collection->GetRewardCoin()); - packet2->setSubstructDataByName("reward_data", "exp_bonus", collection->GetRewardXP()); - - if(reward_items){ - int32 item_count = reward_items->size(); - packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); - i = 0; - if(reward_items) { - for (i = 0; i < reward_items->size(); i++) { - Item* item = reward_items->at(i)->item; - if (item) { - packet2->setArrayDataByName("reward_id", item->details.item_id, i); - packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); - } - } - } - } - if (selectable_reward_items) { - packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_reward_items->size()); - for (i = 0; i < selectable_reward_items->size(); i++) { - Item* item = selectable_reward_items->at(i)->item; + int32 source_id = collection->GetID(); + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "max_coin", collection->GetRewardCoin()); + packet2->setSubstructDataByName("reward_data", "exp_bonus", collection->GetRewardXP()); + + if (reward_items) { + int32 item_count = reward_items->size(); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); + i = 0; + if (reward_items) { + for (i = 0; i < reward_items->size(); i++) { + Item* item = reward_items->at(i)->item; if (item) { - packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); - packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); } } } } + if (selectable_reward_items) { + packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_reward_items->size()); + for (i = 0; i < selectable_reward_items->size(); i++) { + Item* item = selectable_reward_items->at(i)->item; + if (item) { + packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + } + } + } + } EQ2Packet* app = packet2->serialize(); QueuePacket(app); safe_delete(packet2); return; } - + if (!(packet = configReader.getStruct("WS_QuestComplete", version))) { return; } - + packet->setDataByName("title", "Quest Reward!"); packet->setDataByName("name", collection->GetName()); packet->setDataByName("description", collection->GetCategory()); @@ -10808,7 +11035,7 @@ void Client::DisplayCollectionComplete(Collection* collection) { packet->setArrayLengthByName("num_rewards", reward_items->size()); for (i = 0; i < reward_items->size(); i++) { reward_item = reward_items->at(i); - if(!reward_item->item) + if (!reward_item->item) { LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); @@ -10826,7 +11053,7 @@ void Client::DisplayCollectionComplete(Collection* collection) { packet->setArrayLengthByName("num_select_rewards", selectable_reward_items->size()); for (i = 0; i < selectable_reward_items->size(); i++) { reward_item = selectable_reward_items->at(i); - if(!reward_item->item) + if (!reward_item->item) { LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); @@ -10882,7 +11109,7 @@ void Client::HandInCollections() { break; } } - if(quest_updates) { + if (quest_updates) { SaveQuestRewardData(true); } } @@ -10940,28 +11167,28 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it /* reset the pending collection reward and check for my collections that the player needs to hand in */ player->SetPendingCollectionReward(0); - + RemoveQueuedQuestReward(); - + GetPlayer()->SetActiveReward(false); - + HandInCollections(); GetPlayer()->GetZone()->SendSubSpawnUpdates(SUBSPAWN_TYPES::COLLECTOR); } void Client::SendRecipeList() { - if(GetRecipeListSent()) { + if (GetRecipeListSent()) { return; } - + PacketStruct* packet = 0; map* recipes = player->GetRecipeList()->GetRecipes(); map::iterator itr; int16 i = 0; Recipe* recipe; - - if(version <= 561) { + + if (version <= 561) { PacketStruct* packet = 0; if (!(packet = configReader.getStruct("WS_UpdateRecipeBook", GetVersion()))) { return; @@ -11006,7 +11233,7 @@ void Client::SendRecipeList() { int8 even = level - level * .05 + .5; int8 easymin = level - level * .25 + .5; int8 veasymin = level - level * .35 + .5; - if (rlevel > level ) + if (rlevel > level) packet->setArrayDataByName("tier", 4, i); else if ((rlevel <= level) & (rlevel >= even)) packet->setArrayDataByName("tier", 3, i); @@ -11026,7 +11253,7 @@ void Client::SendRecipeList() { packet->setArrayDataByName("technique", recipe->GetTechnique(), i); packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); - + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); if (recipe_device != devices.end()) packet->setArrayDataByName("device_type", recipe_device - devices.begin(), i); @@ -11066,15 +11293,15 @@ void Client::ShowRecipeBook() { packet->setDataByName("device", target->GetName()); packet->setDataByName("unknown1", 1); auto res = std::find(devices.begin(), devices.end(), target->GetName()); - if (res != devices.end()){ + if (res != devices.end()) { index = res - devices.begin(); int32 deviceID = 0; - deviceID |= 1UL << index; + deviceID |= 1UL << index; //LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "GetDeviceID() = %u, deviceID = %u", ((Object*)target)->GetDeviceID(), deviceID); packet->setDataByName("unknown2", devices.size()); packet->setDataByName("unknown3", deviceID); } - else + else packet->setDataByName("unknown2", devices.size()); QueuePacket(packet->serialize()); safe_delete(packet); @@ -11117,14 +11344,14 @@ void Client::SendUpdateTitles(sint32 prefix, sint32 suffix) { Title* prefix_title = 0; if (suffix != -1) { suffix_title = player->GetPlayerTitles()->GetTitle(suffix); - if(suffix_title) + if (suffix_title) strcpy(player->appearance.suffix_title, suffix_title->GetName()); } else memset(player->appearance.suffix_title, 0, strlen(player->appearance.suffix_title)); if (prefix != -1) { prefix_title = player->GetPlayerTitles()->GetTitle(prefix); - if(prefix_title) + if (prefix_title) strcpy(player->appearance.prefix_title, prefix_title->GetName()); } else @@ -11139,8 +11366,8 @@ void Client::SendLanguagesUpdate(int32 id, bool setlang) { Language* language; int32 i = 0; - if(setlang==1){ - GetPlayer()->SetCurrentLanguage(id); + if (setlang == 1) { + GetPlayer()->SetCurrentLanguage(id); } PacketStruct* packet = configReader.getStruct("WS_Languages", GetVersion()); @@ -11190,7 +11417,7 @@ void Client::SendBiography() { PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); if (packet) { char biography[512]; - if(player->GetInfoStruct()->get_biography().size() < 1) + if (player->GetInfoStruct()->get_biography().size() < 1) { safe_delete(packet); return; @@ -11198,9 +11425,9 @@ void Client::SendBiography() { else { int16 size = player->GetInfoStruct()->get_biography().size(); - if(size>511) + if (size > 511) size = 511; - + strncpy(biography, player->GetInfoStruct()->get_biography().c_str(), player->GetInfoStruct()->get_biography().size()); biography[player->GetInfoStruct()->get_biography().size()] = '\0'; } @@ -11425,7 +11652,6 @@ int32 Client::GetTransmuteID() { bool Client::HandleNewLogin(int32 account_id, int32 access_code) { - printf("HandleNewLogin: AcctID: %i AccessCode: %i\n", account_id, access_code); ZoneAuthRequest* zar = zone_auth.GetAuth(account_id, access_code); if (zar) @@ -11453,7 +11679,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code) GetPlayer()->CalculateOfflineDebtRecovery(GetLastSavedTimeStamp()); GetPlayer()->vis_changed = false; GetPlayer()->info_changed = false; - + bool pvp_allowed = rule_manager.GetZoneRule(GetCurrentZoneID(), R_PVP, AllowPVP)->GetBool(); if (pvp_allowed) this->GetPlayer()->SetAttackable(1); @@ -11461,7 +11687,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code) if (client) { if (client->getConnection()) client->getConnection()->SendDisconnect(true); - + bool restore_ld_success = false; if (client->GetCurrentZone() && !client->IsZoning()) { //swap players, allowing the client to resume his LD'd player (ONLY if same version of the client) @@ -11472,7 +11698,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code) SetPlayer(client->GetPlayer()); GetPlayer()->SetClient(this); GetPlayer()->SetReturningFromLD(true); - + SetCharacterID(client->GetCharacterID()); SetAccountID(client->GetAccountID()); SetAdminStatus(client->GetAdminStatus()); @@ -11480,15 +11706,15 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code) client->SetPlayer(current_player); GetPlayer()->ResetSavedSpawns(); restore_ld_success = true; - + char tmpldname[128]; - snprintf(tmpldname, 128, "%s Linkdead",GetPlayer()->GetName()); + snprintf(tmpldname, 128, "%s Linkdead", GetPlayer()->GetName()); client->GetPlayer()->SetName(tmpldname, false); } ZoneServer* tmpZone = client->GetCurrentZone(); tmpZone->RemoveClientImmediately(client); } - if(!restore_ld_success && !database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { + if (!restore_ld_success && !database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { LogWrite(ZONE__ERROR, 0, "Zone", "Error reloading LD character and loading DB character: %s", player->GetName()); ClientPacketFunctions::SendLoginDenied(this); Disconnect(); @@ -11510,7 +11736,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code) GetCurrentZone()->AddClient(this); //add to zones client list zone_list.AddClientToMap(player->GetName(), this); // this initiates additional DB loading and client offloading within the Zone the player resides, need the client already added in zone and to the map above. - if(GetCurrentZone()->IsLoading()) { + if (GetCurrentZone()->IsLoading()) { new_client_login = NewLoginState::LOGIN_DELAYED; } else { @@ -11569,10 +11795,10 @@ void Client::SendSpawnChanges(set& spawns) { if (index == 0 || !GetPlayer()->WasSentSpawn(spawn->GetID())) continue; - if(GetPlayer() == spawn && GetVersion() <= 284) { // stopped self client/player warping in the EQ2 release disc (Beta), don't send yourself in bulk spawn updates + if (GetPlayer() == spawn && GetVersion() <= 284) { // stopped self client/player warping in the EQ2 release disc (Beta), don't send yourself in bulk spawn updates continue; } - + int16 tmp_info_size = 0; int16 tmp_pos_size = 0; int16 tmp_vis_size = 0; @@ -11614,7 +11840,7 @@ void Client::SendSpawnChanges(set& spawns) { } MakeSpawnChangePacket(tmp_info_changes, tmp_pos_changes, tmp_vis_changes, tmp_info_size, tmp_pos_size, data.size); - + for (auto& kv : tmp_info_changes) { safe_delete_array(kv.second.data); } @@ -11700,8 +11926,8 @@ void Client::MakeSpawnChangePacket(map info_changes, mapEmuToEQ(OP_EqUpdateGhostCmd); } int32 size = info_size + pos_size + vis_size + 8; @@ -11790,18 +12016,18 @@ void Client::SendHailCommand(Spawn* spawn) // hardcoded 'hail' entity commands switch (spawn->secondary_command_list_id) { - case 9: - case 1262: - case 1265: - case 1267: - { - EQ2_16BitString* command = new EQ2_16BitString(); - command->data = ""; - command->size = 0; - commands.Process(COMMAND_HAIL, command, this, spawn); - safe_delete(command); - break; - } + case 9: + case 1262: + case 1265: + case 1267: + { + EQ2_16BitString* command = new EQ2_16BitString(); + command->data = ""; + command->size = 0; + commands.Process(COMMAND_HAIL, command, this, spawn); + safe_delete(command); + break; + } } } @@ -11880,7 +12106,7 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object) tmp->SetSpawnOrigY(tmp->GetY()); tmp->SetSpawnOrigZ(tmp->GetZ()); tmp->SetSpawnOrigHeading(tmp->GetHeading()); - + database.SaveSpawnInfo(tmp); database.SaveSpawnEntry(tmp, "houseplacement", 100, 0, 0, 0); @@ -11888,7 +12114,7 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object) { GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), tmp->GetDatabaseID()); // we need to copy as to not delete the ZoneServer object_list entry this on house item pickup - GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy()); + GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy()); } return true; @@ -11918,18 +12144,18 @@ bool Client::PopulateHouseSpawnFinalize() query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID); } - if(uniqueItem->GetItemScript() && + if (uniqueItem->GetItemScript() && lua_interface->RunItemScript(uniqueItem->GetItemScript(), "placed", uniqueItem, GetPlayer(), tmp)) { uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); } - - if(uniqueItem) { + + if (uniqueItem) { database.DeleteItem(GetCharacterID(), uniqueItem, 0); GetPlayer()->item_list.RemoveItem(uniqueItem, true); QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); } - + SetPlacementUniqueItemID(0); } return true; @@ -11990,9 +12216,9 @@ void Client::SendShowBook(Spawn* sender, string title, int8 language, int8 num_p packet->setDataByName("book_title", title.c_str()); packet->setDataByName("book_type", "simple"); packet->setDataByName("unknown2", 1); - if(language > 0 && !GetPlayer()->HasLanguage(language)) + if (language > 0 && !GetPlayer()->HasLanguage(language)) packet->setDataByName("language", language); - + if (GetVersion() > 561) packet->setDataByName("unknown5", 1, 4); @@ -12007,28 +12233,28 @@ void Client::SendShowBook(Spawn* sender, string title, int8 language, int8 num_p switch (GetVersion()) { // release client - case 283: - case 373: // trial isle client - { - endString.append(page); - break; - } - // DoF trial - case 546: - case 561: - { - if (p == 0) - packet->setDataByName("cover_page", page.c_str()); - else - packet->setArrayDataByName("page_text", page.c_str(), p - 1); - break; - } - // all other clients - default: - { - packet->setArrayDataByName("page_text", page.c_str(), p); - break; - } + case 283: + case 373: // trial isle client + { + endString.append(page); + break; + } + // DoF trial + case 546: + case 561: + { + if (p == 0) + packet->setDataByName("cover_page", page.c_str()); + else + packet->setArrayDataByName("page_text", page.c_str(), p - 1); + break; + } + // all other clients + default: + { + packet->setArrayDataByName("page_text", page.c_str(), p); + break; + } } } @@ -12060,8 +12286,8 @@ void Client::SendShowBook(Spawn* sender, string title, int8 language, vectorsetDataByName("book_title", title.c_str()); packet->setDataByName("book_type", "simple"); packet->setDataByName("unknown2", 1); - - if(language > 0 && !GetPlayer()->HasLanguage(language)) + + if (language > 0 && !GetPlayer()->HasLanguage(language)) packet->setDataByName("language", language); if (GetVersion() > 561) @@ -12121,7 +12347,7 @@ void Client::ReplaceGroupClient(Client* new_client) { world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); - if(group) + if (group) { group->MGroupMembers.writelock(); rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; @@ -12146,7 +12372,7 @@ void Client::TempRemoveGroup() { world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); - if(group) + if (group) { group->MGroupMembers.writelock(); rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; @@ -12167,8 +12393,8 @@ void Client::TempRemoveGroup() } } -void Client::CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, -int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +void Client::CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) { Mail mail; mail.mail_id = 0; @@ -12196,11 +12422,11 @@ int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 ite mail.time_sent = time_sent; mail.expire_time = expire_time; - database.SavePlayerMail(&mail); + database.SavePlayerMail(&mail); } -void Client::CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, -int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +void Client::CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) { Mail* mail = new Mail(); mail->player_to_id = GetCharacterID(); @@ -12230,7 +12456,7 @@ int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 ite void Client::SendEquipOrInvUpdateBySlot(int8 slot) { - if(slot < NUM_SLOTS) + if (slot < NUM_SLOTS) { EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion(), GetPlayer()); if (app) @@ -12239,14 +12465,14 @@ void Client::SendEquipOrInvUpdateBySlot(int8 slot) else { EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); - if (outapp) - QueuePacket(outapp); + if (outapp) + QueuePacket(outapp); } } void Client::QueueStateCommand(int32 spawn_player_id, int32 state) { - if(spawn_player_id < 1) + if (spawn_player_id < 1) return; MQueueStateCmds.writelock(); @@ -12256,12 +12482,12 @@ void Client::QueueStateCommand(int32 spawn_player_id, int32 state) void Client::ProcessStateCommands() { - if(!IsReadyForUpdates()) + if (!IsReadyForUpdates()) return; MQueueStateCmds.writelock(); map::iterator itr = queued_state_commands.begin(); - for(; itr != queued_state_commands.end(); itr++) + for (; itr != queued_state_commands.end(); itr++) ClientPacketFunctions::SendStateCommand(this, itr->first, itr->second); queued_state_commands.clear(); @@ -12272,9 +12498,9 @@ void Client::PurgeItem(Item* item) { std::unique_lock lock(MConversation); map::iterator itr; - for(itr = conversation_items.begin(); itr != conversation_items.end(); itr++) + for (itr = conversation_items.begin(); itr != conversation_items.end(); itr++) { - if ( itr->second == item ) + if (itr->second == item) { conversation_items.erase(itr); break; @@ -12284,26 +12510,26 @@ void Client::PurgeItem(Item* item) void Client::ConsumeFoodDrink(Item* item, int32 slot) { - if(GetPlayer()->StopSaveSpellEffects()) + if (GetPlayer()->StopSaveSpellEffects()) return; - if(item) { - LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count); - if(item->GetItemScript() && lua_interface){ - lua_interface->RunItemScript(item->GetItemScript(), "cast", item, GetPlayer()); - if (slot == 22) { - Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str()); - GetPlayer()->SetActiveFoodUniqueID(item->details.unique_id); - } - else { - Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str()); - GetPlayer()->SetActiveDrinkUniqueID(item->details.unique_id); - } + if (item) { + LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count); + if (item->GetItemScript() && lua_interface) { + lua_interface->RunItemScript(item->GetItemScript(), "cast", item, GetPlayer()); + if (slot == 22) { + Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str()); + GetPlayer()->SetActiveFoodUniqueID(item->details.unique_id); } else { - Message(CHANNEL_NARRATIVE, "SERVER BUG! Item Script not assigned for consuming '%s'.", item->name.c_str()); - return; + Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str()); + GetPlayer()->SetActiveDrinkUniqueID(item->details.unique_id); } + } + else { + Message(CHANNEL_NARRATIVE, "SERVER BUG! Item Script not assigned for consuming '%s'.", item->name.c_str()); + return; + } if (item->details.count > 1) { item->details.count -= 1; @@ -12312,11 +12538,11 @@ void Client::ConsumeFoodDrink(Item* item, int32 slot) else { database.DeleteItem(GetPlayer()->GetCharacterID(), item, "EQUIPPED"); GetPlayer()->GetEquipmentList()->RemoveItem(slot, true); - + } GetPlayer()->SetCharSheetChanged(true); QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); - if(GetVersion() <= 373) { + if (GetVersion() <= 373) { EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); QueuePacket(outapp); } @@ -12325,7 +12551,7 @@ void Client::ConsumeFoodDrink(Item* item, int32 slot) void Client::AwardCoins(int64 total_coins, std::string reason) { - if (total_coins > 0) { + if (total_coins > 0) { player->AddCoins(total_coins); PlaySound("coin_cha_ching"); char tmp[64] = { 0 }; @@ -12359,18 +12585,18 @@ void Client::AwardCoins(int64 total_coins, std::string reason) message.append(reason); int8 type = CHANNEL_LOOT; SimpleMessage(type, message.c_str()); - } + } } void Client::TriggerSpellSave() { int32 interval = rule_manager.GetZoneRule(GetCurrentZoneID(), R_Spells, PlayerSpellSaveStateWaitInterval)->GetInt32(); // default to not have some bogus value in the rule - if(interval < 1) + if (interval < 1) interval = 100; - + MSaveSpellStateMutex.lock(); - if(!save_spell_state_timer.Enabled()) + if (!save_spell_state_timer.Enabled()) { save_spell_state_time_bucket = 0; save_spell_state_timer.Start(interval, true); @@ -12381,12 +12607,12 @@ void Client::TriggerSpellSave() save_spell_state_time_bucket += elapsed_time; int32 save_wait_cap = rule_manager.GetZoneRule(GetCurrentZoneID(), R_Spells, PlayerSpellSaveStateCap)->GetInt32(); - + // default to not have some bogus value in the rule - if(save_wait_cap < interval) - save_wait_cap = interval+1; - - if(save_spell_state_time_bucket >= save_wait_cap) + if (save_wait_cap < interval) + save_wait_cap = interval + 1; + + if (save_spell_state_time_bucket >= save_wait_cap) { save_spell_state_timer.Trigger(); } @@ -12397,7 +12623,7 @@ void Client::TriggerSpellSave() void Client::UpdateSentSpellList() { MSpellDetails.readlock(__FUNCTION__, __LINE__); std::map::iterator itr; - for(itr = sent_spell_details.begin(); itr != sent_spell_details.end(); itr++) { + for (itr = sent_spell_details.begin(); itr != sent_spell_details.end(); itr++) { Spell* spell = master_spell_list.GetSpell(itr->first, itr->second); EQ2Packet* app = spell->SerializeSpell(this, false, false); QueuePacket(app); @@ -12405,10 +12631,10 @@ void Client::UpdateSentSpellList() { MSpellDetails.releasereadlock(__FUNCTION__, __LINE__); } -void Client::SetTempPlacementSpawn(Spawn* tmp) { +void Client::SetTempPlacementSpawn(Spawn* tmp) { tempPlacementSpawn = tmp; hasSentTempPlacementSpawn = false; - if(tempPlacementSpawn) + if (tempPlacementSpawn) temp_placement_timer.Start(); else temp_placement_timer.Disable(); @@ -12425,7 +12651,7 @@ void Client::SetPlayer(Player* new_player) { bool Client::UseItem(Item* item, Spawn* target) { if (item && item->GetItemScript()) { int16 item_index = item->details.index; - if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { + if (!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); } else if (item->CheckFlag(EVIL_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { @@ -12443,15 +12669,15 @@ bool Client::UseItem(Item* item, Spawn* target) { std::string itemName = string(item->name); int32 item_id = item->details.item_id; sint64 flags = 0; - if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0) + if (lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0) { //reobtain item make sure it wasn't removed item = player->item_list.GetItemFromIndex(item_index); - if(!item) { + if (!item) { LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", GetPlayer()->GetName(), itemName.c_str(), item_id); return true; } - else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1) { + else if (!item->generic_info.display_charges && item->generic_info.max_charges == 1) { Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity return true; @@ -12461,7 +12687,7 @@ bool Client::UseItem(Item* item, Spawn* target) { item->details.count--; // charges item->save_needed = true; QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); - if(!item->details.count) { + if (!item->details.count) { Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity } @@ -12478,7 +12704,7 @@ bool Client::UseItem(Item* item, Spawn* target) { //reobtain item make sure it wasn't removed item = player->item_list.GetItemFromIndex(item_index); SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is out of charges."); - if(item) { + if (item) { LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however details.count is 0.", GetPlayer()->GetName(), item->name.c_str(), item->details.item_id); } } @@ -12487,80 +12713,80 @@ bool Client::UseItem(Item* item, Spawn* target) { return false; } -void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, - VoiceOverStruct* garble, bool success, bool garble_success) { - VoiceOverStruct* resStruct = nullptr; - - if(language == 0 || GetPlayer()->HasLanguage(language)) { - if(success) { - resStruct = non_garble; - } - } - else if(garble_success) { - resStruct = garble; - } - - if(resStruct) { - GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language); +void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, + VoiceOverStruct* garble, bool success, bool garble_success) { + VoiceOverStruct* resStruct = nullptr; + + if (language == 0 || GetPlayer()->HasLanguage(language)) { + if (success) { + resStruct = non_garble; } + } + else if (garble_success) { + resStruct = garble; + } + + if (resStruct) { + GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language); + } } void Client::SaveQuestRewardData(bool force_refresh) { - Query query; - if(force_refresh) { - query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u", - GetCharacterID()); - - query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u", - GetCharacterID()); - } - vector::iterator itr; - vector tmp_quest_rewards; - MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); - int index = 0; - for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { - int32 questID = (*itr)->quest_id; - if(!(*itr)->db_saved || force_refresh) { - query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %llu, %u, '%s')", - GetCharacterID(), index, questID, (*itr)->is_temporary, (*itr)->is_collection, (*itr)->has_displayed, (*itr)->tmp_coin, (*itr)->tmp_status, database.getSafeEscapeString((*itr)->description.c_str()).c_str()); - (*itr)->db_saved = true; - (*itr)->db_index = index; - if(questID && (*itr)->is_temporary) { - std::vector items; - GetPlayer()->GetQuestTemporaryRewards(questID, &items); - if(!force_refresh && items.size() > 0) { - query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u", - GetCharacterID(), questID); - } - for(int i=0;idetails.item_id, items[i]->details.count); - } + Query query; + if (force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u", + GetCharacterID()); + + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u", + GetCharacterID()); + } + vector::iterator itr; + vector tmp_quest_rewards; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + int index = 0; + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { + int32 questID = (*itr)->quest_id; + if (!(*itr)->db_saved || force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %llu, %u, '%s')", + GetCharacterID(), index, questID, (*itr)->is_temporary, (*itr)->is_collection, (*itr)->has_displayed, (*itr)->tmp_coin, (*itr)->tmp_status, database.getSafeEscapeString((*itr)->description.c_str()).c_str()); + (*itr)->db_saved = true; + (*itr)->db_index = index; + if (questID && (*itr)->is_temporary) { + std::vector items; + GetPlayer()->GetQuestTemporaryRewards(questID, &items); + if (!force_refresh && items.size() > 0) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u", + GetCharacterID(), questID); + } + for (int i = 0; i < items.size(); i++) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_temporary_rewards (char_id, quest_id, item_id, count) values(%u, %u, %u, %u)", + GetCharacterID(), questID, items[i]->details.item_id, items[i]->details.count); } } - index++; } - MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + index++; + } + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); } void Client::UpdateCharacterRewardData(QuestRewardData* data) { - - if(!data) + + if (!data) return; - if(data->db_saved) { + if (data->db_saved) { Query query; - query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %llu, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u", + query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %llu, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u", data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id); } } void Client::AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i) { - - + + int index = 0; - if(recipe == nullptr) + if (recipe == nullptr) return; - + PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); if (prl->GetRecipe(recipe->GetID())) { delete recipe; @@ -12597,26 +12823,26 @@ void Client::AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* packet->setArrayDataByName("recipe_name", recipe->GetName(), *i); packet->setArrayDataByName("recipe_book", recipe->GetBook(), *i); packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), *i); - if(i) { + if (i) { (*i)++; } } } bool Client::SetPlayerPOVGhost(Spawn* spawn) { - if(!spawn) { + if (!spawn) { pov_ghost_spawn_id = 0; SendCharPOVGhost(); return true; } - + int32 ghost_id = player->GetIDWithPlayerSpawn(spawn); - if(ghost_id) { + if (ghost_id) { pov_ghost_spawn_id = spawn->GetID(); SendCharPOVGhost(); return true; } - + return false; } @@ -12628,21 +12854,21 @@ void Client::HandleDialogSelectMsg(int32 conversation_id, int32 response_index) conversation = std::string(conversation_map[conversation_id][response_index].c_str()); conv_established = true; } - + int32 spawn_id = conversation_spawns[conversation_id]; - + Item* item = conversation_items[conversation_id]; MConversation.unlock_shared(); - + if (GetCurrentZone()) { Spawn* spawn = nullptr; - if(spawn_id) { + if (spawn_id) { spawn = GetCurrentZone()->GetSpawnByID(spawn_id); } - + if (conv_established) { if (spawn) { - if(conversation == "CloseItemConversation") { + if (conversation == "CloseItemConversation") { LogWrite(LUA__ERROR, 0, "LUA", "CloseItemConversation is an invalid function call for this conversation with spawn id %u", spawn_id); } else { @@ -12656,12 +12882,12 @@ void Client::HandleDialogSelectMsg(int32 conversation_id, int32 response_index) } else CloseDialog(conversation_id); - } + } } bool Client::SetPetName(const char* petName) { - int8 result = database.CheckNameFilter(petName,4,31); + int8 result = database.CheckNameFilter(petName, 4, 31); if (result == BADNAMELENGTH_REPLY) { SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 31."); return false; @@ -12688,67 +12914,67 @@ bool Client::SetPetName(const char* petName) { } bool Client::CheckConsumptionAllowed(int16 slot, bool send_message) { - switch(slot) { - case EQ2_FOOD_SLOT: { - if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { - if(send_message) { - Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); - } - return false; - } - break; - } - case EQ2_DRINK_SLOT: { - if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) - { - if(send_message) { - Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); - } - return false; - } - break; - } - default: { - if (GetVersion() <= 373) { - Item* item = GetPlayer()->item_list.GetItemFromIndex(slot); - if(item->IsFood()) { - if(item->IsFoodFood()) { - if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { - if(send_message) { - Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); - } - return false; - } - return true; - } - else if(item->IsFoodDrink()) { - if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) - { - if(send_message) { - Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); - } - return false; - } - return true; - } - } + switch (slot) { + case EQ2_FOOD_SLOT: { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if (send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); } return false; - break; } + break; } - + case EQ2_DRINK_SLOT: { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if (send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + break; + } + default: { + if (GetVersion() <= 373) { + Item* item = GetPlayer()->item_list.GetItemFromIndex(slot); + if (item->IsFood()) { + if (item->IsFoodFood()) { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if (send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); + } + return false; + } + return true; + } + else if (item->IsFoodDrink()) { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if (send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + return true; + } + } + } + return false; + break; + } + } + return true; } void Client::StartLinkdeadTimer() { - if(!linkdead_timer) { + if (!linkdead_timer) { int32 LD_Timer = rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, LinkDeadTimer)->GetInt32(); - LogWrite(CCLIENT__DEBUG, 0, "Client", "Starting linkdead timer for %s (timer %u seconds)", GetPlayer()->GetName(), (LD_Timer/1000)); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Starting linkdead timer for %s (timer %u seconds)", GetPlayer()->GetName(), (LD_Timer / 1000)); linkdead_timer = new Timer(LD_Timer); linkdead_timer->Enable(); - - if(GetPlayer()->GetGroupMemberInfo()) { + + if (GetPlayer()->GetGroupMemberInfo()) { LogWrite(CCLIENT__DEBUG, 0, "Client", "Telling player %s group they are disconnecting", GetPlayer()->GetName()); world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", GetPlayer()->GetName()); } @@ -12756,10 +12982,10 @@ void Client::StartLinkdeadTimer() { } bool Client::IsLinkdeadTimerEnabled() { - if(linkdead_timer) { + if (linkdead_timer) { return linkdead_timer->Enabled(); } - + return false; } @@ -12767,10 +12993,10 @@ void Client::SendNewAdventureSpells() { SendNewSpells(player->GetAdventureClass()); int8 base_class = classes.GetBaseClass(player->GetAdventureClass()); int secondary_class = classes.GetSecondaryBaseClass(player->GetAdventureClass()); - if(base_class != player->GetAdventureClass()) { + if (base_class != player->GetAdventureClass()) { SendNewSpells(base_class); } - if(secondary_class != player->GetAdventureClass() && secondary_class != base_class) { + if (secondary_class != player->GetAdventureClass() && secondary_class != base_class) { SendNewSpells(secondary_class); } } @@ -12778,33 +13004,33 @@ void Client::SendNewAdventureSpells() { void Client::SendNewTradeskillSpells() { SendNewTSSpells(player->GetTradeskillClass()); int8 secondary_class = classes.GetSecondaryTSBaseClass(player->GetTradeskillClass()); - if(secondary_class != player->GetTradeskillClass()) { + if (secondary_class != player->GetTradeskillClass()) { SendNewTSSpells(secondary_class); } } bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { Recipe* master_recipe = master_recipebook_list.GetRecipeBooks(recipe_book_id); - - if(master_recipe) { + + if (master_recipe) { Recipe* recipe_book = new Recipe(master_recipe); // if valid recipe book and the player doesn't have it if (recipe_book && recipe_book->GetLevel() > GetPlayer()->GetTSLevel()) { - if(item) { + if (item) { Message(CHANNEL_NARRATIVE, "Your tradeskill level is not high enough to scribe this book."); } safe_delete(recipe_book); } - else if(recipe_book && item && !recipe_book->CanUseRecipeByClass(item, GetPlayer()->GetTradeskillClass())) { - if(item) { + else if (recipe_book && item && !recipe_book->CanUseRecipeByClass(item, GetPlayer()->GetTradeskillClass())) { + if (item) { Message(CHANNEL_NARRATIVE, "Your tradeskill class cannot use this recipe."); } safe_delete(recipe_book); } - else if (recipe_book && (!item || !(GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)))){ + else if (recipe_book && (!item || !(GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)))) { LogWrite(PLAYER__DEBUG, 0, "Recipe", "Valid recipe book that the player doesn't have"); // Add recipe book to the players list - if(!GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)) { + if (!GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)) { GetPlayer()->GetRecipeBookList()->AddRecipeBook(recipe_book); } @@ -12823,7 +13049,7 @@ bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { else { LogWrite(PLAYER__ERROR, 0, "Recipe", "no recipes found for %s book", recipe_book->GetBookName()); } - + //Filter out duplicate recipes the player already has for (auto itr = recipes.begin(); itr != recipes.end();) { Recipe* recipe = *itr; @@ -12853,7 +13079,7 @@ bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Done adding recipes"); database.SavePlayerRecipeBook(GetPlayer(), recipe_book->GetBookID()); - if(item) { + if (item) { database.DeleteItem(GetCharacterID(), item, 0); GetPlayer()->item_list.RemoveItem(item, true); } @@ -12872,7 +13098,7 @@ bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { } } else { - LogWrite(PLAYER__ERROR, 0, "Player", "%u recipe book id does not exist. Cannot AddRecipeToPlayer.", recipe_book_id); + LogWrite(PLAYER__ERROR, 0, "Player", "%u recipe book id does not exist. Cannot AddRecipeToPlayer.", recipe_book_id); } return false; } @@ -12880,11 +13106,11 @@ bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); - + PacketStruct* packet = configReader.getStruct("WS_RecipeList", version); Recipe* recipe = prl->GetRecipe(recipe_id); int8 level = player->GetTSLevel(); - if(packet && recipe) { + if (packet && recipe) { packet->setDataByName("command_type", 1); packet->setArrayLengthByName("num_recipes", 1); int32 myid = recipe->GetID(); @@ -12892,7 +13118,7 @@ bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { int8 even = level - level * .05 + .5; int8 easymin = level - level * .25 + .5; int8 veasymin = level - level * .35 + .5; - if (rlevel > level ) + if (rlevel > level) packet->setArrayDataByName("tier", 4, 0); else if ((rlevel <= level) & (rlevel >= even)) packet->setArrayDataByName("tier", 3, 0); @@ -12912,7 +13138,7 @@ bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { packet->setArrayDataByName("technique", recipe->GetTechnique(), 0); packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), 0); - + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); if (recipe_device != devices.end()) packet->setArrayDataByName("device_type", recipe_device - devices.begin(), 0); @@ -12923,12 +13149,12 @@ bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { packet->setArrayDataByName("recipe_name", recipe->GetName(), 0); packet->setArrayDataByName("recipe_book", recipe->GetBook(), 0); packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), 0); - QueuePacket(packet->serialize()); + QueuePacket(packet->serialize()); } safe_delete(packet); - + bool res = prl->RemoveRecipe(recipe_id); - if(res) { + if (res) { Query query; query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "DELETE FROM character_recipes where char_id=%u and recipe_id=%u", GetCharacterID(), recipe_id); } @@ -12939,7 +13165,7 @@ void Client::SaveSpells() { MSaveSpellStateMutex.lock(); player->SaveSpellEffects(); player->SetSaveSpellEffects(true); - MSaveSpellStateMutex.unlock(); + MSaveSpellStateMutex.unlock(); } void Client::SendReplaceWidget(int32 widget_id, bool delete_widget, float x, float y, float z, int32 grid_id) { @@ -12955,90 +13181,90 @@ void Client::SendReplaceWidget(int32 widget_id, bool delete_widget, float x, flo EQ2Packet* ret = new_spawn->serialize(GetPlayer(), GetVersion()); QueuePacket(ret); - + // we have to delete spawn* references anyway, we don't keep this widget live in the spawn list GetPlayer()->RemoveSpawn(new_spawn, delete_widget); - + safe_delete(new_spawn); } void Client::ProcessZoneIgnoreWidgets() { GetPlayer()->MIgnoredWidgets.lock_shared(); std::map::iterator itr; - for(itr = GetPlayer()->ignored_widgets.begin(); itr != GetPlayer()->ignored_widgets.end(); itr++) { + for (itr = GetPlayer()->ignored_widgets.begin(); itr != GetPlayer()->ignored_widgets.end(); itr++) { SendReplaceWidget(itr->first, true); } GetPlayer()->MIgnoredWidgets.unlock_shared(); } void Client::PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i) { - if(!recipe || !packet) + if (!recipe || !packet) return; - - int8 level = player->GetTSLevel(); - int32 myid = recipe->GetID(); - int8 rlevel = recipe->GetLevel(); - int8 even = level - level * .05 + .5; - int8 easymin = level - level * .25 + .5; - int8 veasymin = level - level * .35 + .5; - if (rlevel > level ) - packet->setArrayDataByName("tier", 4, i); - else if ((rlevel <= level) & (rlevel >= even)) - packet->setArrayDataByName("tier", 3, i); - else if ((rlevel <= even) & (rlevel >= easymin)) - packet->setArrayDataByName("tier", 2, i); - else if ((rlevel <= easymin) & (rlevel >= veasymin)) - packet->setArrayDataByName("tier", 1, i); - else if ((rlevel <= veasymin) & (rlevel >= 0)) - packet->setArrayDataByName("tier", 0, i); - if (rlevel == 2) - int xxx = 1; - packet->setArrayDataByName("recipe_id", myid, i); - packet->setArrayDataByName("level", recipe->GetLevel(), i); - packet->setArrayDataByName("icon", recipe->GetIcon(), i); - packet->setArrayDataByName("classes", recipe->GetClasses(), i); - packet->setArrayDataByName("technique", recipe->GetTechnique(), i); - packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); - packet->setArrayDataByName("device", recipe->GetDevice(), i); - packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); - packet->setArrayDataByName("recipe_name", recipe->GetName(), i); - packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); - packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); - - packet->setArrayDataByName("book_volume", 0x01, i); - packet->setArrayDataByName("device_id", 0x01, i); + + int8 level = player->GetTSLevel(); + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level) + packet->setArrayDataByName("tier", 4, i); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, i); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, i); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, i); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, i); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, i); + packet->setArrayDataByName("level", recipe->GetLevel(), i); + packet->setArrayDataByName("icon", recipe->GetIcon(), i); + packet->setArrayDataByName("classes", recipe->GetClasses(), i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); + packet->setArrayDataByName("device", recipe->GetDevice(), i); + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); + + packet->setArrayDataByName("book_volume", 0x01, i); + packet->setArrayDataByName("device_id", 0x01, i); } int32 Client::GetRecipeCRC(Recipe* recipe) { - + PacketStruct* packet = 0; if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { return 0; } packet->setArrayLengthByName("num_recipes", 1); - + PopulateRecipeData(recipe, packet); - + string* generic_string_data = packet->serializeString(); int32 size = generic_string_data->length(); - - if(size < 5) + + if (size < 5) return 0; - - uchar* out_data = new uchar[size+1]; + + uchar* out_data = new uchar[size + 1]; uchar* out_ptr = out_data; - memcpy(out_ptr, (uchar*)generic_string_data->c_str()+4, generic_string_data->length()-4); - uint32 out_crc = GenerateCRCRecipe(0, (void*)out_ptr, size-4); + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + 4, generic_string_data->length() - 4); + uint32 out_crc = GenerateCRCRecipe(0, (void*)out_ptr, size - 4); safe_delete(packet); safe_delete_array(out_data); - + return out_crc; } void Client::SendRecipeDetails(vector* recipes) { - if(!recipes || recipes->size() == 0) + if (!recipes || recipes->size() == 0) return; - + PacketStruct* packet = 0; if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { return; @@ -13048,12 +13274,12 @@ void Client::SendRecipeDetails(vector* recipes) { int16 i = 0; int32 count = 0; vector::iterator recipe_itr; - for(recipe_itr = recipes->begin(); recipe_itr != recipes->end(); recipe_itr++) { + for (recipe_itr = recipes->begin(); recipe_itr != recipes->end(); recipe_itr++) { Recipe* recipe = player->GetRecipeList()->GetRecipe(*recipe_itr); - if(!recipe) { + if (!recipe) { continue; } - else if(i > 99) { + else if (i > 99) { QueuePacket(packet->serialize()); safe_delete(packet); packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()); @@ -13065,41 +13291,44 @@ void Client::SendRecipeDetails(vector* recipes) { PopulateRecipeData(recipe, packet, i); i++; } - + //packet->PrintPacket(); - + QueuePacket(packet->serialize()); safe_delete(packet); } -ZoneServer* Client::GetHouseZoneServer(int32 spawn_id, int64 house_id) { +bool Client::GetHouseZoneServer(ZoneChangeDetails* zone_details, int32 spawn_id, int64 house_id) { PlayerHouse* ph = nullptr; HouseZone* hz = nullptr; - if(spawn_id) { + if (spawn_id) { Spawn* houseWidget = GetPlayer()->GetSpawnByIndex(spawn_id); - if(houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { + if (houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID()); if (hz) { ph = world.GetPlayerHouseByHouseID(GetPlayer()->GetCharacterID(), hz->id); - } else { + } + else { Message(CHANNEL_COLOR_YELLOW, "HouseWidget spawn index %u house zone could not be found.", spawn_id); } } } - - if(!ph && house_id) { + + if (!ph && house_id) { ph = world.GetPlayerHouseByUniqueID(house_id); if (ph) { hz = world.GetHouseZone(ph->house_id); } } - + if (ph && hz) { - ZoneServer* house = zone_list.GetByInstanceID(ph->instance_id, hz->zone_id, false, true); - return house; + if (zone_list.GetZoneByInstance(zone_details, ph->instance_id, hz->zone_id)) { + return true; + } + return false; } - - return nullptr; + + return false; } void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time) { @@ -13107,21 +13336,21 @@ void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int1 if (packet) { int32 caster_id = GetPlayer()->GetIDWithPlayerSpawn(caster); int32 target_id = GetPlayer()->GetIDWithPlayerSpawn(target); - + packet->setDataByName("spawn_id", caster_id); packet->setArrayLengthByName("num_targets", 1); packet->setArrayDataByName("target", target_id); packet->setDataByName("num_targets", 1); int32 visual = GetSpellVisualOverride(spell_visual); - + packet->setDataByName("spell_visual", visual); //result - packet->setDataByName("cast_time", cast_time*.01f); //delay + packet->setDataByName("cast_time", cast_time * .01f); //delay packet->setDataByName("spell_id", 1); packet->setDataByName("spell_level", 1); packet->setDataByName("spell_tier", 1); EQ2Packet* outapp = packet->serialize(); - + //DumpPacket(outapp); QueuePacket(outapp); safe_delete(packet); @@ -13130,15 +13359,50 @@ void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int1 int32 Client::GetSpellVisualOverride(int32 spell_visual) { int32 visual = spell_visual; - if(GetVersion() <= 561) { // spell's spell_visual field is based on newer clients, DoF has to convert + if (GetVersion() <= 561) { // spell's spell_visual field is based on newer clients, DoF has to convert Emote* spellVisualEmote = visual_states.FindSpellVisualByID(visual, 60085); - if(spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) { + if (spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) { spellVisualEmote = visual_states.FindSpellVisual(spellVisualEmote->GetMessageString(), GetVersion()); - if(spellVisualEmote) { + if (spellVisualEmote) { visual = (int32)spellVisualEmote->GetVisualState(); } } } - + return visual; +} + +void Client::HandleGroupAcceptResponse(int8 result) { + if (result == 0) + SimpleMessage(CHANNEL_GROUP_CHAT, "You have joined the group."); + else if (result == 1) + SimpleMessage(CHANNEL_GROUP_CHAT, "You do not have a pending invite."); + else if (result == 2) + SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - could not find leader."); + else + SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - unknown error."); +} + +void Client::SetGroupOptionsReference(GroupOptions* options) { + if (options) { + options->loot_method = GetPlayer()->GetInfoStruct()->get_group_loot_method(); + options->loot_items_rarity = GetPlayer()->GetInfoStruct()->get_group_loot_items_rarity(); + options->auto_split = GetPlayer()->GetInfoStruct()->get_group_auto_split(); + options->default_yell = GetPlayer()->GetInfoStruct()->get_group_default_yell(); + options->group_autolock = GetPlayer()->GetInfoStruct()->get_group_autolock(); + options->group_lock_method = GetPlayer()->GetInfoStruct()->get_group_lock_method(); + options->solo_autolock = GetPlayer()->GetInfoStruct()->get_group_solo_autolock(); + options->auto_loot_method = GetPlayer()->GetInfoStruct()->get_group_auto_loot_method(); + } +} + +void Client::SendReceiveOffer(Client* target_client, int8 type, std::string name, int8 unknown2) { + PacketStruct* packet = configReader.getStruct("WS_ReceiveOffer", target_client->GetVersion()); + if (packet) { + packet->setDataByName("type", type); + packet->setDataByName("name", name.c_str()); + packet->setDataByName("unknown2", unknown2); + target_client->QueuePacket(packet->serialize()); + } + safe_delete(packet); } \ No newline at end of file diff --git a/source/WorldServer/client.h b/source/WorldServer/client.h index cffb112..7bba81b 100644 --- a/source/WorldServer/client.h +++ b/source/WorldServer/client.h @@ -32,11 +32,14 @@ #include "Player.h" #include "Quests.h" +#include "Web/PeerManager.h" + using namespace std; #define CLIENT_TIMEOUT 60000 struct TransportDestination; struct ConversationOption; struct VoiceOverStruct; +struct GroupOptions; #define MAIL_SEND_RESULT_SUCCESS 0 #define MAIL_SEND_RESULT_UNKNOWN_PLAYER 1 @@ -165,13 +168,14 @@ public: void QueuePacket(EQ2Packet* app, bool attemptedCombine=false); void SendLoginInfo(); int8 GetMessageChannelColor(int8 channel_type); - void HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id); + void HandleTellMessage(const char* fromName, const char* message, const char* to, int32 current_language_id); void SimpleMessage(int8 color, const char* message); void Message(int8 type, const char* message, ...); void SendSpellUpdate(Spell* spell, bool add_silently = false, bool add_to_hotbar = true); - void Zone(ZoneServer* new_zone, bool set_coords = true, bool is_spell = false); + 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 ApproveZone(); void SendZoneInfo(); void SendZoneSpawns(); void HandleVerbRequest(EQApplicationPacket* app); @@ -194,7 +198,7 @@ public: void ChangeTSLevel(int16 old_level, int16 new_level); bool Summon(const char* search_name); std::string IdentifyInstanceLockout(int32 zoneID, bool displayClient = true); - ZoneServer* IdentifyInstance(int32 zoneID); + 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); void DisplayDeadWindow(); @@ -588,6 +592,15 @@ public: int32 GetSpellVisualOverride(int32 spell_visual); sint16 GetClientItemPacketOffset() { sint16 offset = -1; if(GetVersion() <= 373) { offset = -2; } return offset; } + + int32 GetZoningID() { return zoning_id; } + int32 GetZoningInstanceID() { return zoning_instance_id; } + + void SetZoningDetails(ZoneChangeDetails* details) { zoning_details = ZoneChangeDetails(details); } + + void HandleGroupAcceptResponse(int8 result); + void SetGroupOptionsReference(GroupOptions* options); + void SendReceiveOffer(Client* client_target, int8 type, std::string name, int8 unknown2); private: void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i); void SavePlayerImages(); @@ -657,7 +670,9 @@ private: bool seencharsel; bool connected_to_zone; - bool client_zoning; + std::atomic client_zoning; + std::atomic client_zoning_details_set; + ZoneChangeDetails zoning_details; int32 zoning_id; int32 zoning_instance_id; ZoneServer* zoning_destination; @@ -691,7 +706,7 @@ private: string* pending_last_name; IncomingPaperdollImage incoming_paperdoll; int32 transmuteID; - ZoneServer* GetHouseZoneServer(int32 spawn_id, int64 house_id); + bool GetHouseZoneServer(ZoneChangeDetails* zone_details, int32 spawn_id, int64 house_id); std::atomic m_recipeListSent; bool initial_spawns_sent; diff --git a/source/WorldServer/makefile b/source/WorldServer/makefile index b8f661f..c60399d 100644 --- a/source/WorldServer/makefile +++ b/source/WorldServer/makefile @@ -19,7 +19,7 @@ Lua_C_Flags = -DLUA_COMPAT_ALL -DLUA_USE_LINUX Lua_W_Flags = -Wall C_Flags = -I/eq2emu/fmt/include -I/eq2emu/recastnavigation/Detour/Include -I/usr/include/mariadb -I/usr/local/include/boost -I/usr/include/glm -I/usr/include/lua5.4 -march=native -pipe -pthread -std=c++17 -LD_Flags = -L/usr/lib/x86_64-linux-gnu -lmariadb -lz -lpthread -L/eq2emu/recastnavigation/RecastDemo/Build/gmake2/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -llua5.4-c++ -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex +LD_Flags = -L/usr/lib/x86_64-linux-gnu -lmariadb -lz -lpthread -L/eq2emu/recastnavigation/RecastDemo/Build/gmake2/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -llua5.4-c++ -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex -lboost_program_options # World flags W_Flags = -Wall -Wno-reorder diff --git a/source/WorldServer/net.cpp b/source/WorldServer/net.cpp index d922298..57a589e 100644 --- a/source/WorldServer/net.cpp +++ b/source/WorldServer/net.cpp @@ -20,6 +20,7 @@ #include "../common/debug.h" #include "../common/Log.h" +#include #include using namespace std; #include @@ -59,6 +60,9 @@ using namespace std; #include "Transmute.h" #include "Zone/ChestTrap.h" +#include "Web/PeerManager.h" +#include "Web/HTTPSClientPool.h" + //devn00b #ifdef DISCORD //linux only for the moment. @@ -111,6 +115,8 @@ extern MasterSkillList master_skill_list; extern MasterItemList master_item_list; extern GuildList guild_list; extern Variables variables; +extern PeerManager peer_manager; +extern HTTPSClientPool peer_https_pool; ConfigReader configReader; int32 MasterItemList::next_unique_id = 0; int last_signal = 0; @@ -125,6 +131,7 @@ extern map EQOpcodeVersions; ThreadReturnType ItemLoad (void* tmp); ThreadReturnType AchievmentLoad (void* tmp); ThreadReturnType SpellLoad (void* tmp); +ThreadReturnType StartPeerPoll (void* tmp); //devn00b #ifdef DISCORD #ifndef WIN32 @@ -133,6 +140,7 @@ ThreadReturnType SpellLoad (void* tmp); #endif int main(int argc, char** argv) { + net.is_primary = true; #ifdef PROFILER PROFILE_FUNC(); #endif @@ -184,8 +192,8 @@ int main(int argc, char** argv) { LogWrite(WORLD__DEBUG, 0, "World", "Randomizing World..."); srand(time(NULL)); - net.ReadLoginINI(); - + net.ReadLoginINI(argc, argv); + // JA: Grouping all System (core) data loads together for timing purposes LogWrite(WORLD__INFO, 0, "World", "Loading System Data..."); int32 t_now = Timer::GetUnixTimeStamp(); @@ -371,6 +379,13 @@ int main(int argc, char** argv) { world.world_loaded = true; world.world_uptime = getCurrentTimestamp(); + #ifdef WIN32 + _beginthread(StartPeerPoll, 0, NULL); + #else + pthread_t thread; + pthread_create(&thread, NULL, &StartPeerPoll, NULL); + pthread_detach(thread); + #endif } else { LogWrite(NET__ERROR, 0, "Net", "Failed to open port %i.", net.GetWorldPort()); @@ -384,8 +399,11 @@ int main(int argc, char** argv) { TimeoutTimer->Start(); EQStream* eqs = 0; UpdateWindowTitle(0); - LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); - database.LoadSpecialZones(); + + if(net.is_primary) { + database.LoadSpecialZones(); + } + map connecting_clients; map::iterator cc_itr; @@ -469,7 +487,7 @@ int main(int argc, char** argv) { database.PingNewDB(); database.PingAsyncDatabase(); - if (net.LoginServerInfo && loginserver.Connected() == false && loginserver.CanReconnect()) { + if (net.is_primary && net.LoginServerInfo && loginserver.Connected() == false && loginserver.CanReconnect()) { LogWrite(WORLD__DEBUG, 0, "Thread", "Starting autoinit loginserver thread..."); #ifdef WIN32 _beginthread(AutoInitLoginServer, 0, NULL); @@ -495,6 +513,8 @@ int main(int argc, char** argv) { } LogWrite(WORLD__DEBUG, 0, "World", "The world is ending!"); + peer_https_pool.stopPolling(); + LogWrite(WORLD__DEBUG, 0, "World", "Shutting down zones..."); zone_list.ShutDownZones(); @@ -612,6 +632,14 @@ ThreadReturnType AchievmentLoad (void* tmp) THREAD_RETURN(NULL); } +ThreadReturnType StartPeerPoll (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Start Polling..."); + peer_https_pool.startPolling(); + THREAD_RETURN(NULL); +} + + ThreadReturnType EQ2ConsoleListener(void* tmp) { char cmd[300]; @@ -663,7 +691,7 @@ void CatchSignal(int sig_num) { } } -bool NetConnection::ReadLoginINI() { +bool NetConnection::ReadLoginINI(int argc, char** argv) { JsonParser parser(MAIN_CONFIG_FILE); if(!parser.IsLoaded()) { LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); @@ -730,6 +758,43 @@ bool NetConnection::ReadLoginINI() { web_keypassword = parser.getValue("worldserver.webkeypassword"); web_hardcodeuser = parser.getValue("worldserver.webhardcodeuser"); web_hardcodepassword = parser.getValue("worldserver.webhardcodepassword"); + web_cmduser = parser.getValue("worldserver.webcmduser"); + web_cmdpassword = parser.getValue("worldserver.webcmdpassword"); + + std::string base_peeraddress = "worldserver.peeraddress"; + std::string base_peerport = "worldserver.peerport"; + + for (int i = -1; i <= 100; ++i) { + std::string peeraddress = base_peeraddress; + std::string peerport = base_peerport; + if(i > -1){ + peeraddress = base_peeraddress + std::to_string(i); + peerport = base_peerport + std::to_string(i); + } + + // Assuming parser.getValue can handle the concatenated strings. + std::string web_peeraddress = parser.getValue(peeraddress); + std::string web_peerport = parser.getValue(peerport); + if(web_peeraddress.size() > 0 && web_peerport.size() > 0) { + int16 port = 0; + parser.convertStringToUnsignedShort(web_peerport, port); + if(port > 0) { + web_peers[web_peeraddress] = port; + LogWrite(INIT__INFO, 0, "Init", "Adding peer %s:%u...", web_peeraddress.c_str(), port); + } + else { + LogWrite(INIT__ERROR, 0, "Init", "Error peer %s:%u at position %i, skipped.", web_peeraddress.c_str(), port, i); + } + } + else { + break; + } + } + + std::string webpeerpriority_str = parser.getValue("worldserver.peerpriority"); + parser.convertStringToUnsignedShort(webpeerpriority_str, web_peerpriority); + + peer_https_pool.init(web_certfile, web_keyfile); std::string webloginport_str = parser.getValue("worldserver.webport"); parser.convertStringToUnsignedShort(webloginport_str, web_worldport); @@ -737,6 +802,54 @@ bool NetConnection::ReadLoginINI() { std::string defaultstatus_str = parser.getValue("worldserver.defaultstatus"); parser.convertStringToUnsignedChar(defaultstatus_str, DEFAULTSTATUS); + + // Define namespace for ease of use + namespace po = boost::program_options; + + // Variables to store parsed options + std::string worldAddress(""); + std::string internalWorldAddress(""); + std::string webWorldAddress(""); + uint16 worldPort = 0; + uint16 webWorldPort = 0; + uint16 peerPriority = 0; + + // Setup options + po::options_description desc("Allowed options"); + desc.add_options() + ("worldaddress", po::value(&worldAddress), "World address") + ("internalworldaddress", po::value(&internalWorldAddress), "Internal world address") + ("worldport", po::value(&worldPort)->default_value(0), "Web world port") + ("webworldaddress", po::value(&webWorldAddress), "Web world address") + ("webworldport", po::value(&webWorldPort)->default_value(0), "Web world port") + ("peerpriority", po::value(&peerPriority)->default_value(0), "Peer priority"); + + // Parse the arguments + po::variables_map vm; + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } + catch (const po::error &e) { + std::cerr << "Error parsing options: " << e.what() << "\n"; + std::cout << desc << "\n"; + } + if(peerPriority) + web_peerpriority = peerPriority; + if(webWorldPort) + web_worldport = webWorldPort; + if(worldPort) + worldport = worldPort; + + if(worldAddress.size() > 0) + snprintf(worldaddress, sizeof(worldaddress), "%s", worldAddress.c_str()); + + if(internalWorldAddress.size() > 0) + snprintf(internalworldaddress, sizeof(internalworldaddress), "%s", internalWorldAddress.c_str()); + + if(webWorldAddress.size() > 0) + web_worldaddress = webWorldAddress; + LogWrite(INIT__DEBUG, 0, "Init", "%s read...", MAIN_CONFIG_FILE); LoginServerInfo=1; return true; @@ -763,6 +876,11 @@ char* NetConnection::GetLoginInfo(int16* oPort) { return loginaddress[tmp[x]]; } +void NetConnection::SetPrimary(bool isprimary) { + net.is_primary = isprimary; + database.LoadSpecialZones(); +} + void UpdateWindowTitle(char* iNewTitle) { char tmp[500]; diff --git a/source/WorldServer/net.h b/source/WorldServer/net.h index d3bad05..e66872d 100644 --- a/source/WorldServer/net.h +++ b/source/WorldServer/net.h @@ -34,6 +34,9 @@ #include #endif +#include +#include + #include "../common/linked_list.h" #include "../common/types.h" @@ -64,12 +67,14 @@ public: LoginServerInfo = 0;//ReadLoginINI(); UpdateStats = false; web_worldport = 0; + web_peerpriority = 0; } ~NetConnection() { } - bool ReadLoginINI(); + bool ReadLoginINI(int argc, char** argv); void WelcomeHeader(); - + void SetPrimary(bool isprimary = true); + bool LoginServerInfo; bool UpdateStats; char* GetLoginInfo(int16* oPort); @@ -89,7 +94,12 @@ public: std::string GetWebKeyPassword() { return web_keypassword; } std::string GetWebHardcodeUser() { return web_hardcodeuser; } std::string GetWebHardcodePassword() { return web_hardcodepassword; } + std::string GetCmdUser() { return web_cmduser; } + std::string GetCmdPassword() { return web_cmdpassword; } + std::map GetWebPeers() { std::map copied_map(web_peers); return copied_map; } + int16 GetPeerPriority() { return web_peerpriority; } bool world_locked; + std::atomic is_primary; private: int listening_socket; char loginaddress[4][255]; @@ -107,7 +117,11 @@ private: std::string web_keypassword; std::string web_hardcodeuser; std::string web_hardcodepassword; + std::string web_cmduser; + std::string web_cmdpassword; + std::map web_peers; int16 web_worldport; + int16 web_peerpriority; }; diff --git a/source/WorldServer/zoneserver.cpp b/source/WorldServer/zoneserver.cpp index 1eef0d2..f2a65fd 100644 --- a/source/WorldServer/zoneserver.cpp +++ b/source/WorldServer/zoneserver.cpp @@ -167,18 +167,26 @@ ZoneServer::ZoneServer(const char* name) { strcpy(zonesky_file,""); reloading = true; + spawnthread_active = false; + movementMgr = nullptr; + spellProcess = nullptr; + tradeskillMgr = nullptr; watchdogTimestamp = Timer::GetCurrentTime2(); MPendingSpawnRemoval.SetName("ZoneServer::MPendingSpawnRemoval"); lifetime_client_count = 0; + + is_initialized = false; } typedef map ChangedSpawnMapType; ZoneServer::~ZoneServer() { zoneShuttingDown = true; //ensure other threads shut down too //allow other threads to properly shut down - LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); + if(is_initialized) { + LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); + } int32 disp_count = 0; int32 next_disp_count = 100; while (spawnthread_active || initial_spawn_threads_active > 0){ @@ -246,7 +254,9 @@ ZoneServer::~ZoneServer() { grid_maps.clear(); MGridMaps.unlock(); - LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name); + if(is_initialized) { + LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name); + } --numzones; UpdateWindowTitle(0); zone_list.Remove(this); @@ -276,6 +286,8 @@ void ZoneServer::Init() { LogWrite(ZONE__INFO, 0, "Zone", "Loading new Zone '%s'", zone_name); zone_list.Add(this); + + is_initialized = true; spellProcess = new SpellProcess(); tradeskillMgr = new TradeskillMgr(); @@ -337,6 +349,17 @@ void ZoneServer::Init() pathing = IPathfinder::Load(zoneName); movementMgr = new MobMovementManager(); + if(GetInstanceID()) { + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetInstanceID()); + if(ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if(hz) { + std::string desc = ph->player_name + "'s " + hz->name; + SetZoneDescription((char*)desc.c_str()); + } + } + } + MMasterSpawnLock.SetName("ZoneServer::MMasterSpawnLock"); m_npc_faction_list.SetName("ZoneServer::npc_faction_list"); m_enemy_faction_list.SetName("ZoneServer::enemy_faction_list"); @@ -1414,10 +1437,10 @@ void ZoneServer::LootProcess(Spawn* spawn) { looter = entry; } if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { - world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %u on %s.", entry->GetName(), out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + world.GetGroupManager()->SendGroupChatMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %u on %s.", entry->GetName(), out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); } else { - world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %s (%u) on %s.", entry->GetName(), itemNeed ? "NEED" : "GREED", out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + world.GetGroupManager()->SendGroupChatMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %s (%u) on %s.", entry->GetName(), itemNeed ? "NEED" : "GREED", out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); } } @@ -1493,6 +1516,10 @@ void ZoneServer::DeleteSpawns(bool delete_all) { continue; spawn = itr->first; + + lua_interface->SetLuaUserDataStale(spawn); + spellProcess->RemoveCaster(spawn); + if(movementMgr != nullptr) { movementMgr->RemoveMob((Entity*)spawn); } @@ -1508,8 +1535,6 @@ void ZoneServer::DeleteSpawns(bool delete_all) { spawn_delete_list.erase(erase_itr); MSpawnList.writelock(__FUNCTION__, __LINE__); - lua_interface->SetLuaUserDataStale(spawn); - std::map::iterator sitr = spawn_list.find(spawn->GetID()); if(sitr != spawn_list.end()) { spawn_list.erase(sitr); @@ -1530,7 +1555,6 @@ void ZoneServer::DeleteSpawns(bool delete_all) { housing_spawn_map.erase(spawn->GetID()); } MSpawnList.releasewritelock(__FUNCTION__, __LINE__); - spellProcess->RemoveCaster(spawn); safe_delete(spawn); } else @@ -1710,8 +1734,12 @@ bool ZoneServer::Process() } // client loop - if(charsheet_changes.Check()) + if(charsheet_changes.Check()) { SendCharSheetChanges(); + } + else { + SendRaidSheetChanges(); + } // Client loop ClientProcess(startupDelayTimer.Enabled()); @@ -1998,15 +2026,13 @@ void ZoneServer::CheckRespawns(){ for(int i=tmp_respawn_list.size()-1;i>=0;i--){ if ( IsInstanceZone() ) { - if ( database.DeleteInstanceSpawnRemoved(GetInstanceID(),tmp_respawn_list[i]) ) - { - } - else - { - } + database.DeleteInstanceSpawnRemoved(GetInstanceID(),tmp_respawn_list[i]); + } + else { + database.DeletePersistedRespawn(GetZoneID(),tmp_respawn_list[i]); } - ProcessSpawnLocation(tmp_respawn_list[i], true); + ProcessSpawnLocation(tmp_respawn_list[i], nullptr, nullptr, nullptr, nullptr, nullptr, true); respawn_timers.erase(tmp_respawn_list[i]); } } @@ -2291,6 +2317,23 @@ void ZoneServer::SendPlayerPositionChanges(Player* player){ } } +void ZoneServer::SendRaidSheetChanges(){ + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + Client* client = (Client*)(*client_itr); + if(client && client->IsConnected() && client->GetPlayer()->GetRaidSheetChanged()) { + client->GetPlayer()->SetRaidSheetChanged(false); + EQ2Packet* packet = client->GetPlayer()->GetRaidUpdatePacket(client->GetVersion()); + if(packet) { + client->QueuePacket(packet); + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + void ZoneServer::SendCharSheetChanges(){ vector::iterator client_itr; @@ -2301,9 +2344,11 @@ void ZoneServer::SendCharSheetChanges(){ } void ZoneServer::SendCharSheetChanges(Client* client){ - if(client && client->IsConnected() && client->GetPlayer()->GetCharSheetChanged()){ - client->GetPlayer()->SetCharSheetChanged(false); - ClientPacketFunctions::SendCharacterSheet(client); + if(client && client->IsConnected()){ + if(client->GetPlayer()->GetCharSheetChanged()) { + client->GetPlayer()->SetCharSheetChanged(false); + ClientPacketFunctions::SendCharacterSheet(client); + } } } @@ -2377,7 +2422,7 @@ int32 ZoneServer::CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn MSpawnLocationList.readlock(__FUNCTION__, __LINE__); for (itr = locations->begin(); itr != locations->end(); itr++) { if(spawn_location_list.count(itr->second) > 0){ - spawn = ProcessSpawnLocation(spawn_location_list[itr->second], respawn); + spawn = ProcessSpawnLocation(spawn_location_list[itr->second], nullptr, nullptr, nullptr, nullptr, nullptr, respawn); if(!leader && spawn) leader = spawn; if(leader) @@ -2400,7 +2445,7 @@ int32 ZoneServer::CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn return group; } -void ZoneServer::ProcessSpawnLocation(int32 location_id, bool respawn) +void ZoneServer::ProcessSpawnLocation(int32 location_id, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn) { LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); @@ -2442,12 +2487,12 @@ void ZoneServer::ProcessSpawnLocation(int32 location_id, bool respawn) LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); - ProcessSpawnLocation(spawn_location_list[location_id], respawn); + ProcessSpawnLocation(spawn_location_list[location_id], instNPCs, instGroundSpawns, instObjSpawns, instWidgetSpawns, instSignSpawns, respawn); } MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); } -Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn) +Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn) { LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); @@ -2461,7 +2506,23 @@ Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respa { if(spawnlocation->entities[i]->spawn_percentage == 0) continue; - + + + int32 spawnTime = 0; + + if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id); + + if(spawnTime == 0) { // don't respawn + return nullptr; + } + else if(spawnTime > 1) { // if not 1, respawn after time + AddRespawn(spawnlocation->entities[i]->spawn_location_id, spawnTime); + return nullptr; + } + if (spawnlocation->conditional > 0) { if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_DAY && isDusk) continue; @@ -2635,6 +2696,13 @@ void ZoneServer::ProcessSpawnLocations() instWidgetSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_WIDGET ); instSignSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_SIGN ); } + else { + instNPCs = database.GetPersistedSpawns(this->GetZoneID() , SPAWN_ENTRY_TYPE_NPC ); + instGroundSpawns = database.GetPersistedSpawns(this->GetZoneID() , SPAWN_ENTRY_TYPE_GROUNDSPAWN ); + instObjSpawns = database.GetPersistedSpawns(this->GetZoneID() , SPAWN_ENTRY_TYPE_OBJECT ); + instWidgetSpawns = database.GetPersistedSpawns(this->GetZoneID() , SPAWN_ENTRY_TYPE_WIDGET ); + instSignSpawns = database.GetPersistedSpawns(this->GetZoneID() , SPAWN_ENTRY_TYPE_SIGN ); + } map processed_spawn_locations; map::iterator itr; @@ -2683,7 +2751,7 @@ void ZoneServer::ProcessSpawnLocations() else { //LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessSpawnLocation (%u)...", itr->second->placement_id); - ProcessSpawnLocation(itr->second); + ProcessSpawnLocation(itr->second,instNPCs,instGroundSpawns,instObjSpawns,instWidgetSpawns,instSignSpawns); } } } @@ -3288,13 +3356,14 @@ void ZoneServer::CheckTransporters(Client* client) { client->QueuePacket(packet); } else{ - ZoneServer* new_zone = zone_list.Get(loc->destination_zone_id); - if(new_zone){ + ZoneChangeDetails zone_details; + bool foundZone = zone_list.GetZone(&zone_details, loc->destination_zone_id); + if(foundZone){ client->GetPlayer()->SetX(loc->destination_x); client->GetPlayer()->SetY(loc->destination_y); client->GetPlayer()->SetZ(loc->destination_z); client->GetPlayer()->SetHeading(loc->destination_heading); - client->Zone(new_zone, false); + client->Zone(&zone_details, (ZoneServer*)zone_details.zonePtr); } } break; @@ -3543,8 +3612,10 @@ void ZoneServer::RemoveClient(Client* client) map::iterator itr; for (itr = client->GetPlayer()->SpawnedBots.begin(); itr != client->GetPlayer()->SpawnedBots.end(); itr++) { Spawn* spawn = GetSpawnByID(itr->second); - if (spawn) + if (spawn && spawn->IsBot()) { + ((Entity*)spawn)->SetOwner(nullptr); ((Bot*)spawn)->Camp(); + } } if(dismissPets) { @@ -3598,7 +3669,7 @@ void ZoneServer::ClientProcess(bool ignore_shutdown_timer) { MIncomingClients.readlock(__FUNCTION__, __LINE__); bool shutdownDelayCheck = shutdownDelayTimer.Check(); - if((!AlwaysLoaded() && !shutdownTimer.Enabled()) || shutdownDelayCheck) + if((!AlwaysLoaded() && !shutdownTimer.Enabled()) || (!AlwaysLoaded() && shutdownDelayCheck)) { if(incoming_clients && !shutdownDelayTimer.Enabled()) { LogWrite(ZONE__INFO, 0, "Zone", "Incoming clients (%u) expected for %s, delaying shutdown timer...", incoming_clients, GetZoneName()); @@ -3620,6 +3691,9 @@ void ZoneServer::ClientProcess(bool ignore_shutdown_timer) } } } + else if(AlwaysLoaded() && shutdownTimer.Enabled()) { + shutdownTimer.Disable(); + } MIncomingClients.releasereadlock(__FUNCTION__, __LINE__); return; } @@ -3751,6 +3825,36 @@ void ZoneServer::HandleChatMessage(Client* client, Spawn* from, const char* to, } } +void ZoneServer::HandleChatMessage(Client* client, std::string fromName, const char* to, int16 channel, const char* message, float distance, const char* channel_name, int32 language) { + if (!client->GetPlayer()->IsIgnored(fromName.c_str())) { + PacketStruct* packet = configReader.getStruct("WS_HearChat", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("from", fromName.c_str()); + + int8 clientchannel = client->GetMessageChannelColor(channel); + packet->setDataByName("channel", client->GetMessageChannelColor(channel)); + packet->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet->setMediumStringByName("message", message); + packet->setDataByName("language", language); + + bool hasLanguage = client->GetPlayer()->HasLanguage(language); + if (language > 0 && !hasLanguage) + packet->setDataByName("understood", 0); + else + packet->setDataByName("understood", 1); + + packet->setDataByName("show_bubble", 0); + if (channel_name) + packet->setMediumStringByName("channel_name", channel_name); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } +} + void ZoneServer::HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language){ vector::iterator client_itr; Client* client = 0; @@ -3764,6 +3868,19 @@ void ZoneServer::HandleChatMessage(Spawn* from, const char* to, int16 channel, c MClientList.releasereadlock(__FUNCTION__, __LINE__); } +void ZoneServer::HandleChatMessage(std::string fromName, const char* to, int16 channel, const char* message, float distance, const char* channel_name, int32 language){ + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + HandleChatMessage(client, fromName, to, channel, message, distance, channel_name, language); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + void ZoneServer::HandleBroadcast(const char* message) { vector::iterator client_itr; Client* client = 0; @@ -4582,33 +4699,9 @@ void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool if(erase_from_spawn_list) AddPendingSpawnRemove(spawn->GetID()); - if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0) - { + if(respawn && !spawn->IsPlayer() && spawn->GetSpawnLocationID() > 0) { LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn for '%s'.", spawn->GetName()); - - // handle instance spawn db info - // we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs. - if(spawn->GetZone()->GetInstanceID() > 0 && spawn->GetSpawnLocationID() > 0) - { - LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn in an Instance."); - // use respawn time to either insert/update entry (likely insert in this situation) - if ( spawn->IsNPC() ) - { - database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, - spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); - } - else if ( spawn->IsObject ( ) ) - { - database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, - spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); - } - } - else - { - int32 spawnLocationID = spawn->GetSpawnLocationID(); - int32 spawnRespawnTime = spawn->GetRespawnTime(); - respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000); - } + AddRespawn(spawn); } RemoveSpawnFromGrid(spawn, spawn->GetLocation()); @@ -5145,6 +5238,12 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo else if ( dead->IsObject ( ) ) database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID()); } + else if(!groupMemberAlive && dead->GetSpawnLocationID() > 0) { + if(dead->IsNPC()) + database.CreatePersistedRespawn(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC,dead->GetRespawnTime(),GetZoneID()); + else if(dead->IsObject()) + database.CreatePersistedRespawn(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT,dead->GetRespawnTime(),GetZoneID()); + } // Call the spawn scripts death() function CallSpawnScript(dead, SPAWN_SCRIPT_DEATH, killer); @@ -8662,7 +8761,7 @@ void ZoneServer::ProcessSpawnConditional(int8 condition) { SpawnLocation* loc = itr2->second; if (loc && loc->conditional > 0 && ((loc->conditional & condition) == condition)) if (GetSpawnByLocationID(loc->placement_id) == NULL) - ProcessSpawnLocation(loc); + ProcessSpawnLocation(loc, nullptr, nullptr, nullptr, nullptr, nullptr); } MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); @@ -9127,3 +9226,13 @@ void ZoneServer::AddIgnoredWidget(int32 id) { ignored_widgets.insert(make_pair(id,true)); } } + +void ZoneServer::AddRespawn(Spawn* spawn) { + AddRespawn(spawn->GetSpawnLocationID(), spawn->GetRespawnTime()); +} + +void ZoneServer::AddRespawn(int32 locationID, int32 respawnTime) { + if(locationID > 0 && respawnTime > 0) { + respawn_timers.Put(locationID, Timer::GetCurrentTime2() + respawnTime * 1000); + } +} \ No newline at end of file diff --git a/source/WorldServer/zoneserver.h b/source/WorldServer/zoneserver.h index 59ffdd1..1651ae0 100644 --- a/source/WorldServer/zoneserver.h +++ b/source/WorldServer/zoneserver.h @@ -298,6 +298,9 @@ public: void SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender = true); void HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); void HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); + void HandleChatMessage(Client* client, std::string fromName, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, int32 language = 0); + void HandleChatMessage(std::string fromName, const char* to, int16 channel, const char* message, float distance, const char* channel_name, int32 language); + void HandleBroadcast(const char* message); void HandleAnnouncement(const char* message); @@ -517,7 +520,7 @@ public: void SetZoneDescription(char* desc) { if( strlen(desc) >= sizeof zone_description ) return; - strcpy(zone_description, desc); + strncpy(zone_description, desc, 255); } void SetUnderWorld(float under){ underworld = under; } @@ -543,7 +546,7 @@ public: void SetZoneLockState(bool lock_state) { locked = lock_state; } // JA: /zone lock|unlock int32 GetInstanceID() { return instanceID; } bool IsInstanceZone() { return isInstance; } - + void SetInstanceID(int32 newInstanceID) { instanceID = newInstanceID; } void SetShutdownTimer(int val){ shutdownTimer.SetTimer(val*1000); } @@ -725,8 +728,10 @@ public: int32 GetSpawnCountInGrid(int32 grid_id); void SendClientSpawnListInGrid(Client* client, int32 grid_id); - void AddIgnoredWidget(int32 id); + void AddIgnoredWidget(int32 id); + void AddRespawn(Spawn* spawn); + void AddRespawn(int32 locationID, int32 respawnTime); private: #ifndef WIN32 pthread_t ZoneThread; @@ -752,9 +757,10 @@ private: vector* GetAssociatedLocations(set* groups); // never used outside zone server set* GetAssociatedGroups(int32 group_id); // never used outside zone server list* GetSpawnGroupsByLocation(int32 location_id); // never used outside zone server - void ProcessSpawnLocation(int32 location_id, bool respawn = false); // never used outside zone server - Spawn* ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn = false); // never used outside zone server + void ProcessSpawnLocation(int32 location_id, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn = false); // never used outside zone server + Spawn* ProcessSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn = false); // never used outside zone server Spawn* ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn = false); // never used outside zone server + void SendRaidSheetChanges(); // never used outside zone server void SendCharSheetChanges(); // never used outside zone server void SendCharSheetChanges(Client* client); // never used outside zone server void SaveClients(); // never used outside zone server @@ -925,6 +931,7 @@ private: volatile bool LoadingData; std::atomic reloading_spellprocess; std::atomic zoneShuttingDown; + std::atomic is_initialized; bool cityzone; bool always_loaded; bool isInstance; diff --git a/source/common/LogTypes.h b/source/common/LogTypes.h index 615904b..94aeae8 100644 --- a/source/common/LogTypes.h +++ b/source/common/LogTypes.h @@ -513,6 +513,15 @@ LOG_TYPE(REGION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, LOG_TYPE(REGION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") LOG_TYPE(REGION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") +// Logging Peering code +LOG_CATEGORY(PEERING) +LOG_TYPE(PEERING, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PEERING, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PEERING, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PEERING, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PEERING, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PEERING, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + #undef LOG_TYPE #undef LOG_CATEGORY #undef ENABLED diff --git a/source/common/MiscFunctions.cpp b/source/common/MiscFunctions.cpp index f4d5bbf..c468877 100644 --- a/source/common/MiscFunctions.cpp +++ b/source/common/MiscFunctions.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #ifndef WIN32 #include @@ -324,27 +325,19 @@ int64 hextoi64(char* num) { return ret; } -float MakeRandomFloat(float low, float high) -{ -#ifdef _WIN32 - thread_local bool seeded = false; -#else - static bool seeded = false; -#endif - +float MakeRandomFloat(float low, float high) { + // Handle edge case where range is zero or inverted float diff = high - low; - if(!diff) return low; - if(diff < 0) - diff = 0 - diff; + + if (low == high) return low; + if (low > high) std::swap(low, high); - if(!seeded) - { - srand(time(0) * (time(0) % (int)diff)); - seeded = true; - } - - return (rand() / (float)RAND_MAX * diff + (low > high ? high : low)); + // Use a thread-local random generator for thread safety + thread_local std::mt19937 generator(std::random_device{}()); // Seed once per thread + std::uniform_real_distribution distribution(low, high); + + return distribution(generator); } int32 GenerateEQ2Color(float* r, float* g, float* b){ diff --git a/source/common/version.h b/source/common/version.h index db48edb..c8843a3 100644 --- a/source/common/version.h +++ b/source/common/version.h @@ -38,11 +38,11 @@ #endif #if defined(LOGIN) -#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" #elif defined(WORLD) -#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" #else -#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#define CURRENT_VERSION "0.9.8-thetascorpii-DR1" #endif #define COMPILE_DATE __DATE__