1
0

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)
This commit is contained in:
Emagi 2024-11-18 11:13:04 -05:00
parent 7aa35166ca
commit 315ab1d0e7
45 changed files with 9311 additions and 2342 deletions

View File

@ -1613,11 +1613,14 @@ to zero and treated like placeholders." />
<Data ElementName="level_max" Type="int16" Size="1" />
<Data ElementName="race_id" Type="int8" Size="1" />
<Data ElementName="class_id" Type="int8" Size="1" />
<Data ElementName="hp_max" Type="sint32" Size="1" />
<Data ElementName="hp_current" Type="sint32" Size="1" />
<Data ElementName="power_max" Type="sint32" Size="1" />
<Data ElementName="hp_max" Type="sint32" Size="1" />
<Data ElementName="power_current" Type="sint32" Size="1" />
<Data ElementName="unknown1" Type="int8" Size="1" />
<Data ElementName="power_max" Type="sint32" Size="1" />
<Data ElementName="trauma_count" Type="int8" Size="1" />
<Data ElementName="arcane_count" Type="int8" Size="1" />
<Data ElementName="noxious_count" Type="int8" Size="1" />
<Data ElementName="elemental_count" Type="int8" Size="1" />
</Struct>
<Struct Name="Substruct_GroupMember" ClientVersion="546" >
<Data ElementName="zone_status" Type="int8" Size="1" /> <!-- 0=not filled, 1=in_zone, 2=not in zone, 3=unavailable (probably zoning)-->
@ -1784,6 +1787,190 @@ to zero and treated like placeholders." />
<Data ElementName="unknown5" Type="int8" Size="2" />
<Data ElementName="CoEunknown" Type="int8" Size="9" />
</Struct>
<Struct Name="Substruct_RaidMember" ClientVersion="1" >
<Data ElementName="zone_status" Type="int8" Size="1" /> <!-- 0=not filled, 1=in_zone, 2=not in zone-->
<Data ElementName="name" Type="char" Size="16" />
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="pet_id" Type="int32" Size="1" />
<Data ElementName="level_current" Type="int16" Size="1" />
<Data ElementName="race_id" Type="int8" Size="1" />
<Data ElementName="class_id" Type="int8" Size="1" />
<Data ElementName="power_max" Type="sint32" Size="1" />
<Data ElementName="power_current" Type="sint32" Size="1" />
<Data ElementName="hp_max" Type="sint32" Size="1" />
<Data ElementName="hp_current" Type="sint32" Size="1" />
<Data ElementName="trauma_count" Type="int8" Size="1" />
<Data ElementName="arcane_count" Type="int8" Size="1" />
<Data ElementName="noxious_count" Type="int8" Size="1" />
<Data ElementName="elemental_count" Type="int8" Size="1" />
</Struct>
<Struct Name="Substruct_RaidMember" ClientVersion="373" >
<Data ElementName="zone_status" Type="int8" Size="1" /> <!-- 0=not filled, 1=in_zone, 2=not in zone-->
<Data ElementName="name" Type="char" Size="16" />
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="pet_id" Type="int32" Size="1" />
<Data ElementName="level_current" Type="int16" Size="1" />
<Data ElementName="level_max" Type="int16" Size="1" />
<Data ElementName="race_id" Type="int8" Size="1" />
<Data ElementName="class_id" Type="int8" Size="1" />
<Data ElementName="hp_current" Type="sint32" Size="1" />
<Data ElementName="hp_max" Type="sint32" Size="1" />
<Data ElementName="power_current" Type="sint32" Size="1" />
<Data ElementName="power_max" Type="sint32" Size="1" />
<Data ElementName="trauma_count" Type="int8" Size="1" /> <!-- 73 -->
<Data ElementName="arcane_count" Type="int8" Size="1" /> <!-- 74 -->
<Data ElementName="noxious_count" Type="int8" Size="1" /> <!-- 75 -->
<Data ElementName="elemental_count" Type="int8" Size="1" /> <!-- 76 -->
</Struct>
<Struct Name="Substruct_RaidMember" ClientVersion="546" >
<Data ElementName="zone_status" Type="int8" Size="1" /> <!-- 0=not filled, 1=in_zone, 2=not in zone--> <!-- 1 -->
<Data ElementName="name" Type="char" Size="41" /> <!-- 42 -->
<Data ElementName="spawn_id" Type="int32" Size="1" /> <!-- 46 -->
<Data ElementName="pet_id" Type="int32" Size="1" /> <!-- 50 -->
<Data ElementName="level_current" Type="int16" Size="1" /> <!-- 52 -->
<Data ElementName="level_max" Type="int16" Size="1" /> <!-- 54 -->
<Data ElementName="race_id" Type="int8" Size="1" /> <!-- 55 -->
<Data ElementName="class_id" Type="int8" Size="1" /> <!-- 56 -->
<Data ElementName="hp_current" Type="sint32" Size="1" /> <!-- 60 -->
<Data ElementName="hp_max" Type="sint32" Size="1" /> <!-- 64 -->
<Data ElementName="power_current" Type="sint32" Size="1" /> <!-- 68 -->
<Data ElementName="power_max" Type="sint32" Size="1" /> <!-- 72 -->
<Data ElementName="trauma_count" Type="int8" Size="1" /> <!-- 73 -->
<Data ElementName="arcane_count" Type="int8" Size="1" /> <!-- 74 -->
<Data ElementName="noxious_count" Type="int8" Size="1" /> <!-- 75 -->
<Data ElementName="elemental_count" Type="int8" Size="1" /> <!-- 76 -->
<Data ElementName="zone" Type="char" Size="60" /> <!-- 136 -->
<Data ElementName="instance" Type="int8" Size="1" /> <!-- 137 -->
</Struct>
<Struct Name="Substruct_RaidMember" ClientVersion="57048" >
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="pet_id" Type="int32" Size="1" />
<Data ElementName="hp_current" Type="sint64" Size="1" />
<Data ElementName="hp_current2" Type="sint64" Size="1" />
<Data ElementName="hp_max" Type="sint32" Size="1" />
<Data ElementName="hp_max2" Type="sint32" Size="1" />
<Data ElementName="power_current" Type="sint32" Size="1" />
<Data ElementName="power_max" Type="sint32" Size="1" />
<Data ElementName="level_current" Type="int16" Size="1" />
<Data ElementName="level_max" Type="int16" Size="1" />
<Data ElementName="name" Type="char" Size="41" />
<Data ElementName="zone" Type="char" Size="60" />
<Data ElementName="instance" Type="int8" Size="1" />
<Data ElementName="zone_status" Type="int8" Size="1" />
<Data ElementName="unknown4" Type="int16" Size="1" />
<Data ElementName="race_id" Type="int8" Size="1" />
<Data ElementName="class_id" Type="int8" Size="1" />
<Data ElementName="trauma_count" Type="int8" Size="1" />
<Data ElementName="arcane_count" Type="int8" Size="1" />
<Data ElementName="noxious_count" Type="int8" Size="1" />
<Data ElementName="elemental_count" Type="int8" Size="1" />
<Data ElementName="curse_count" Type="int8" Size="1" />
<Data ElementName="unknown5" Type="int8" Size="3" />
</Struct>
<Struct Name="WS_RaidUpdate" ClientVersion="1" OpcodeName="OP_UpdateRaidMsg" >
<Data ElementName="group_member0_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_3" Substruct="Substruct_RaidMember" Size="1" />
</Struct>
<Struct Name="WS_RaidUpdate" ClientVersion="373" OpcodeName="OP_UpdateRaidMsg" >
<Data ElementName="group_member0_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_3" Substruct="Substruct_RaidMember" Size="1" />
</Struct>
<Struct Name="WS_RaidUpdate" ClientVersion="546" OpcodeName="OP_UpdateRaidMsg" >
<Data ElementName="group_member0_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_3" Substruct="Substruct_RaidMember" Size="1" />
</Struct>
<Struct Name="WS_RaidUpdate" ClientVersion="57048" OpcodeName="OP_UpdateRaidMsg" >
<Data ElementName="group_member0_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_0" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_1" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_2" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member0_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member1_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member2_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member3_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member4_3" Substruct="Substruct_RaidMember" Size="1" />
<Data ElementName="group_member5_3" Substruct="Substruct_RaidMember" Size="1" />
</Struct>
<Struct Name="WS_CharacterSheet" ClientVersion="1" OpcodeName="OP_UpdateCharacterSheetMsg" Comment="1560 bytes">
<Data ElementName="character_name" Type="char" Size="16" />
<Data ElementName="race" Type="int8" Size="1" /> <!-- 0x10 -->
@ -1917,7 +2104,7 @@ to zero and treated like placeholders." />
<Data ElementName="spell_state_noaoe" Type="int8" Size="1" /> <!-- 0x4f6 -->
<Data ElementName="current_PCA" Type="int32" Size="1" /> <!-- 0x4f7 -->
<Data ElementName="group_members" Substruct="Substruct_GroupMember" Size="5" /> <!-- 0x4fb -->
<Data ElementName="leader_index" Type="int32" Size="1" /> <!-- 0x5f0 -->
<Data ElementName="group_leader_id" Type="int32" Size="1" /> <!-- 0x5f0 -->
<Data ElementName="pet_id" Type="int32" Size="1" /> <!-- 0x5f4 -->
<Data ElementName="pet_name" Type="char" Size="16" /> <!-- 0x5f8 -->
<Data ElementName="pet_hp" Type="float" Size="1" /> <!-- 0x608 -->
@ -2071,8 +2258,7 @@ to zero and treated like placeholders." />
<Data ElementName="spell_state_noaoe" Type="int8" Size="1" /> <!-- 0x4f6 -->
<Data ElementName="unknownstuff" Type="int8" Size="25" /> <!-- 3425 -->
<Data ElementName="group_members" Substruct="Substruct_GroupMember" Size="5" /> <!-- 3665 -->
<Data ElementName="unknownafter" Type="int8" Size="15" /> <!-- 3425 -->
<Data ElementName="leader_index" Type="int32" Size="1" /> <!-- 3669 -->
<Data ElementName="group_leader_id" Type="int32" Size="1" /> <!-- 3669 -->
<Data ElementName="pet_id" Type="int32" Size="1" /> <!-- 3673 -->
<Data ElementName="pet_name" Type="char" Size="16" /> <!-- 3689 -->
<Data ElementName="pet_health_pct" Type="float" Size="1" /> <!-- 3693 -->
@ -2298,7 +2484,7 @@ to zero and treated like placeholders." />
<Data ElementName="spell_state_always_crit_heal_spell" Type="int8" Size="1" /> <!-- 4007 -->
<Data ElementName="current_PCA" Type="int32" Size="1" /> <!-- 4008, parental control alarm -->
<Data ElementName="group_members" Substruct="Substruct_GroupMember" Size="5" /> <!-- 4012 -->
<Data ElementName="leader_index" Type="int32" Size="1" /> <!-- 4697 -->
<Data ElementName="group_leader_id" Type="int32" Size="1" /> <!-- 4697 -->
<Data ElementName="pet_id" Type="int32" Size="1" /> <!-- 4701 -->
<Data ElementName="pet_name" Type="char" Size="32" /> <!-- 4705 -->
<Data ElementName="unknown6" Type="int8" Size="9" /><!-- 4745 -->
@ -2531,7 +2717,7 @@ to zero and treated like placeholders." />
<Data ElementName="rain2" Type="float" Size="1" /><!-- 4761 -->
<Data ElementName="unknown6" Type="int8" Size="1" /> <!-- 4773 -->
<Data ElementName="group_members" Substruct="Substruct_GroupMember" Size="5" /> <!-- 4012 -->
<Data ElementName="leader_index" Type="int32" Size="1" /> <!-- 4697 -->
<Data ElementName="group_leader_id" Type="int32" Size="1" /> <!-- 4697 -->
<Data ElementName="pet_id" Type="int32" Size="1" /> <!-- 4701 -->
<Data ElementName="pet_name" Type="char" Size="32" /> <!-- 4705 -->
<Data ElementName="unknown6" Type="int8" Size="9" /><!-- 4745 -->
@ -3243,7 +3429,7 @@ to zero and treated like placeholders." />
<Data ElementName="vision" Type="int32" Size="1" />
<Data ElementName="unknown525b" Type="int8" Size="986" />
<Data ElementName="group_members" Substruct="Substruct_GroupMember" Size="5" />
<Data ElementName="group_leader_index" Type="int32" Size="1" />
<Data ElementName="group_leader_id" Type="int32" Size="1" />
<Data ElementName="unknown183" Type="int8" Size="460" />
<Data ElementName="pet_id" Type="int32" Size="1" />
<Data ElementName="pet_name" Type="char" Size="32" />
@ -7830,7 +8016,7 @@ to zero and treated like placeholders." />
<Data ElementName="char_account_id" Type="int32" />
<Data ElementName="zone" Type="char" Size="80" />
</Data>
<Data ElementName="unknown10" Type="int8" />
<Data ElementName="display_zone" Type="int8" />
</Struct>
<Struct Name="WS_WhoQueryReply" ClientVersion="373" OpcodeName="OP_WhoQueryReplyMsg" >
<Data ElementName="account_id" Type="int32" />
@ -7851,7 +8037,7 @@ to zero and treated like placeholders." />
<Data ElementName="zone" Type="char" Size="80" />
<Data ElementName="unknown3" Type="int8" Size="28" />
</Data>
<Data ElementName="unknown10" Type="int8" />
<Data ElementName="display_zone" Type="int8" />
</Struct>
<Struct Name="WS_WhoQueryReply" ClientVersion="546" OpcodeName="OP_WhoQueryReplyMsg" >
<Data ElementName="account_id" Type="int32" />
@ -7871,7 +8057,7 @@ to zero and treated like placeholders." />
<Data ElementName="zone" Type="char" Size="80" />
<Data ElementName="unknown6" Type="int8" Size="28" />
</Data>
<Data ElementName="unknown10" Type="int8" />
<Data ElementName="display_zone" Type="int8" />
</Struct>
<Struct Name="WS_WhoQueryReply" ClientVersion="1188" OpcodeName="OP_WhoQueryReplyMsg" >
<Data ElementName="account_id" Type="int32" />
@ -7892,7 +8078,7 @@ to zero and treated like placeholders." />
<Data ElementName="guild" Type="char" Size="40" />
<Data ElementName="unknown7" Type="int8" />
</Data>
<Data ElementName="unknown10" Type="int8" />
<Data ElementName="display_zone" Type="int8" />
</Struct>
<Struct Name="WS_HearChat" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearChatCmd" >
<Data ElementName="understood" Type="int8" />
@ -11132,9 +11318,36 @@ to zero and treated like placeholders." />
<Data ElementName="type" Type="int32" Size="1" />
<Data ElementName="description" Type="EQ2_16Bit_String" Size="1" />
</Struct>
<!-- KoS, DoF and earlier clients need to be structured properly here -->
<Struct Name="WS_GuildMembershipResponse" ClientVersion="1" OpcodeName="OP_GuildMembershipResponseMsg">
<Data ElementName="guild_id" Type="int32" Size="1" />
<Data ElementName="character_id_to" Type="int32" Size="1" />
<Data ElementName="num_membersx" Type="int32" Size="1" />
<Data ElementName="member_array" Type="Array" ArraySizeVariable="num_members" >
<Data ElementName="character_id" Type="int32" Size="1" />
<Data ElementName="account_id" Type="int32" Size="1" />
<Data ElementName="name" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="adventure_class" Type="int32" Size="1" />
<Data ElementName="adventure_level" Type="int32" Size="1" />
<Data ElementName="tradeskill_class" Type="int32" Size="1" />
<Data ElementName="tradeskill_level" Type="int32" Size="1" />
<Data ElementName="rank" Type="int32" Size="1" />
<Data ElementName="member_flags" Type="int32" Size="1" />
<Data ElementName="join_date" Type="int32" Size="1" />
<Data ElementName="guild_status" Type="int32" Size="1" />
<Data ElementName="last_login" Type="int32" Size="1" />
<Data ElementName="recruiter_id" Type="int32" Size="1" />
<Data ElementName="points" Type="float" Size="1" />
<Data ElementName="zone" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="note" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="officer_note" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="unique_id" Type="int32" Size="1" />
</Data>
<Data ElementName="unknown7" Type="int16" Size="1" />
</Struct>
<Struct Name="WS_GuildMembershipResponse" ClientVersion="562" OpcodeName="OP_GuildMembershipResponseMsg">
<Data ElementName="guild_id" Type="int32" Size="1" />
<Data ElementName="character_id_to" Type="int32" Size="1" />
<Data ElementName="num_members" Type="int32" Size="1" />
<Data ElementName="member_array" Type="Array" ArraySizeVariable="num_members" >
<Data ElementName="character_id" Type="int32" Size="1" />

View File

@ -360,4 +360,12 @@
<ConfigType Type="PACKET" Level="9" Color="Yellow" Enabled="False" Logs="3" />
<ConfigType Type="TRACE" Level="9" Color="Yellow" Enabled="False" Logs="1" />
</LogConfig>
<LogConfig Category="PEERING">
<ConfigType Type="INFO" Level="9" Color="WhiteBold" Enabled="True" Logs="3" />
<ConfigType Type="WARNING" Level="9" Color="YellowBold" Enabled="True" Logs="3" />
<ConfigType Type="ERROR" Level="9" Color="RedBold" Enabled="True" Logs="3" />
<ConfigType Type="DEBUG" Level="9" Color="GreenBold" Enabled="False" Logs="3" />
<ConfigType Type="PACKET" Level="9" Color="Yellow" Enabled="False" Logs="3" />
<ConfigType Type="TRACE" Level="9" Color="Yellow" Enabled="False" Logs="1" />
</LogConfig>
</EQ2EmuLogConfigs>

View File

@ -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);

View File

@ -49,6 +49,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#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<int32> 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 <name>
*/
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 <name>
*/
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 <name>
*/
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<int32> raidGroups;
world.GetGroupManager()->GetRaidGroups(gmi->group_id, &raidGroups);
peer_manager.sendPeersNewGroupRequest("", 0, gmi->group_id, "", "", &goptions, "", &raidGroups, true);
std::vector<int32> 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<int32> raidGroups;
world.GetGroupManager()->GetRaidGroups(gmi->group_id, &raidGroups);
std::vector<int32>::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<int32> 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());
}
}
}

View File

@ -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

View File

@ -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
{

View File

@ -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<int32, GuildMember *>::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<int32, GuildMember *>::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<int32, GuildMember *>::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<int32, GuildMember *>::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<int32, GuildMember *>::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];

View File

@ -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;}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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<unsigned int>(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 = 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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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, ...) {

View File

@ -544,20 +544,30 @@ void Brain::AddToEncounter(Entity* entity) {
PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* 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<int32> raidGroups;
group->GetRaidGroups(&raidGroups);
if(raidGroups.size() < 1)
raidGroups.push_back(group_id);
std::vector<int32>::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<GroupMemberInfo*>* 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 {

View File

@ -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<int32> 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<int32>::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<GroupMemberInfo*>* members = group->GetMembers();
deque<GroupMemberInfo*>::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<GroupMemberInfo*>::iterator itr;
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* 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)

View File

@ -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<int32, Mail*>* GetMail();
Mail* GetMail(int32 mail_id);
@ -1112,6 +1114,7 @@ public:
map<int8, int32> 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<Spawn*, bool> current_quest_flagged;
PlayerFaction factions;
map<int32, Quest*> completed_quests;
bool charsheet_changed;
std::atomic<bool> charsheet_changed;
std::atomic<bool> raidsheet_changed;
std::atomic<bool> hassent_raid;
map<int32, string> spawn_vis_packet_list;
map<int32, string> spawn_info_packet_list;
map<int32, string> 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;

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
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;
};
/// <summary>Represents a players group in game</summary>
@ -73,19 +79,22 @@ public:
/// <summary>Adds a new member to the players group</summary>
/// <param name='member'>Entity to add to the group, can be a Player or NPC</param>
/// <returns>True if the member was added</returns>
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);
/// <summary>Removes a member from the players group</summary>
/// <param name='member'>Entity to remove from the player group</param>
/// <returns>True if the member was removed</param>
bool RemoveMember(Entity* member);
bool RemoveMember(std::string name, bool is_client, int32 charID);
/// <summary>Removes all members from the group and destroys the group</summary>
void Disband();
/// <summary>Sends updates to all the clients in the group</summary>
/// <param name='exclude'>Client to exclude from the update</param>
void SendGroupUpdate(Client* exclude = 0);
void SendGroupUpdate(Client* exclude = 0, bool forceRaidUpdate = false);
/// <summary>Gets the total number of members in the group</summary>
/// <returns>int32, number of members in the group</returns>
@ -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<int32>* groups);
void ReplaceRaidGroups(std::vector<int32>* 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<GroupMemberInfo*> m_members; // List of members in this group
std::vector<int32> m_raidgroups;
mutable std::shared_mutex MRaidGroups; // mutex for std vector
};
/// <summary>Responsible for managing all the player groups in the world</summary>
@ -130,17 +153,19 @@ public:
/// <param name='group_id'>ID of the group to add a member to</param>
/// <param name='member'>Entity* to add to the group</param>
/// <returns>True if the member was added to the group</returns>
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);
/// <summary>Removes a member from a group</summary>
/// <param name='group_id'>ID of the group to remove a member from</param>
/// <param name='member'>Entity* to remove from the group</param>
/// <returns>True if the member was removed from the group</returns>
bool RemoveGroupMember(int32 group_id, Entity* member);
bool RemoveGroupMember(int32 group_id, std::string name, bool is_client, int32 charID);
/// <summary>Creates a new group with the provided Entity* as the leader</summary>
/// <param name='leader'>The Entity* that will be the leader of the group</param>
void NewGroup(Entity* leader);
int32 NewGroup(Entity* leader, GroupOptions* goptions, int32 override_group_id = 0);
/// <summary>Removes the group from the group manager</summary>
/// <param name='group_id'>ID of the group to remove</param>
@ -151,11 +176,12 @@ public:
/// <param name='member'>Player or NPC that is the target of the invite</param>
/// <returns>Error code if invite was unsuccessful, 0 if successful</returns>
int8 Invite(Player* leader, Entity* member);
bool AddInvite(Player* leader, Entity* member);
/// <summary>Handles accepting of a group invite</summary>
/// <param name='member'>Entity* that is accepting the invite</param>
/// <returns>Error code if accepting the invite failed, 0 if successful<returns>
int8 AcceptInvite(Entity* member);
int8 AcceptInvite(Entity* member, int32* group_override_id = nullptr, bool auto_add_group = true);
/// <summary>Handles declining of a group invite</summary>
/// <param name='member'>Entity* that is declining the invite</param>
@ -169,7 +195,7 @@ public:
/// <summary>Send updates to all the clients in the group</summary>
/// <param name='group_id'>ID of the group to send updates to</param>
/// <param name='exclude'>Client* to exclude from the update, usually the one that triggers the update</param>
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<int32>* groups);
void ReplaceRaidGroups(int32 groupID, std::vector<int32>* 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<int32, PlayerGroup*> m_groups; // int32 is the group id, PlayerGroup* is a pointer to the actual group
map<string, string> m_pendingInvites; // First string is the person invited to the group, second string is the leader of the group
map<string, string> 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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "HTTPSClient.h"
#include "PeerManager.h"
#include "../net.h"
#include "../../common/Log.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/steady_timer.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace boost_net = boost::asio; // From <boost/asio.hpp>
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<const unsigned char*>(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<boost::asio::ssl::context> HTTPSClient::createSSLContext() {
auto sslCtx = std::make_shared<boost::asio::ssl::context>(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<http::string_body>& 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<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>(ioContext, *sslCtx);
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(ioContext);
auto results = resolver->resolve(server, port);
// Persistent objects to manage response, request, and buffer
auto res = std::make_shared<http::response<http::string_body>>();
auto buffer = std::make_shared<boost::beast::flat_buffer>();
auto req = std::make_shared<http::request<http::string_body>>(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<int>(::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<boost::asio::steady_timer>(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<boost::asio::steady_timer>(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<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>(ioContext, *sslCtx);
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(ioContext);
auto results = resolver->resolve(server, port);
// Persistent objects to manage response, request, and buffer
auto res = std::make_shared<http::response<http::string_body>>();
auto buffer = std::make_shared<boost::beast::flat_buffer>();
auto req = std::make_shared<http::request<http::string_body>>(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<int>(::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<boost::asio::steady_timer>(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<boost::asio::steady_timer>(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 {};
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef HTTPSCLIENT_H
#define HTTPSCLIENT_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/asio/ssl.hpp>
#include <string>
#include <unordered_map>
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<std::string, std::string> cookies;
std::string certFile;
std::string keyFile;
std::string server;
std::string port;
void parseAndStoreCookies(const http::response<http::string_body>& res);
std::shared_ptr<boost::asio::ssl::context> createSSLContext(); // New helper function
std::string buildCookieHeader() const;
};
#endif // HTTPSCLIENT_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <regex>
#include <iostream>
#include <sstream>
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<std::string>("character_name")) {
charName = char_name.get();
}
if (auto successful = tree.get_optional<int32>("success")) {
success = successful.get();
}
if (auto account_id = tree.get_optional<int32>("account_id")) {
acct_id = account_id.get();
}
if (auto zone_name = tree.get_optional<std::string>("zone_name")) {
zoneName = zone_name.get();
}
if (auto zone_id = tree.get_optional<int32>("zone_id")) {
zoneId = zone_id.get();
}
if (auto instance_id = tree.get_optional<int32>("instance_id")) {
instanceId = instance_id.get();
}
if (auto first_login = tree.get_optional<bool>("first_login")) {
firstLogin = first_login.get();
}
if (auto peerclientaddr = tree.get_optional<std::string>("peer_client_address")) {
worldAddr = peerclientaddr.get();
}
if (auto peerclient_internaladdr = tree.get_optional<std::string>("peer_client_internal_address")) {
internalWorldAddr = peerclient_internaladdr.get();
}
if (auto peerclientport = tree.get_optional<int16>("peer_client_port")) {
worldPort = peerclientport.get();
}
if (auto clientip = tree.get_optional<std::string>("client_ip")) {
clientIP = clientip.get();
}
if (auto character_id = tree.get_optional<int32>("character_id")) {
charID = character_id.get();
}
if (auto login_key = tree.get_optional<int32>("login_key")) {
loginKey = login_key.get();
}
if (auto world_id = tree.get_optional<int32>("world_id")) {
worldID = world_id.get();
}
if (auto from_id = tree.get_optional<int32>("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<std::string>("peer_web_address")) {
peerWebAddress = addr.get();
}
if (auto port = tree.get_optional<int16>("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<int32>("success")) {
success = successful.get();
}
if (auto name = tree.get_optional<std::string>("to_name")) {
toName = name.get();
}
if (auto name = tree.get_optional<std::string>("from_name")) {
fromName = name.get();
}
if (auto message = tree.get_optional<std::string>("message")) {
msg = message.get();
}
if (auto from_language = tree.get_optional<int8>("from_language")) {
language = from_language.get();
}
if (auto channel = tree.get_optional<int16>("channel")) {
in_channel = channel.get();
}
if (auto msg = tree.get_optional<std::string>("away_message")) {
awaymsg = msg.get();
}
if (auto away_lang = tree.get_optional<int8>("away_language")) {
away_language = away_lang.get();
}
if (auto group = tree.get_optional<int32>("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<int32>("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<std::string>("leader_name")) {
leader = leader_name.get();
}
if (auto member_name = tree.get_optional<std::string>("member_name")) {
member = member_name.get();
}
if (auto lootmethod = tree.get_optional<int8>("loot_method")) {
options.loot_method = lootmethod.get();
}
if (auto itemrarity = tree.get_optional<int8>("loot_item_rarity")) {
options.loot_items_rarity = itemrarity.get();
}
if (auto autosplit = tree.get_optional<int8>("auto_split")) {
options.auto_split = autosplit.get();
}
if (auto defaultyell = tree.get_optional<int8>("default_yell")) {
options.default_yell = defaultyell.get();
}
if (auto grouplockmethod = tree.get_optional<int8>("group_lock_method")) {
options.group_lock_method = grouplockmethod.get();
}
if (auto groupautolock = tree.get_optional<int8>("group_auto_lock")) {
options.group_autolock = groupautolock.get();
}
if (auto soloautolock = tree.get_optional<int8>("solo_auto_lock")) {
options.solo_autolock = soloautolock.get();
}
if (auto autoloot = tree.get_optional<int8>("auto_loot_method")) {
options.auto_loot_method = autoloot.get();
}
if (auto lastlootindex = tree.get_optional<int8>("last_looted_index")) {
options.last_looted_index = lastlootindex.get();
}
if (auto groupid = tree.get_optional<int32>("group_id")) {
group_id = groupid.get();
}
if (auto entityid = tree.get_optional<int32>("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<bool>("success")) {
success = successful.get();
}
if (auto guild_id = tree.get_optional<int32>("guild_id")) {
guildID = guild_id.get();
}
if (auto name = tree.get_optional<std::string>("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<GroupMemberInfo*>::iterator itr;
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* 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<void(boost::property_tree::ptree tree)>;
// Set up the endpoint-to-function map
std::unordered_map<std::string, HandlerFunction> 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<std::mutex> 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<HTTPSClient> HTTPSClientPool::getClient(const std::string& peerId) {
std::unique_lock<std::mutex> 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<HTTPSClient> HTTPSClientPool::getOrCreateClient(const std::string& id, const std::string& server, const std::string& port) {
std::unique_lock<std::mutex> 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<HTTPSClient>(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<std::string>("world_status")) {
online_status = status.get();
}
if (auto priority = json_tree.get_optional<int16>("peer_priority")) {
peer_priority = priority.get();
}
if (auto isprimary = json_tree.get_optional<bool>("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<Peer> 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 = 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 = 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 = 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<std::mutex> lock(queueMutex);
taskQueue.emplace([this, peerId, server, port, target, payload]() {
std::shared_ptr<HTTPSClient> 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<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return stop || !taskQueue.empty(); });
if (stop && taskQueue.empty()) {
return;
}
task = std::move(taskQueue.front());
taskQueue.pop();
}
task();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef HTTPSCLIENTPOOL_H
#define HTTPSCLIENTPOOL_H
#include "HTTPSClient.h"
#include <unordered_map>
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>
#include <boost/property_tree/ptree.hpp> // Include Boost property tree
struct pair_hash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2>& pair) const {
return std::hash<T1>()(pair.first) ^ std::hash<T2>()(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<HTTPSClient> 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<HTTPSClient> getClient(const std::string& peerId);
std::unordered_map<std::pair<std::string, std::string>, std::shared_ptr<HTTPSClient>, pair_hash> clients;
std::unordered_map<std::string, std::shared_ptr<HTTPSClient>> clientsById; // New map for ID-based lookup
std::string certFile;
std::string keyFile;
int pollingInterval; // Polling interval in milliseconds
std::queue<std::function<void()>> 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<std::thread> workers; // Worker threads
bool stop = false; // Flag to stop workers
std::atomic<bool> running; // Flag to control polling loop
void pollPeerHealth(const std::string& server, const std::string& port); // Polls individual peer
};
#endif // HTTPSCLIENTPOOL_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<double> 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> peer = std::make_shared<Peer>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::string>("zone_name");
std::string zone_file_name = zone.second.get<std::string>("zone_file_name");
int32 zone_id = zone.second.get<int32>("zone_id");
int32 instance_id = zone.second.get<int32>("instance_id");
bool shutting_down = zone.second.get<std::string>("shutting_down") == "true";
bool instance_zone = zone.second.get<std::string>("instance_zone") == "true";
int32 num_players = zone.second.get<int32>("num_players");
bool city_zone = zone.second.get<std::string>("city_zone") == "true";
float safe_x = zone.second.get<float>("safe_x");
float safe_y = zone.second.get<float>("safe_y");
float safe_z = zone.second.get<float>("safe_z");
float safe_heading = zone.second.get<float>("safe_heading");
bool lock_state = zone.second.get<bool>("lock_state");
sint16 min_status = zone.second.get<sint16>("min_status");
int16 min_level = zone.second.get<int16>("min_level");
int16 max_level = zone.second.get<int16>("max_level");
int16 min_version = zone.second.get<int16>("min_version");
int32 default_lockout_time = zone.second.get<int32>("default_lockout_time");
int32 default_reenter_time = zone.second.get<int32>("default_reenter_time");
int8 instance_type = zone.second.get<int8>("instance_type");
bool always_loaded = zone.second.get<bool>("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<std::string> PeerManager::getHealthyPeer() const {
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status == HealthStatus::OK) {
return id;
}
}
return std::nullopt;
}
std::shared_ptr<Peer> PeerManager::getHealthyPeerPtr() const {
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status == HealthStatus::OK) {
return peer;
}
}
return nullptr;
}
std::shared_ptr<Peer> 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<Peer> PeerManager::getHealthyPeerWithLeastClients() const {
std::vector<std::shared_ptr<Peer>> healthyPeers;
// Seed random generator
std::srand(static_cast<unsigned>(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<size_t>::max();
for (const auto& peer : healthyPeers) {
std::lock_guard<std::mutex> lock(peer->dataMutex); // Lock for thread-safe access
std::shared_ptr<boost::property_tree::ptree> 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<std::shared_ptr<Peer>> minClientPeers;
for (const auto& peer : healthyPeers) {
std::lock_guard<std::mutex> lock(peer->dataMutex);
std::shared_ptr<boost::property_tree::ptree> 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<Peer> PeerManager::getPeerById(const std::string& id) const {
auto it = peers.find(id);
if (it != peers.end()) {
return it->second; // Return the shared_ptr<Peer> 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<std::mutex> 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<Peer> 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<int32>* 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<int32>::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> 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> 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<Peer> 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<Peer> 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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PEERMANAGER_H
#define PEERMANAGER_H
#include <iostream>
#include <chrono>
#include <map>
#include <string>
#include <optional>
#include <memory>
#include <mutex>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#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<double> 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<boost::property_tree::ptree> zone_tree;
std::shared_ptr<boost::property_tree::ptree> client_tree;
std::atomic<bool> sentInitialPeerData;
std::atomic<bool> wasOffline;
mutable std::mutex dataMutex; // Mutex to protect access to ptree
// Default constructor
Peer()
: zone_tree(std::make_shared<boost::property_tree::ptree>()),
client_tree(std::make_shared<boost::property_tree::ptree>()) {
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<boost::property_tree::ptree>()),
client_tree(std::make_shared<boost::property_tree::ptree>()) {
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<std::mutex> 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<std::mutex> lock(dataMutex);
std::ostringstream oss;
boost::property_tree::write_json(oss, *client_tree); // Dereference data
return oss.str();
}
};
class PeerManager {
private:
std::map<std::string, std::shared_ptr<Peer>> peers;
std::atomic<int32> 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<std::string> getHealthyPeer() const;
std::shared_ptr<Peer> getHealthyPeerPtr() const;
std::shared_ptr<Peer> getHealthyPrimaryPeerPtr() const;
std::shared_ptr<Peer> getHealthyPeerWithLeastClients() const;
std::shared_ptr<Peer> 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<int32>* 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<Peer> getCurrentPrimary();
void sendPeersMessage(const std::string& endpoint, int32 command, int32 sub_command = 0);
void sendZonePlayerList(std::vector<string>* queries, std::vector<WhoAllPeerPlayer>* peer_list, bool isGM);
bool GetClientGuildDetails(int32 matchCharID, GuildMember* member_details);
};
#endif // PEERMANAGER_H

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,9 @@
#include "Player.h"
#include "Web/PeerManager.h"
#include "Web/HTTPSClientPool.h"
#include <boost/algorithm/string.hpp>
#include <string>
#include <iostream>
@ -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<std::string, int16> peers = net.GetWebPeers();
std::map<std::string, int16>::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 = 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<ZoneServer*>::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<ZoneServer*>::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<ZoneServer*>::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<ZoneServer*>::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<ZoneServer*>::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 = 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<ZoneServer*>::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 = 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<std::mutex> 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<int32>("account_id");
bool is_linkdead = zone.second.get<bool>("is_linkdead");
bool is_zoning = zone.second.get<bool>("is_zoning");
bool in_zone = zone.second.get<bool>("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<std::mutex> 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<std::string>("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<int32> raidGroups;
world.GetGroupManager()->GetRaidGroups(group_id, &raidGroups);
for (auto& [peerId, peer] : peers) {
if(peer->healthCheck.status != HealthStatus::OK)
continue;
try {
std::lock_guard<std::mutex> 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<int32>("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<std::mutex> 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<int32>("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<std::mutex> 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<std::string>("zone_name");
std::string zone_file_name = zone.second.get<std::string>("zone_file_name");
int32 zone_id = zone.second.get<int32>("zone_id");
int32 instance_id = zone.second.get<int32>("instance_id");
bool shutting_down = zone.second.get<std::string>("shutting_down") == "true";
bool instance_zone = zone.second.get<std::string>("instance_zone") == "true";
int32 num_players = zone.second.get<int32>("num_players");
bool city_zone = zone.second.get<std::string>("city_zone") == "true";
float safe_x = zone.second.get<float>("safe_x");
float safe_y = zone.second.get<float>("safe_y");
float safe_z = zone.second.get<float>("safe_z");
float safe_heading = zone.second.get<float>("safe_heading");
bool lock_state = zone.second.get<bool>("lock_state");
sint16 min_status = zone.second.get<sint16>("min_status");
int16 min_level = zone.second.get<int16>("min_level");
int16 max_level = zone.second.get<int16>("max_level");
int16 min_version = zone.second.get<int16>("min_version");
int32 default_lockout_time = zone.second.get<int32>("default_lockout_time");
int32 default_reenter_time = zone.second.get<int32>("default_reenter_time");
int8 instance_type = zone.second.get<int8>("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<string>* queries, std::vector<WhoAllPeerPlayer>* 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<std::mutex> lock(peer->dataMutex);
for (const auto& player : peer->client_tree->get_child("Clients")) {
std::string char_name = player.second.get<std::string>("character_name");
std::string subtitle = player.second.get<std::string>("subtitle");
std::string zone_name = player.second.get<std::string>("zonedescription");
int8 adventure_class = player.second.get<int8>("adventure_class");
int8 tradeskill_class = player.second.get<int8>("tradeskill_class");
int8 deity = player.second.get<int8>("deity");
int8 race = player.second.get<int8>("race");
sint16 status = player.second.get<sint16>("status");
int flags = player.second.get<int>("flags");
int flags2 = player.second.get<int>("flags2");
int16 level = player.second.get<int16>("level");
found_match = false;
add_player = true;
for(int32 i=0;add_player && queries && i<queries->size();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<std::mutex> lock(peer->dataMutex);
for (const auto& player : peer->client_tree->get_child("Clients")) {
std::string char_name = player.second.get<std::string>("character_name");
std::string subtitle = player.second.get<std::string>("subtitle");
std::string zone_name = player.second.get<std::string>("zonedescription");
int8 adventure_class = player.second.get<int8>("adventure_class");
int8 tradeskill_class = player.second.get<int8>("tradeskill_class");
int8 deity = player.second.get<int8>("deity");
int8 race = player.second.get<int8>("race");
sint16 status = player.second.get<sint16>("status");
int flags = player.second.get<int>("flags");
int flags2 = player.second.get<int>("flags2");
int16 level = player.second.get<int16>("level");
int16 tradeskill_level = player.second.get<int16>("tradeskill_level");
int32 character_id = player.second.get<int32>("character_id");
int32 account_id = player.second.get<int32>("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<ZoneServer*>::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<ZoneServer*>::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<string>* queries, ZoneServer* zone, vector
void ZoneList::ProcessWhoQuery(const char* query, Client* client){
list<ZoneServer*>::iterator zone_iter;
vector<Entity*> players;
vector<WhoAllPeerPlayer> peer_players;
vector<WhoAllPeerPlayer>::iterator peer_iter;
vector<Entity*>::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<GroupMemberInfo*>::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<unsigned int>(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
}
}

View File

@ -43,6 +43,8 @@
#include "./Zone/region_map.h"
#include "./Zone/map.h"
#include "./Web/PeerManager.h"
using namespace std;
struct MerchantInfo{
vector<int32> 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);
/// <summary>Get the instance for the given zone id with the lowest population</summary>
/// <param name='zone_id'>The id of the zone to look up</param>
@ -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<string>* queries, ZoneServer* zone, vector<Entity*>* players, bool isGM);
@ -481,6 +517,7 @@ class ZoneList {
void SendTimeUpdate();
void PopulateClientList(http::response<http::string_body>& res);
void PopulateZoneList(http::response<http::string_body>& 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<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_clients(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_setadminstatus(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_reloadrules(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_reloadcommand(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_addpeer(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_zones(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_addcharauth(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_startzone(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_sendglobalmessage(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_newgroup(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_addgroupmember(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_removegroupmember(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_disbandgroup(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_createguild(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_addguildmember(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_removeguildmember(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_setguildpermission(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_setguildeventfilter(const http::request<http::string_body>& req, http::response<http::string_body>& res);
Mutex MVoiceOvers;
static sint64 newValue;

View File

@ -52,7 +52,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#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 = 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<int32,int32>* 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<int32,int32>* WorldDatabase::GetPersistedSpawns(int32 zone_id, int8 type)
{
DatabaseResult result;
map<int32,int32>* 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<int32,int32>;
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__);

View File

@ -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<int32,int32>* GetPersistedSpawns(int32 zone_id, int8 type);
//
MutexMap<int32, LoginEquipmentUpdate>* 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);

File diff suppressed because it is too large Load Diff

View File

@ -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<bool> client_zoning;
std::atomic<bool> 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<bool> m_recipeListSent;
bool initial_spawns_sent;

View File

@ -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

View File

@ -20,6 +20,7 @@
#include "../common/debug.h"
#include "../common/Log.h"
#include <boost/program_options.hpp>
#include <iostream>
using namespace std;
#include <string.h>
@ -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<int16, int16> 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<EQStream*, int32> connecting_clients;
map<EQStream*, int32>::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<std::string>(&worldAddress), "World address")
("internalworldaddress", po::value<std::string>(&internalWorldAddress), "Internal world address")
("worldport", po::value<uint16>(&worldPort)->default_value(0), "Web world port")
("webworldaddress", po::value<std::string>(&webWorldAddress), "Web world address")
("webworldport", po::value<uint16>(&webWorldPort)->default_value(0), "Web world port")
("peerpriority", po::value<uint16>(&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];

View File

@ -34,6 +34,9 @@
#include <windows.h>
#endif
#include <atomic>
#include <cstring>
#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<std::string, int16> GetWebPeers() { std::map<std::string, int16> copied_map(web_peers); return copied_map; }
int16 GetPeerPriority() { return web_peerpriority; }
bool world_locked;
std::atomic<bool> 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<std::string, int16> web_peers;
int16 web_worldport;
int16 web_peerpriority;
};

View File

@ -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 <int32, bool> 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<int32, Spawn*>::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<Client*>::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<Client*>::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<int32,int32>* instNPCs, map<int32,int32>* instGroundSpawns, map<int32,int32>* instObjSpawns, map<int32,int32>* instWidgetSpawns, map<int32,int32>* 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<int32,int32>* instNPCs, map<int32,int32>* instGroundSpawns, map<int32,int32>* instObjSpawns, map<int32,int32>* instWidgetSpawns, map<int32,int32>* 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<int32, bool> processed_spawn_locations;
map<int32, SpawnLocation*>::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<int32, int32>::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<Client*>::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<Client*>::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<Client*>::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);
}
}

View File

@ -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<int32>* GetAssociatedLocations(set<int32>* groups); // never used outside zone server
set<int32>* GetAssociatedGroups(int32 group_id); // never used outside zone server
list<int32>* 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<int32,int32>* instNPCs, map<int32,int32>* instGroundSpawns, map<int32,int32>* instObjSpawns, map<int32,int32>* instWidgetSpawns, map<int32,int32>* instSignSpawns, bool respawn = false); // never used outside zone server
Spawn* ProcessSpawnLocation(SpawnLocation* spawnlocation, map<int32,int32>* instNPCs, map<int32,int32>* instGroundSpawns, map<int32,int32>* instObjSpawns, map<int32,int32>* instWidgetSpawns, map<int32,int32>* instSignSpawns, bool respawn = false); // never used outside zone server
Spawn* ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map<int32,int32>* instNPCs, map<int32,int32>* instGroundSpawns, map<int32,int32>* instObjSpawns, map<int32,int32>* instWidgetSpawns, map<int32,int32>* 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<bool> reloading_spellprocess;
std::atomic<bool> zoneShuttingDown;
std::atomic<bool> is_initialized;
bool cityzone;
bool always_loaded;
bool isInstance;

View File

@ -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

View File

@ -24,6 +24,7 @@
#include <time.h>
#include <math.h>
#include <chrono>
#include <random>
#ifndef WIN32
#include <netinet/in.h>
@ -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<float> distribution(low, high);
return distribution(generator);
}
int32 GenerateEQ2Color(float* r, float* g, float* b){

View File

@ -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__