- Fix #504 - Group Options Support (loot methods, yell restrictions, encounter lock features, item rarity, auto split coin, auto loot mode)

- Rule R_Loot, LootDistributionTime added to set lotto/NBG timer countdown for distribution, default 120 (in seconds)
- /setautolootmode [x] command now supported, 0 = none, 1 = need/lotto, 2 = decline
DB Update: update commands set handler=534 where command='setautolootmode';
- /loot list details added - tracks the loot windows of players and tells if they are still open or closed (to determine when loot should dispense)
- Addressed spells causing crashes on deconstruct of NPCs
- Fixed inner struct data honoring the IfVariableSet/IfVariableNotSet flag, eg. previously item_id would not honor IfVariableSet/IfVariableNotSet:
<Data ElementName="item_count" Type="int8" IfVariableNotSet="loot_all"/>
<Data ElementName="item_list" Type="Array" ArraySizeVariable="item_count" IfVariableNotSet="loot_all">
       <Data ElementName="item_id" Type="int32" IfVariableNotSet="loot_all"/>
</Data>
This commit is contained in:
Emagi 2024-02-04 14:51:55 -05:00
parent 5c1c66afaf
commit 47196d6b67
25 changed files with 1534 additions and 315 deletions

View file

@ -2849,10 +2849,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if (sep && sep->arg[0]) {
if (!spawn->GetDatabaseID())
{
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn has no database id to assign to loottables.");
break;
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "NOTE: Spawn has no database id to assign to loottables.");
}
else if (!spawn->IsNPC())
if (!spawn->IsNPC())
{
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "these /loot list [add/remove/clearall] sub-commands are only designed for an NPC.");
break;
@ -2893,6 +2893,21 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list clearall - could not match any spawn_id entries in loottable_id.");
}
else if (!stricmp(sep->arg[0], "details"))
{
spawn->LockLoot();
client->SimpleMessage(CHANNEL_COMMANDS, "Loot Window List:");
if (spawn->GetLootWindowList()->size() > 0) {
std::map<int32, bool>::iterator itr;
for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) {
Spawn* looter = client->GetPlayer()->GetZone()->GetSpawnByID(itr->first);
if (looter) {
client->Message(CHANNEL_COLOR_YELLOW, "Looter: %s IsLootWindowOpen: %s, HasCompletedLootWindow: %s.", looter->GetName(), itr->second ? "NO" : "YES", spawn->HasSpawnLootWindowCompleted(itr->first) ? "YES" : "NO");
}
}
}
spawn->UnlockLoot();
}
else
{
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list argument not supported.");
@ -2995,7 +3010,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if (!rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool() && command->handler == COMMAND_DISARM )
client->OpenChest(cmdTarget, true);
else
client->Loot(cmdTarget, rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool());
client->LootSpawnRequest(cmdTarget, rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool());
if (!(cmdTarget)->HasLoot()){
if (((Entity*)cmdTarget)->IsNPC())
client->GetCurrentZone()->RemoveDeadSpawn(cmdTarget);
@ -5708,6 +5723,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_CUREPLAYER: { Command_CurePlayer(client, sep); break; }
case COMMAND_SHARE_QUEST: { Command_ShareQuest(client, sep); break; }
case COMMAND_YELL: { Command_Yell(client, sep); break; }
case COMMAND_SETAUTOLOOTMODE: { Command_SetAutoLootMode(client, sep); break; }
default:
{
LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@ -12037,45 +12053,67 @@ void Commands::Command_Yell(Client* client, Seperator* sep) {
Spawn* res = nullptr;
Spawn* prev = nullptr;
bool cycleAll = false;
if(player->GetTarget() == player) {
if (player->GetTarget() == player) {
cycleAll = true; // self target breaks all encounters
}
else if(player->GetTarget()) {
else if (player->GetTarget()) {
res = player->GetTarget(); // selected target other than self only dis-engages that encounter
}
if(res && !client->GetPlayer()->IsEngagedBySpawnID(res->GetID()))
if (res && !client->GetPlayer()->IsEngagedBySpawnID(res->GetID()))
return;
bool groupPermissionYell = true;
GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
// If the player has a group and has a target
if (gmi) {
deque<GroupMemberInfo*>::iterator itr;
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group && !group->GetGroupOptions()->default_yell && !gmi->leader) { // default_yell_method = 0 means leader only
groupPermissionYell = false;
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
if (!groupPermissionYell) {
LogWrite(COMMAND__ERROR, 0, "Command", "%s permission to yell denied due to group yell method set to leader only", client->GetPlayer()->GetName());
return;
}
bool alreadyYelled = false;
do {
if(!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list
if (!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list
}
if(!res || prev == res) {
return;
}
if(res->IsNPC() && ((NPC*)res)->Brain()) {
if(!alreadyYelled) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You yell for help!");
string yellMsg = std::string(client->GetPlayer()->GetName()) + " yelled for help!";
client->GetPlayer()->GetZone()->SimpleMessage(CHANNEL_COLOR_RED, yellMsg.c_str(), client->GetPlayer(), 35.0f, false);
client->GetPlayer()->GetZone()->SendYellPacket(client->GetPlayer());
}
alreadyYelled = true;
if (!res || prev == res) {
return;
}
NPC* npc = (NPC*)res;
npc->Brain()->ClearEncounter();
npc->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN);
npc->UpdateEncounterState(ENCOUNTER_STATE_BROKEN);
}
prev = res;
res = nullptr;
}while(cycleAll);
if (res->IsNPC() && ((NPC*)res)->Brain()) {
if (!alreadyYelled) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You yell for help!");
string yellMsg = std::string(client->GetPlayer()->GetName()) + " yelled for help!";
client->GetPlayer()->GetZone()->SimpleMessage(CHANNEL_COLOR_RED, yellMsg.c_str(), client->GetPlayer(), 35.0f, false);
client->GetPlayer()->GetZone()->SendYellPacket(client->GetPlayer());
}
alreadyYelled = true;
if(!player->IsEngagedInEncounter()) {
if(player->GetInfoStruct()->get_engaged_encounter()) {
NPC* npc = (NPC*)res;
npc->Brain()->ClearEncounter();
npc->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN);
npc->UpdateEncounterState(ENCOUNTER_STATE_BROKEN);
}
prev = res;
res = nullptr;
} while (cycleAll);
if (!player->IsEngagedInEncounter()) {
if (player->GetInfoStruct()->get_engaged_encounter()) {
player->GetInfoStruct()->set_engaged_encounter(0);
player->SetRegenValues((player->GetInfoStruct()->get_effective_level() > 0) ? player->GetInfoStruct()->get_effective_level() : player->GetLevel());
client->GetPlayer()->SetCharSheetChanged(true);
@ -12083,3 +12121,33 @@ void Commands::Command_Yell(Client* client, Seperator* sep) {
}
}
}
/*
Function: Command_SetAutoLootMode()
Purpose : Set player auto loot mode (0 = disabled, 1 = need/lotto, 2 = decline).
Example : /setautolootmode [mode]
*/
void Commands::Command_SetAutoLootMode(Client* client, Seperator* sep) {
if (sep && sep->IsNumber(0)) {
int8 mode = atoul(sep->arg[0]);
switch (mode) {
case AutoLootMode::METHOD_DISABLED: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Disabled auto loot mode");
break;
}
case AutoLootMode::METHOD_ACCEPT: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode for need and lotto.");
break;
}
default: {
mode = AutoLootMode::METHOD_DISABLED;
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode to decline need and lotto.");
break;
}
}
client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(mode);
database.insertCharacterProperty(client, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(mode).c_str());
client->SendDefaultGroupOptions();
}
}

View file

@ -449,6 +449,7 @@ public:
void Command_CurePlayer(Client* client, Seperator* sep);
void Command_ShareQuest(Client* client, Seperator* sep);
void Command_Yell(Client* client, Seperator* sep);
void Command_SetAutoLootMode(Client* client, Seperator* sep);
// AA Commands
void Get_AA_Xml(Client* client, Seperator* sep);
@ -937,6 +938,7 @@ private:
#define COMMAND_RELOAD_VOICEOVERS 532
#define COMMAND_SHARE_QUEST 533
#define COMMAND_SETAUTOLOOTMODE 534
#define GET_AA_XML 750
#define ADD_AA 751

View file

@ -357,6 +357,15 @@ void Entity::MapInfoStruct()
get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &info_struct);
get_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::get_group_loot_method, &info_struct);
get_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::get_group_loot_items_rarity, &info_struct);
get_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::get_group_auto_split, &info_struct);
get_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::get_group_default_yell, &info_struct);
get_int8_funcs["group_autolock"] = l::bind(&InfoStruct::get_group_autolock, &info_struct);
get_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::get_group_lock_method, &info_struct);
get_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::get_group_solo_autolock, &info_struct);
get_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::get_group_auto_loot_method, &info_struct);
get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct);
get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct);
@ -549,6 +558,15 @@ void Entity::MapInfoStruct()
set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &info_struct, l::_1);
set_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::set_group_loot_method, &info_struct, l::_1);
set_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::set_group_loot_items_rarity, &info_struct, l::_1);
set_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::set_group_auto_split, &info_struct, l::_1);
set_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::set_group_default_yell, &info_struct, l::_1);
set_int8_funcs["group_autolock"] = l::bind(&InfoStruct::set_group_autolock, &info_struct, l::_1);
set_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::set_group_lock_method, &info_struct, l::_1);
set_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::set_group_solo_autolock, &info_struct, l::_1);
set_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::set_group_auto_loot_method, &info_struct, l::_1);
set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1);
set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1);

View file

@ -279,6 +279,15 @@ struct InfoStruct{
first_world_login_ = 0;
reload_player_spells_ = 0;
group_loot_method_ = 1;
group_loot_items_rarity_ = 1;
group_auto_split_ = 1;
group_default_yell_ = 1;
group_autolock_ = 0;
group_lock_method_ = 0;
group_solo_autolock_ = 0;
group_auto_loot_method_ = 0;
action_state_ = std::string("");
}
@ -680,6 +689,15 @@ struct InfoStruct{
int8 get_reload_player_spells() { std::lock_guard<std::mutex> lk(classMutex); return reload_player_spells_; }
int8 get_group_loot_method() { std::lock_guard<std::mutex> lk(classMutex); return group_loot_method_; }
int8 get_group_loot_items_rarity() { std::lock_guard<std::mutex> lk(classMutex); return group_loot_items_rarity_; }
int8 get_group_auto_split() { std::lock_guard<std::mutex> lk(classMutex); return group_auto_split_; }
int8 get_group_default_yell() { std::lock_guard<std::mutex> lk(classMutex); return group_default_yell_; }
int8 get_group_autolock() { std::lock_guard<std::mutex> lk(classMutex); return group_autolock_; }
int8 get_group_lock_method() { std::lock_guard<std::mutex> lk(classMutex); return group_lock_method_; }
int8 get_group_solo_autolock() { std::lock_guard<std::mutex> lk(classMutex); return group_solo_autolock_; }
int8 get_group_auto_loot_method() { std::lock_guard<std::mutex> lk(classMutex); return group_auto_loot_method_; }
std::string get_action_state() { std::lock_guard<std::mutex> lk(classMutex); return action_state_; }
std::string get_combat_action_state() { std::lock_guard<std::mutex> lk(classMutex); return combat_action_state_; }
@ -976,6 +994,15 @@ struct InfoStruct{
void set_reload_player_spells(int8 value) { std::lock_guard<std::mutex> lk(classMutex); reload_player_spells_ = value; }
void set_group_loot_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_loot_method_ = value; }
void set_group_loot_items_rarity(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_loot_items_rarity_ = value; }
void set_group_auto_split(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_auto_split_ = value; }
void set_group_default_yell(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_default_yell_ = value; }
void set_group_autolock(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_autolock_ = value; }
void set_group_lock_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_lock_method_ = value; }
void set_group_solo_autolock(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_solo_autolock_ = value; }
void set_group_auto_loot_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_auto_loot_method_ = value; }
void set_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); action_state_ = value; }
void set_combat_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); combat_action_state_ = value; }
@ -1187,6 +1214,15 @@ private:
int8 first_world_login_;
int8 reload_player_spells_;
int8 group_loot_method_;
int8 group_loot_items_rarity_;
int8 group_auto_split_;
int8 group_default_yell_;
int8 group_autolock_;
int8 group_lock_method_;
int8 group_solo_autolock_;
int8 group_auto_loot_method_;
std::string action_state_;
std::string combat_action_state_;

View file

@ -2304,11 +2304,7 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
if (bag_info->num_slots > max_slots)
bag_info->num_slots = max_slots;
if (client->GetVersion() <= 546) {
packet->setSubstructDataByName("details", "num_slots", bag_info->num_slots);
packet->setSubstructDataByName("details", "weight_reduction", bag_info->weight_reduction);
}
else {
int16 free_slots = bag_info->num_slots;
if (player) {
Item* bag = player->GetPlayerItemList()->GetItemFromUniqueID(details.unique_id, true);
@ -2354,7 +2350,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
int8 blah[] = { 0xd8,0x66,0x9b,0x6d,0xb6,0xfb,0x7f };
for (int8 i = 0; i < sizeof(blah); i++)
packet->setSubstructDataByName("footer", "footer_unknown_0", blah[i], 0, i);
}
}
break;
}

View file

@ -72,7 +72,8 @@ NPC* Entity::DropChest() {
chest->SetIcon(32);
// 1 = show the right click menu
chest->SetShowCommandIcon(1);
chest->SetLootMethod(this->GetLootMethod(), this->GetLootRarity(), this->GetLootGroupID());
chest->SetLootName(this->GetName());
int8 highest_tier = 0;
vector<Item*>::iterator itr;
for (itr = ((Spawn*)this)->GetLootItems()->begin(); itr != ((Spawn*)this)->GetLootItems()->end(); ) {

View file

@ -11152,7 +11152,7 @@ int EQ2Emu_lua_GetSpellTier(lua_State* state) {
LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
lua_interface->ResetFunctionStack(state);
if (!luaspell) {
if (!luaspell || !luaspell->spell) {
lua_interface->LogError("%s: LUA GetSpellTier command error: must be used in a spell script", lua_interface->GetScriptName(state));
return 0;
}
@ -11168,7 +11168,7 @@ int EQ2Emu_lua_GetSpellID(lua_State* state) {
LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
lua_interface->ResetFunctionStack(state);
if (!luaspell) {
if (!luaspell || !luaspell->spell) {
lua_interface->LogError("%s: LUA GetSpellID command error: must be used in a spell script", lua_interface->GetScriptName(state));
return 0;
}
@ -11321,7 +11321,7 @@ int EQ2Emu_lua_ShowLootWindow(lua_State* state) {
lua_interface->LogError("%s: LUA ShowLootWindow has no items", lua_interface->GetScriptName(state));
return 0;
}
client->Loot(spawn->GetLootCoins(), items, spawn);
client->SendLootResponsePacket(spawn->GetLootCoins(), items, spawn, true);
return 0;
}

View file

@ -575,14 +575,24 @@ bool Brain::CheckLootAllowed(Entity* entity) {
bool ret = false;
vector<int32>::iterator itr;
if(m_body)
if (m_body)
{
if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetInt8()
&& m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime()+(rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000))
if ((m_body->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && m_body->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->GetLooterSpawnID() > 0 && m_body->GetLooterSpawnID() != entity->GetID()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter spawn id %u does not match received %s(%u)", GetBody()->GetName(), m_body->GetLooterSpawnID(), entity->GetName(), entity->GetID());
return false;
}
if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetInt8()
&& m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32() * 1000)) {
return true;
if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetInt8()
&& m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime()+(rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000))
}
if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetInt8()
&& m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32() * 1000)) {
return true;
}
if ((m_body->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || m_body->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->HasSpawnLootWindowCompleted(entity->GetID())) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter %s(%u) has already completed their lotto selections.", GetBody()->GetName(), entity->GetName(), entity->GetID());
return false;
}
}
// Check the encounter list to see if the given entity is in it, if so return true.
MEncounter.readlock(__FUNCTION__, __LINE__);

View file

@ -125,7 +125,6 @@ Player::Player(){
}
Player::~Player(){
SetSaveSpellEffects(true);
DeleteSpellEffects();
for(int32 i=0;i<spells.size();i++){
safe_delete(spells[i]);
}

View file

@ -35,6 +35,7 @@ extern RuleManager rule_manager;
PlayerGroup::PlayerGroup(int32 id) {
m_id = id;
MGroupMembers.SetName("MGroupMembers");
SetDefaultGroupOptions();
}
PlayerGroup::~PlayerGroup() {
@ -163,6 +164,24 @@ void PlayerGroup::SimpleGroupMessage(const char* message) {
MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
void PlayerGroup::SendGroupMessage(int8 type, const char* message, ...) {
va_list argptr;
char buffer[4096];
buffer[0] = 0;
va_start(argptr, message);
vsnprintf(buffer, sizeof(buffer), message, argptr);
va_end(argptr);
deque<GroupMemberInfo*>::iterator itr;
MGroupMembers.readlock(__FUNCTION__, __LINE__);
for (itr = m_members.begin(); itr != m_members.end(); itr++) {
GroupMemberInfo* info = *itr;
if (info->client)
info->client->SimpleMessage(type, buffer);
}
MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
void PlayerGroup::GroupChatMessage(Spawn* from, int32 language, const char* message) {
deque<GroupMemberInfo*>::iterator itr;
MGroupMembers.readlock(__FUNCTION__, __LINE__);
@ -192,7 +211,6 @@ bool PlayerGroup::MakeLeader(Entity* new_leader) {
return true;
}
bool PlayerGroup::ShareQuestWithGroup(Client* quest_sharer, Quest* quest) {
if(!quest || !quest_sharer)
return false;
@ -311,6 +329,17 @@ void PlayerGroupManager::NewGroup(Entity* leader) {
// Create a new group with the valid ID we got from above
PlayerGroup* new_group = new PlayerGroup(m_nextGroupID);
GroupOptions goptions;
goptions.loot_method = leader->GetInfoStruct()->get_group_loot_method();
goptions.loot_items_rarity = leader->GetInfoStruct()->get_group_loot_items_rarity();
goptions.auto_split = leader->GetInfoStruct()->get_group_auto_split();
goptions.default_yell = leader->GetInfoStruct()->get_group_default_yell();
goptions.group_autolock = leader->GetInfoStruct()->get_group_autolock();
goptions.group_lock_method = leader->GetInfoStruct()->get_group_lock_method();
goptions.solo_autolock = leader->GetInfoStruct()->get_group_solo_autolock();
goptions.auto_loot_method = leader->GetInfoStruct()->get_group_auto_loot_method();
new_group->SetDefaultGroupOptions(&goptions);
// Add the new group to the list (need to do this first, AddMember needs ref to the PlayerGroup ptr -> UpdateGroupMemberInfo)
m_groups[m_nextGroupID] = new_group;
@ -599,6 +628,20 @@ void PlayerGroupManager::SimpleGroupMessage(int32 group_id, const char* message)
m_groups[group_id]->SimpleGroupMessage(message);
}
void PlayerGroupManager::SendGroupMessage(int32 group_id, int8 type, const char* message, ...) {
std::shared_lock lock(MGroups);
va_list argptr;
char buffer[4096];
buffer[0] = 0;
va_start(argptr, message);
vsnprintf(buffer, sizeof(buffer), message, argptr);
va_end(argptr);
if (m_groups.count(group_id) > 0)
m_groups[group_id]->SendGroupMessage(type, buffer);
}
void PlayerGroupManager::GroupMessage(int32 group_id, const char* message, ...) {
va_list argptr;
char buffer[4096];
@ -928,3 +971,30 @@ Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_posit
return ret;
}
void PlayerGroup::SetDefaultGroupOptions(GroupOptions* options) {
MGroupMembers.writelock();
if (options != nullptr) {
group_options.loot_method = options->loot_method;
group_options.loot_items_rarity = options->loot_items_rarity;
group_options.auto_split = options->auto_split;
group_options.default_yell = options->default_yell;
group_options.group_lock_method = options->group_lock_method;
group_options.group_autolock = options->group_autolock;
group_options.solo_autolock = options->solo_autolock;
group_options.auto_loot_method = options->auto_loot_method;
}
else {
group_options.loot_method = 1;
group_options.loot_items_rarity = 0;
group_options.auto_split = 1;
group_options.default_yell = 1;
group_options.group_lock_method = 0;
group_options.group_autolock = 0;
group_options.solo_autolock = 0;
group_options.auto_loot_method = 0;
group_options.last_looted_index = 0;
}
MGroupMembers.releasewritelock();
}

View file

@ -38,8 +38,11 @@ struct GroupOptions{
int8 loot_items_rarity;
int8 auto_split;
int8 default_yell;
int8 group_lock_method;
int8 group_autolock;
int8 solo_autolock;
int8 auto_loot_method;
int8 last_looted_index;
};
/// <summary>All the generic info for the group window, plus a client pointer for players</summary>
@ -94,6 +97,7 @@ public:
void SimpleGroupMessage(const char* message);
void SendGroupMessage(int8 type, const char* message, ...);
void GroupChatMessage(Spawn* from, int32 language, const char* message);
bool MakeLeader(Entity* new_leader);
@ -103,8 +107,14 @@ public:
void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked=false);
Entity* GetGroupMemberByPosition(Entity* seeker, int32 mapped_position);
void SetDefaultGroupOptions(GroupOptions* options=nullptr);
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; }
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
@ -186,6 +196,7 @@ public:
bool HasGroupCompletedQuest(int32 group_id, int32 quest_id);
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);
bool MakeLeader(int32 group_id, Entity* new_leader);

View file

@ -141,6 +141,12 @@ Spawn::Spawn(){
scared_by_strong_players = false;
is_alive = true;
SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE);
loot_method = GroupLootMethod::METHOD_FFA;
loot_rarity = 0;
loot_group_id = 0;
looter_spawn_id = 0;
is_loot_complete = false;
is_loot_dispensed = false;
}
Spawn::~Spawn(){
@ -3707,23 +3713,71 @@ void Spawn::UpdateEncounterState(int8 new_state) {
}
}
void Spawn::CheckEncounterState(Entity* victim) {
if(!IsEntity() || !victim->IsNPC())
void Spawn::CheckEncounterState(Entity* victim, bool test_auto_lock) {
if (!IsEntity() || !victim->IsNPC())
return;
Entity* ent = ((Entity*)this);
if(victim->GetLockedNoLoot() == ENCOUNTER_STATE_AVAILABLE) {
if(!ent->GetInfoStruct()->get_engaged_encounter()) {
ent->GetInfoStruct()->set_engaged_encounter(1);
}
if (victim->GetLockedNoLoot() == ENCOUNTER_STATE_AVAILABLE) {
Entity* attacker = nullptr;
if(ent->GetOwner())
if (ent->GetOwner())
attacker = ent->GetOwner();
else
attacker = ent;
if(!attacker->GetInfoStruct()->get_engaged_encounter()) {
bool matchedAutoLock = false;
if (attacker->IsEntity() && ((Entity*)attacker)->GetGroupMemberInfo()) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
GroupMemberInfo* gmi = ((Entity*)attacker)->GetGroupMemberInfo();
if (gmi && gmi->group_id)
{
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group && ((group->GetGroupOptions()->group_lock_method && group->GetGroupOptions()->group_autolock == 1) || attacker->GetGroupMemberInfo()->leader))
{
matchedAutoLock = true;
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if (member->GetZone() != attacker->GetZone())
continue;
if (member->IsEntity()) {
if (!member->GetInfoStruct()->get_engaged_encounter()) {
member->GetInfoStruct()->set_engaged_encounter(1);
}
if (((NPC*)victim)->Brain()) {
((NPC*)victim)->Brain()->AddHate(member, 0);
((NPC*)victim)->Brain()->AddToEncounter(member);
victim->AddTargetToEncounter(member);
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
else if (attacker->GetInfoStruct()->get_group_solo_autolock()) {
matchedAutoLock = true;
if (((NPC*)victim)->Brain()) {
((NPC*)victim)->Brain()->AddHate(attacker, 0);
((NPC*)victim)->Brain()->AddToEncounter(attacker);
victim->AddTargetToEncounter(attacker);
}
}
if (test_auto_lock && !matchedAutoLock) {
return;
}
if (!ent->GetInfoStruct()->get_engaged_encounter()) {
ent->GetInfoStruct()->set_engaged_encounter(1);
}
if (!attacker->GetInfoStruct()->get_engaged_encounter()) {
attacker->GetInfoStruct()->set_engaged_encounter(1);
}
@ -3732,8 +3786,8 @@ void Spawn::CheckEncounterState(Entity* victim) {
int8 difficulty = attacker->GetArrowColor(victim->GetLevel());
int8 new_enc_state = ENCOUNTER_STATE_AVAILABLE;
if(skip_loot_gray_mob_flag && difficulty == ARROW_COLOR_GRAY) {
if(!attacker->IsPlayer() && !attacker->IsBot()) {
if (skip_loot_gray_mob_flag && difficulty == ARROW_COLOR_GRAY) {
if (!attacker->IsPlayer() && !attacker->IsBot()) {
new_enc_state = ENCOUNTER_STATE_BROKEN;
}
else {
@ -3741,7 +3795,7 @@ void Spawn::CheckEncounterState(Entity* victim) {
}
}
else {
if(attacker->IsPlayer() || attacker->IsBot()) {
if (attacker->IsPlayer() || attacker->IsBot()) {
new_enc_state = ENCOUNTER_STATE_LOCKED;
}
else {
@ -4210,6 +4264,16 @@ int32 Spawn::GetLootItemID() {
return ret;
}
void Spawn::GetLootItemsList(std::vector<int32>* out_entries) {
if(!out_entries)
return;
vector<Item*>::iterator itr;
for (itr = loot_items.begin(); itr != loot_items.end(); itr++) {
out_entries->push_back((*itr)->details.item_id);
}
}
bool Spawn::HasLootItemID(int32 id) {
bool ret = false;
@ -4703,3 +4767,263 @@ void Spawn::SendGroupUpdate() {
world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id);
}
}
bool Spawn::AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: AddNeedGreedItemRequest Item ID: %u, Spawn ID: %u, Need Item: %u", GetName(), item_id, spawn_id, need_item);
if (HasSpawnNeedGreedEntry(item_id, spawn_id)) {
return false;
}
need_greed_items.insert(make_pair(item_id, std::make_pair(spawn_id, need_item)));
AddSpawnLootWindowCompleted(spawn_id, false);
return true;
}
bool Spawn::AddLottoItemRequest(int32 item_id, int32 spawn_id) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: AddLottoItemRequest Item ID: %u, Spawn ID: %u", GetName(), item_id, spawn_id);
if (HasSpawnLottoEntry(item_id, spawn_id)) {
return false;
}
lotto_items.insert(make_pair(item_id, spawn_id));
AddSpawnLootWindowCompleted(spawn_id, false);
return true;
}
void Spawn::AddSpawnLootWindowCompleted(int32 spawn_id, bool status_) {
if (loot_complete.find(spawn_id) == loot_complete.end()) {
loot_complete.insert(make_pair(spawn_id, status_));
}
is_loot_complete = HasLootWindowCompleted();
}
bool Spawn::SetSpawnLootWindowCompleted(int32 spawn_id) {
std::map<int32, bool>::iterator itr = loot_complete.find(spawn_id);
if (itr != loot_complete.end()) {
itr->second = true;
is_loot_complete = HasLootWindowCompleted();
return true;
}
return false;
}
bool Spawn::HasSpawnLootWindowCompleted(int32 spawn_id) {
std::map<int32, bool>::iterator itr = loot_complete.find(spawn_id);
if (itr != loot_complete.end() && itr->second) {
return true;
}
return false;
}
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);
if (spawn_id == itr->second.first) {
return true;
}
}
return false;
}
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);
if (spawn_id == itr->second) {
return true;
}
}
return false;
}
void Spawn::GetSpawnLottoEntries(int32 item_id, std::map<int32, int32>* out_entries) {
if (!out_entries)
return;
std::map<int32, bool> spawn_matches;
for (auto [itr, endrange] = lotto_items.equal_range(item_id); itr != endrange; itr++) {
out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100)));
spawn_matches[itr->second] = true;
}
// 0xFFFFFFFF represents selecting "All" on the lotto screen
for (auto [itr, endrange] = lotto_items.equal_range(0xFFFFFFFF); itr != endrange; itr++) {
if (spawn_matches.find(itr->second) == spawn_matches.end()) {
out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100)));
}
}
}
void Spawn::GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map<int32, int32>* out_entries) {
if (!out_entries)
return;
for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) {
out_entries->insert(std::make_pair(itr->second.first, (int32)MakeRandomInt(0, 100)));
}
}
bool Spawn::HasLootWindowCompleted() {
std::map<int32, bool>::iterator itr;
for (itr = loot_complete.begin(); itr != loot_complete.end(); itr++) {
if (!itr->second)
return false;
}
return true;
}
void Spawn::StartLootTimer(Spawn* looter) {
if (!IsLootTimerRunning()) {
int32 loot_timer_time = rule_manager.GetGlobalRule(R_Loot, LootDistributionTime)->GetInt32() * 1000;
if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) {
loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) / 2;
}
if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) {
loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) / 2;
}
if(loot_timer_time < 1000) {
loot_timer_time = 60000; // hardcode assure they aren't setting some really ridiculous low number
}
loot_timer.Start(loot_timer_time, true);
}
if (looter) {
looter_spawn_id = looter->GetID();
}
}
void Spawn::CloseLoot(Spawn* sender) {
if (sender) {
SetSpawnLootWindowCompleted(sender->GetID());
}
if (sender && looter_spawn_id > 0 && sender->GetID() != looter_spawn_id) {
LogWrite(LOOT__ERROR, 0, "Loot", "%s: CloseLoot Looter Spawn ID: %u does not match sender %u.", GetName(), looter_spawn_id, sender->GetID());
return;
}
if (!IsLootTimerRunning() && GetLootMethod() != GroupLootMethod::METHOD_LOTTO && GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) {
loot_timer.Disable();
}
looter_spawn_id = 0;
}
void Spawn::SetLootMethod(GroupLootMethod method, int8 item_rarity, int32 group_id) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Set Loot Method : %u, group id : %u", GetName(), (int32)method, group_id);
loot_group_id = group_id;
loot_method = method;
loot_rarity = item_rarity;
if (loot_name.size() < 1) {
loot_name = std::string(GetName());
}
}
bool Spawn::IsItemInLootTier(Item* item) {
if (!item)
return true;
bool skipItem = true;
switch (GetLootRarity()) {
case LootTier::ITEMS_TREASURED_PLUS: {
if (item->details.tier >= ITEM_TAG_TREASURED) {
skipItem = false;
}
break;
}
case LootTier::ITEMS_LEGENDARY_PLUS: {
if (item->details.tier >= ITEM_TAG_LEGENDARY) {
skipItem = false;
}
break;
}
case LootTier::ITEMS_FABLED_PLUS: {
if (item->details.tier >= ITEM_TAG_FABLED) {
skipItem = false;
}
break;
}
default: {
skipItem = false;
break;
}
}
return skipItem;
}
void Spawn::DistributeGroupLoot_RoundRobin(std::vector<int32>* item_list, bool roundRobinTrashLoot) {
std::vector<int32>::iterator item_itr;
for (item_itr = item_list->begin(); item_itr != item_list->end(); item_itr++) {
int32 item_id = *item_itr;
Item* tmpItem = master_item_list.GetItem(item_id);
Spawn* looter = nullptr;
bool skipItem = IsItemInLootTier(tmpItem);
if ((skipItem && !roundRobinTrashLoot) || (!skipItem && roundRobinTrashLoot))
continue;
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(GetLootGroupID());
if (group) {
group->MGroupMembers.writelock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
int8 index = group->GetLastLooterIndex();
if (index >= members->size()) {
index = 0;
}
GroupMemberInfo* gmi = members->at(index);
if (gmi) {
looter = gmi->member;
}
bool loopAttempted = false;
while (looter) {
if (!looter->IsPlayer()) {
index++;
if (index >= members->size()) {
if (loopAttempted) {
looter = nullptr;
break;
}
loopAttempted = true;
index = 0;
}
gmi = members->at(index);
if (gmi) {
looter = gmi->member;
}
continue;
}
else {
break;
}
}
index += 1;
group->SetNextLooterIndex(index);
group->MGroupMembers.releasewritelock(__FUNCTION__, __LINE__);
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
if (looter) {
if (looter->IsPlayer()) {
Item* item = LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(this, item, ((Player*)looter), roundRobinTrashLoot);
if (!success)
AddLootItem(item);
}
else {
Item* item = LootItem(item_id);
safe_delete(item);
}
}
}
}

View file

@ -264,6 +264,28 @@ struct TimedGridData {
int32 widget_id;
};
enum GroupLootMethod {
METHOD_LEADER=0,
METHOD_FFA=1,
METHOD_LOTTO=2,
METHOD_NEED_BEFORE_GREED=3,
METHOD_ROUND_ROBIN=4
};
enum AutoLootMode {
METHOD_DISABLED=0,
METHOD_ACCEPT=1,
METHOD_DECLINE=2
};
enum LootTier {
ITEMS_ALL=0,
ITEMS_TREASURED_PLUS=1,
ITEMS_LEGENDARY_PLUS=2,
ITEMS_FABLED_PLUS=3
};
class Spawn {
public:
Spawn();
@ -941,6 +963,15 @@ public:
MLootItems.unlock();
}
int32 GetLootCount() {
int32 loot_item_count = 0;
MLootItems.lock();
loot_item_count = loot_items.size();
MLootItems.unlock();
return loot_item_count;
}
void ClearNonBodyLoot() {
MLootItems.lock();
@ -964,10 +995,14 @@ public:
UnlockLoot();
return coins;
}
void SetLootCoins(int32 val) {
LockLoot();
void SetLootCoins(int32 val, bool lockloot = true) {
if(lockloot)
LockLoot();
loot_coins = val;
UnlockLoot();
if(lockloot)
UnlockLoot();
}
void AddLootCoins(int32 coins) {
LockLoot();
@ -1016,7 +1051,7 @@ public:
bool IsInSpawnGroup(Spawn* spawn);
Spawn* IsSpawnGroupMembersAlive(Spawn* ignore_spawn=nullptr, bool npc_only = true);
void UpdateEncounterState(int8 new_state);
void CheckEncounterState(Entity* victim);
void CheckEncounterState(Entity* victim, bool test_auto_lock = false);
void AddTargetToEncounter(Entity* entity);
void SendSpawnChanges(bool val){ send_spawn_changes = val; }
@ -1301,6 +1336,47 @@ public:
void SendGroupUpdate();
void OverrideLootMethod(GroupLootMethod newMethod) { loot_method = newMethod; }
void SetLootMethod(GroupLootMethod method, int8 item_rarity = 0, int32 group_id = 0);
int32 GetLootGroupID() { return loot_group_id; }
GroupLootMethod GetLootMethod() { return loot_method; }
int8 GetLootRarity() { return loot_rarity; }
int32 GetLootTimeRemaining() { return loot_timer.GetRemainingTime(); }
bool IsLootTimerRunning() { return loot_timer.Enabled(); }
bool CheckLootTimer() { return loot_timer.Check(); }
void DisableLootTimer() { return loot_timer.Disable(); }
int32 GetLooterSpawnID() { return looter_spawn_id; }
void SetLooterSpawnID(int32 id) { looter_spawn_id = id; }
bool AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item);
bool AddLottoItemRequest(int32 item_id, int32 spawn_id);
void AddSpawnLootWindowCompleted(int32 spawn_id, bool status_);
bool SetSpawnLootWindowCompleted(int32 spawn_id);
bool HasSpawnLootWindowCompleted(int32 spawn_id);
bool HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id);
bool HasSpawnLottoEntry(int32 item_id, int32 spawn_id);
void GetSpawnLottoEntries(int32 item_id, std::map<int32, int32>* out_entries);
void GetLootItemsList(std::vector<int32>* out_entries);
void GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map<int32, int32>* out_entries);
bool HasLootWindowCompleted();
bool IsLootWindowComplete() { return is_loot_complete; }
void SetLootDispensed() { is_loot_dispensed = true; }
bool IsLootDispensed() { return is_loot_dispensed; }
std::map<int32, bool>* GetLootWindowList() { return &loot_complete; }
void StartLootTimer(Spawn* looter);
void CloseLoot(Spawn* sender);
void SetLootName(char* name) {
if(name != nullptr) {
loot_name = std::string(name);
}
}
const char* GetLootName() { return loot_name.c_str(); }
bool IsItemInLootTier(Item* item);
void DistributeGroupLoot_RoundRobin(std::vector<int32>* item_list, bool roundRobinTrashLoot = false); // trash loot is what falls under the item tier requirement by group options
mutable std::shared_mutex MIgnoredWidgets;
std::map<int32, bool> ignored_widgets;
@ -1346,9 +1422,23 @@ protected:
void CheckProximities();
Timer pause_timer;
private:
int32 loot_group_id;
GroupLootMethod loot_method;
int8 loot_rarity;
Timer loot_timer;
int32 looter_spawn_id;
vector<Item*> loot_items;
int32 loot_coins;
std::multimap<int32, int32> lotto_items;
std::multimap<int32, std::pair<int32, bool>> need_greed_items;
std::map<int32, bool> loot_complete;
bool is_loot_complete;
bool is_loot_dispensed;
std::string loot_name;
bool trap_triggered;
int32 trap_state;
int32 chest_drop_time;

View file

@ -2066,7 +2066,7 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
if (!stricmp(prop_name, CHAR_PROPERTY_SPEED))
{
float new_speed = atof(prop_value);
client->GetPlayer()->SetSpeed(new_speed,true);
client->GetPlayer()->SetSpeed(new_speed, true);
client->GetPlayer()->SetCharSheetChanged(true);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_FLYMODE))
@ -2110,6 +2110,46 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages.");
}
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTMETHOD))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_loot_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTITEMRARITY))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOSPLIT))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_auto_split(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPDEFAULTYELL))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_default_yell(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOLOCK))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_autolock(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPSOLOAUTOLOCK))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_solo_autolock(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_AUTOLOOTMETHOD))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOCKMETHOD))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val);
}
}
return true;

View file

@ -113,6 +113,15 @@ using namespace std;
#define CHAR_PROPERTY_GMVISION "modify_gmvision"
#define CHAR_PROPERTY_LUADEBUG "modify_luadebug"
#define CHAR_PROPERTY_GROUPLOOTMETHOD "group_loot_method"
#define CHAR_PROPERTY_GROUPLOOTITEMRARITY "group_loot_item_rarity"
#define CHAR_PROPERTY_GROUPAUTOSPLIT "group_auto_split"
#define CHAR_PROPERTY_GROUPDEFAULTYELL "group_default_yell"
#define CHAR_PROPERTY_GROUPAUTOLOCK "group_autolock"
#define CHAR_PROPERTY_GROUPLOCKMETHOD "group_lock_method"
#define CHAR_PROPERTY_GROUPSOLOAUTOLOCK "group_solo_autolock"
#define CHAR_PROPERTY_AUTOLOOTMETHOD "group_auto_loot_method"
#define DB_TYPE_SPELLEFFECTS 1
#define DB_TYPE_MAINTAINEDEFFECTS 2

View file

@ -1038,10 +1038,16 @@ void Client::SendDefaultGroupOptions() {
*/
PacketStruct* default_options = configReader.getStruct("WS_DefaultGroupOptions", GetVersion());
if (default_options) {
default_options->setDataByName("loot_method", 1);
default_options->setDataByName("loot_items_rarity", 1);
default_options->setDataByName("auto_split_coin", 1);
default_options->setDataByName("default_yell_method", 1);
default_options->setDataByName("loot_method", GetPlayer()->GetInfoStruct()->get_group_loot_method());
default_options->setDataByName("loot_items_rarity", GetPlayer()->GetInfoStruct()->get_group_loot_items_rarity());
default_options->setDataByName("auto_split_coin", GetPlayer()->GetInfoStruct()->get_group_auto_split());
default_options->setDataByName("default_yell_method", GetPlayer()->GetInfoStruct()->get_group_default_yell());
default_options->setDataByName("group_autolock", GetPlayer()->GetInfoStruct()->get_group_autolock());
default_options->setDataByName("default_group_lock_method", GetPlayer()->GetInfoStruct()->get_group_lock_method());
if(GetVersion() > 561) {
default_options->setDataByName("solo_autolock", GetPlayer()->GetInfoStruct()->get_group_solo_autolock());
default_options->setDataByName("auto_loot_method", GetPlayer()->GetInfoStruct()->get_group_auto_loot_method());
}
EQ2Packet* app7 = default_options->serialize();
QueuePacket(app7);
safe_delete(default_options);
@ -1131,6 +1137,66 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
safe_delete(request);
break;
}
case OP_DefaultGroupOptionsMsg: {
PacketStruct* packet = configReader.getStruct("WS_DefaultGroupOptions", GetVersion());
if (packet) {
if (packet->LoadPacketData(app->pBuffer, app->size)) {
packet->PrintPacket();
int8 loot_method = packet->getType_int8_ByName("loot_method");
int8 loot_items_rarity = packet->getType_int8_ByName("loot_items_rarity");
int8 auto_split_coin = packet->getType_int8_ByName("auto_split_coin");
int8 default_yell_method = packet->getType_int8_ByName("default_yell_method");
int8 autolock = packet->getType_int8_ByName("group_autolock");
int8 group_lock_method = packet->getType_int8_ByName("default_group_lock_method");
int8 solo_autolock = packet->getType_int8_ByName("solo_autolock");
int8 auto_loot_method = 0;
if (GetVersion() > 561) {
auto_loot_method = packet->getType_int8_ByName("auto_loot_method");
if (auto_loot_method > AutoLootMode::METHOD_DECLINE)
auto_loot_method = AutoLootMode::METHOD_DECLINE;
}
GetPlayer()->GetInfoStruct()->set_group_loot_method(loot_method);
GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(loot_items_rarity);
GetPlayer()->GetInfoStruct()->set_group_auto_split(auto_split_coin);
GetPlayer()->GetInfoStruct()->set_group_default_yell(default_yell_method);
GetPlayer()->GetInfoStruct()->set_group_autolock(autolock);
GetPlayer()->GetInfoStruct()->set_group_lock_method(group_lock_method);
GetPlayer()->GetInfoStruct()->set_group_solo_autolock(solo_autolock);
GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(auto_loot_method);
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTMETHOD, (char*)std::to_string(loot_method).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTITEMRARITY, (char*)std::to_string(loot_items_rarity).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOSPLIT, (char*)std::to_string(auto_split_coin).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPDEFAULTYELL, (char*)std::to_string(default_yell_method).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOLOCK, (char*)std::to_string(autolock).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOCKMETHOD, (char*)std::to_string(group_lock_method).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPSOLOAUTOLOCK, (char*)std::to_string(solo_autolock).c_str());
database.insertCharacterProperty(this, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(auto_loot_method).c_str());
if (this->GetPlayer()->GetGroupMemberInfo() && this->GetPlayer()->GetGroupMemberInfo()->leader)
{
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id);
if (group)
{
GroupOptions goptions;
goptions.loot_method = loot_method;
goptions.loot_items_rarity = loot_items_rarity;
goptions.auto_split = auto_split_coin;
goptions.default_yell = default_yell_method;
goptions.group_autolock = autolock;
goptions.solo_autolock = solo_autolock;
goptions.auto_loot_method = auto_loot_method;
group->SetDefaultGroupOptions(&goptions);
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
}
safe_delete(packet);
}
break;
}
case OP_MapRequest: {
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapRequest", opcode, opcode);
PacketStruct* packet = configReader.getStruct("WS_MapRequest", GetVersion());
@ -1507,7 +1573,21 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
}
case OP_LootItemsRequestMsg: {
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_LootItemsRequestMsg", opcode, opcode);
HandleLoot(app);
HandleLootItemRequestPacket(app);
break;
}
case OP_StoppedLootingMsg: {
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoppedLootingMsg", opcode, opcode);
if (app->size < sizeof(int32))
break;
int32 loot_id = 0;
memcpy(&loot_id, app->pBuffer, sizeof(int32));
Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id);
if(spawn) {
spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID());
spawn->SetLooterSpawnID(0);
}
break;
}
case OP_WaypointSelectMsg: {
@ -1561,8 +1641,9 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
}
else
{
if(zoning_destination)
if(zoning_destination) {
SetCurrentZone(zoning_destination);
}
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyToZoneMsg", opcode, opcode);
bool succeed_override_zone = true;
if(!GetCurrentZone()) {
@ -2664,28 +2745,64 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
return ret;
}
bool Client::HandleLootItem(Spawn* entity, Item* item) {
bool Client::HandleLootItem(Spawn* entity, Item* item, Spawn* target, bool overrideLootRestrictions) {
if (!item) {
SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find item to loot!");
return false;
}
int32 conflictItemList = 0, conflictequipmentList = 0, conflictAppearanceEquipmentList = 0;
int16 lore_stack_count = 0;
if(((conflictItemList = player->item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE ||
(conflictequipmentList = player->equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE ||
(conflictAppearanceEquipmentList = player->appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) {
Player* lootingPlayer = player;
Client* lootingClient = this;
if (target != nullptr && target != lootingPlayer && target->IsPlayer()) {
lootingPlayer = (Player*)target;
lootingClient = lootingPlayer->GetClient();
}
// needs to only be checked before expiration of loot restrictions
if (!overrideLootRestrictions) {
if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID())) {
LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Group ID from %s did not match Item: %s (%u), expected group id %u.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id, entity->GetLootGroupID());
return false;
}
if (entity->GetLootMethod() != GroupLootMethod::METHOD_FFA) {
switch (entity->GetLootMethod()) {
case GroupLootMethod::METHOD_LEADER: {
if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || (lootingPlayer->GetGroupMemberInfo() && (lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID() || !lootingPlayer->GetGroupMemberInfo()->leader)))) {
LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Attempt from %s was not allowed with Item: %s (%u), must be group leader.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id);
return false;
}
break;
}
case GroupLootMethod::METHOD_LOTTO:
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
if (entity->IsLootTimerRunning()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Loot Timer is still running, flag player %s to lotto Item: %s (%u).", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id);
return false;
}
break;
}
}
}
}
if (((conflictItemList = lootingPlayer->item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE ||
(conflictequipmentList = lootingPlayer->equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE ||
(conflictAppearanceEquipmentList = lootingPlayer->appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) {
Message(CHANNEL_COLOR_RED, "You cannot loot %s due to lore conflict.", item->name.c_str());
return false;
}
else if(conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || conflictAppearanceEquipmentList == STACK_LORE) {
else if (conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || conflictAppearanceEquipmentList == STACK_LORE) {
Message(CHANNEL_COLOR_RED, "You cannot loot %s due to stack lore conflict.", item->name.c_str());
return false;
}
if (player->item_list.HasFreeSlot() || player->item_list.CanStack(item)) {
if (player->item_list.AssignItemToFreeSlot(item)) {
if(item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support
GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo();
if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item)) {
if (lootingPlayer->item_list.AssignItemToFreeSlot(item)) {
if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support
GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo();
if (gmi && gmi->group_id)
{
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
@ -2693,18 +2810,18 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
if(members) {
if (members) {
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if(!member)
if (!member)
continue;
if ((member->GetZone() != this->GetPlayer()->GetZone()))
if ((member->GetZone() != lootingClient->GetPlayer()->GetZone()))
continue;
if(member->IsPlayer()) {
item->grouped_char_ids.insert(std::make_pair(((Player*)member)->GetCharacterID(), true));
item->save_needed = true;
if (member->IsPlayer()) {
item->grouped_char_ids.insert(std::make_pair(((Player*)member)->GetCharacterID(), true));
item->save_needed = true;
}
}
}
@ -2715,12 +2832,12 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
int8 type = CHANNEL_LOOT;
if (entity) {
Message(type, "You loot %s from the corpse of %s", item->CreateItemLink(GetVersion()).c_str(), entity->GetName());
lootingClient->Message(type, "You loot %s from the corpse of %s", item->CreateItemLink(GetVersion()).c_str(), entity->GetName());
}
else {
Message(type, "You found a %s.", item->CreateItemLink(GetVersion()).c_str());
lootingClient->Message(type, "You found a %s.", item->CreateItemLink(GetVersion()).c_str());
}
Guild* guild = player->GetGuild();
Guild* guild = lootingPlayer->GetGuild();
if (guild && item->details.tier >= ITEM_TAG_LEGENDARY) {
char adjective[32];
int8 type;
@ -2737,153 +2854,210 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
strncpy(adjective, "Legendary", sizeof(adjective) - 1);
type = GUILD_EVENT_LOOTS_LEGENDARY_ITEM;
}
guild->AddNewGuildEvent(type, "%s has looted the %s %s", Timer::GetUnixTimeStamp(), true, player->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
guild->SendMessageToGuild(type, "%s has looted the %s %s", player->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
guild->AddNewGuildEvent(type, "%s has looted the %s %s", Timer::GetUnixTimeStamp(), true, lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
guild->SendMessageToGuild(type, "%s has looted the %s %s", lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
}
if (item->GetItemScript() && lua_interface)
lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, lootingPlayer);
CheckPlayerQuestsItemUpdate(item);
lootingClient->CheckPlayerQuestsItemUpdate(item);
if(GetVersion() <= 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (GetVersion() <= 546) {
EQ2Packet* outapp = lootingPlayer->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
lootingClient->QueuePacket(outapp);
}
return true;
}
else
SimpleMessage(CHANNEL_COLOR_RED, "Could not find free slot to place item.");
lootingClient->SimpleMessage(CHANNEL_COLOR_RED, "Could not find free slot to place item.");
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to loot item: Inventory is FULL.");
lootingClient->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to loot item: Inventory is FULL.");
return false;
}
bool Client::HandleLootItem(Spawn* entity, int32 item_id) {
bool Client::HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target) {
if (!entity) {
return false;
}
Item* item = entity->LootItem(item_id);
bool success = false;
success = HandleLootItem(entity, item);
if(!success)
success = HandleLootItem(entity, item, target);
if (!success)
entity->AddLootItem(item);
return success;
}
void Client::HandleLoot(EQApplicationPacket* app) {
PacketStruct* packet = configReader.getStruct("WS_LootType", GetVersion());
void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) {
PacketStruct* packet = configReader.getStruct("WS_LootItem", GetVersion());
if (packet) {
if(packet->LoadPacketData(app->pBuffer, app->size)) {
if (packet->LoadPacketData(app->pBuffer, app->size)) {
int32 loot_id = packet->getType_int32_ByName("loot_id");
bool loot_all = (packet->getType_int8_ByName("loot_all") == 1);
safe_delete(packet);
int32 item_id = 0;
Item* item = 0;
int32 target_id = packet->getType_int32_ByName("target_id");
int8 button_clicked = packet->getType_int8_ByName("button_clicked");
Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id);
if (player->HasPendingLootItems(loot_id)) {
Item* master_item = 0;
if (loot_all) {
vector<Item*>* items = player->GetPendingLootItems(loot_id);
if (items) {
for (int32 i = 0; loot_all && i < items->size(); i++) {
master_item = items->at(i);
if (master_item) {
item = new Item(master_item);
if (item) {
loot_all = HandleLootItem(0, item);
if (loot_all) {
player->RemovePendingLootItem(loot_id, item->details.item_id);
if(!spawn) {
safe_delete(packet);
return;
}
Item* item = nullptr;
vector<Item*>* items = player->GetPendingLootItems(loot_id);
if (items) {
int32 item_id = packet->getType_int32_ByName("item_id_0");
for (int32 i = 0; i < items->size(); i++) {
Item* master_item = items->at(i);
if (master_item && (loot_all || master_item->details.item_id == item_id)) {
item = new Item(master_item);
if (item) {
loot_all = HandleLootItem(0, item);
if (loot_all) {
player->RemovePendingLootItem(loot_id, item->details.item_id);
if(GetVersion() <= 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
}
if (GetVersion() <= 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
}
}
safe_delete(items);
if (!loot_all)
break;
}
}
safe_delete(items);
safe_delete(packet);
return;
}
spawn->LockLoot();
bool unlockedLoot = false;
if (spawn && !spawn->Alive() && spawn->IsNPC() && ((NPC*)spawn)->Brain()->CheckLootAllowed(player)) {
if (loot_all) {
switch (spawn->GetLootMethod()) {
case GroupLootMethod::METHOD_LOTTO: {
spawn->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID());
break;
}
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
spawn->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true);
}
default: {
if (!unlockedLoot) {
spawn->UnlockLoot();
unlockedLoot = true;
}
int32 item_id = 0;
while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) {
loot_all = HandleLootItemByID(spawn, item_id, GetPlayer());
}
break;
}
}
spawn->UnlockLoot();
if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) {
CloseLoot(loot_id);
}
}
else {
packet = configReader.getStruct("WS_LootItem", GetVersion());
if (packet) {
if(packet->LoadPacketData(app->pBuffer, app->size)) {
item_id = packet->getType_int32_ByName("item_id");
vector<Item*>* items = player->GetPendingLootItems(loot_id);
if (items) {
for (int32 i = 0; i < items->size(); i++) {
master_item = items->at(i);
if (master_item && master_item->details.item_id == item_id) {
item = new Item(master_item);
if (item && HandleLootItem(0, item))
player->RemovePendingLootItem(loot_id, item->details.item_id);
break;
}
}
safe_delete(items);
int8 item_count = packet->getType_int8_ByName("item_count");
for (int8 cur = 0; cur < item_count; cur++) {
char item_field_name[64];
snprintf(item_field_name, 64, "item_id_%u", cur);
int32 item_id = packet->getType_int32_ByName(item_field_name);
Spawn* target = this->GetPlayer();
if (target_id != 0xFFFFFFFF && GetPlayer()->GetGroupMemberInfo()) {
Spawn* destTarget = GetPlayer()->GetSpawnWithPlayerID(target_id);
if (destTarget && (!destTarget->IsPlayer() || !world.GetGroupManager()->IsInGroup(GetPlayer()->GetGroupMemberInfo()->group_id, ((Player*)destTarget)))) {
SimpleMessage(CHANNEL_COMMAND_TEXT, "HACKS!!");
safe_delete(packet);
spawn->UnlockLoot();
return;
}
target = destTarget;
}
safe_delete(packet);
bool breakLoopAllLooted = false;
switch (spawn->GetLootMethod()) {
case GroupLootMethod::METHOD_LOTTO: {
spawn->AddLottoItemRequest(item_id, GetPlayer()->GetID());
break;
}
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
if (button_clicked == 3) { // decline
break;
}
if (GetVersion() <= 546) {
button_clicked = 1; // selecting is need
}
spawn->AddNeedGreedItemRequest(item_id, GetPlayer()->GetID(), (button_clicked == 1));
break;
}
default: {
if (!unlockedLoot) {
spawn->UnlockLoot();
unlockedLoot = true;
}
if (!loot_all) {
HandleLootItemByID(spawn, item_id, target);
}
else {
while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) {
loot_all = HandleLootItemByID(spawn, item_id, target);
}
breakLoopAllLooted = true;
}
break;
}
}
if (breakLoopAllLooted) {
break;
}
}
if (!unlockedLoot) {
spawn->UnlockLoot();
}
if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO ||
(spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED && item_count >= spawn->GetLootCount())) {
CloseLoot(loot_id);
}
}
if(GetVersion() > 546) {
if (GetVersion() > 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
Loot(0, player->GetPendingLootItems(loot_id), spawn);
}
else {
if (spawn && !spawn->Alive() && spawn->IsNPC() && ((NPC*)spawn)->Brain()->CheckLootAllowed(player)) {
if (loot_all) {
while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) {
loot_all = HandleLootItem(spawn, item_id);
}
}
else {
packet = configReader.getStruct("WS_LootItem", GetVersion());
if (packet) {
if(packet->LoadPacketData(app->pBuffer, app->size)) {
item_id = packet->getType_int32_ByName("item_id");
HandleLootItem(spawn, item_id);
}
safe_delete(packet);
}
}
if(GetVersion() > 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
Loot(spawn);
if (!spawn->HasLoot()) {
CloseLoot(loot_id);
if (spawn->IsNPC())
GetCurrentZone()->RemoveDeadSpawn(spawn);
}
if (spawn->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && spawn->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) {
LootSpawnRequest(spawn);
}
else {
if (!spawn) {
LogWrite(WORLD__ERROR, 0, "World", "Unknown id of %u when looting!", loot_id);
SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn to loot!");
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not unable to loot that at this time.");
spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID());
}
if (!spawn->HasLoot()) {
CloseLoot(loot_id);
if (spawn->IsNPC())
GetCurrentZone()->RemoveDeadSpawn(spawn);
}
}
else {
spawn->UnlockLoot();
if (!spawn) {
LogWrite(WORLD__ERROR, 0, "World", "Unknown id of %u when looting!", loot_id);
SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn to loot!");
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not unable to loot that at this time.");
}
}
else {
safe_delete(packet);
}
}
safe_delete(packet);
}
}
void Client::HandleSkillInfoRequest(EQApplicationPacket* app) {
@ -4500,6 +4674,8 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) {
LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__);
GetCurrentZone()->RemoveSpawn(player, false, true, true, true, !is_spell);
GetPlayer()->DeleteSpellEffects(true);
LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName());
SetZoningDestination(new_zone);
SetCurrentZone(new_zone);
@ -5200,11 +5376,6 @@ void Client::ChangeTSLevel(int16 old_level, int16 new_level) {
QueuePacket(master_trait_list.GetTraitListPacket(this));
}
void Client::SendPendingLoot(int32 total_coins, Spawn* entity) {
if (entity)
Loot(total_coins, player->GetPendingLootItems(entity->GetID()), entity);
}
void Client::CloseLoot(int32 spawn_id) {
if (GetVersion() > 546) {
PacketStruct* packet = configReader.getStruct("WS_CloseWindow", GetVersion());
@ -5218,7 +5389,8 @@ void Client::CloseLoot(int32 spawn_id) {
safe_delete(packet);
}
}
else if(spawn_id > 0){
if(spawn_id > 0){
PacketStruct* packet = configReader.getStruct("WS_StoppedLooting", GetVersion());
if (packet) {
packet->setDataByName("spawn_id", spawn_id);
@ -5227,6 +5399,11 @@ void Client::CloseLoot(int32 spawn_id) {
QueuePacket(outapp);
safe_delete(packet);
}
Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id);
if(spawn) {
spawn->CloseLoot(GetPlayer());
}
}
}
@ -5266,7 +5443,7 @@ string Client::GetCoinMessage(int32 total_coins) {
return message;
}
void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
void Client::SendLootResponsePacket(int32 total_coins, vector<Item*>* items, Spawn* entity, bool ignore_loot_tier) {
if (!entity) {
CloseLoot(0);
return;
@ -5277,7 +5454,7 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
string message = "";
if (entity->GetHP() == 0) {
message = "You loot ";
entity->SetLootCoins(0);
entity->SetLootCoins(0, false);
}
else
message = "You receive ";
@ -5290,30 +5467,48 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
}
if (!items || items->size() == 0)
CloseLoot(entity->GetID());
entity->StartLootTimer(GetPlayer());
PacketStruct* packet = configReader.getStruct("WS_UpdateLoot", GetVersion());
if (packet) {
entity->AddSpawnLootWindowCompleted(GetPlayer()->GetID(), false);
vector<Item*>::iterator itr;
int32 packet_size = 0;
EQ2Packet* outapp = 0;
uchar* data = 0;
vector<Item*> send_items;
if (items && items->size() > 0) {
for (int i = 0; i < items->size(); i++) {
Item* item = (*items)[i];
if (entity->GetLootMethod() > GroupLootMethod::METHOD_FFA && !ignore_loot_tier) {
bool skipItem = entity->IsItemInLootTier(item);
if (!skipItem) {
send_items.push_back(item);
}
}
else {
send_items.push_back(item);
}
}
}
if (GetVersion() >= 284) {
if (GetVersion() > 546) {
if (items && items->size() > 0) {
packet->setDataByName("loot_count", items->size());
if (GetVersion() > 561) {
if (send_items.size() > 0) {
packet->setDataByName("loot_count", send_items.size());
packet->setDataByName("display", 1);
}
packet->setDataByName("loot_type", 1);
if (version >= 1096)
packet->setDataByName("lotto_timeout", 0x78);
else
packet->setDataByName("lotto_timeout", 0x3C);
packet->setDataByName("loot_type", entity->GetLootMethod());
packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000);
packet->setDataByName("loot_id", entity->GetID());
EQ2Packet* tmpPacket = packet->serialize();
packet_size += tmpPacket->size;
if (items && items->size() > 0) {
data = new uchar[items->size() * 1000 + packet_size];
memset(data, 0, items->size() * 1000 + packet_size);
if (send_items.size() > 0) {
data = new uchar[send_items.size() * 1000 + packet_size];
memset(data, 0, send_items.size() * 1000 + packet_size);
}
else {
data = new uchar[packet_size];
@ -5324,8 +5519,8 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
ptr += tmpPacket->size;
safe_delete(tmpPacket);
Item* item = 0;
if (items && items->size() > 0) {
for (itr = items->begin(); itr != items->end(); itr++) {
if (send_items.size() > 0) {
for (itr = send_items.begin(); itr != send_items.end(); itr++) {
item = *itr;
memcpy(ptr, &item->details.item_id, sizeof(int32));
ptr += sizeof(int32);
@ -5340,7 +5535,7 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
else if (GetVersion() >= 860) {
offset = 11;
}
else if (GetVersion() <= 546) {
else if (GetVersion() <= 561) {
offset = 19;
}
else {
@ -5360,12 +5555,12 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
outapp = new EQ2Packet(OP_ClientCmdMsg, data, packet_size);
}
else {
if (items && items->size() > 0) {
packet->setArrayLengthByName("loot_count", items->size());
if (send_items.size() > 0) {
packet->setArrayLengthByName("loot_count", send_items.size());
Item* item = 0;
if (items && items->size() > 0) {
if (send_items.size() > 0) {
int i = 0;
for (itr = items->begin(); itr != items->end(); itr++) {
for (itr = send_items.begin(); itr != send_items.end(); itr++) {
item = *itr;
packet->setArrayDataByName("loot_id", item->details.item_id, i);
packet->setItemArrayDataByName("item", item, GetPlayer(), i, 0, 2, true);
@ -5374,17 +5569,17 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
}
packet->setDataByName("display", 1);
}
packet->setDataByName("loot_type", 1); // normal
packet->setDataByName("lotto_timeout", 0x3c); // 60 seconds
packet->setDataByName("loot_type", entity->GetLootMethod()); // normal
packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); // 60 seconds
packet->setDataByName("spawn_id", entity->GetID());
outapp = packet->serialize();
}
}
else {
if (items && items->size() > 0) {
packet->setArrayLengthByName("loot_count", items->size());
for (int i = 0; i < items->size(); i++) {
Item* item = (*items)[i];
if (send_items.size() > 0) {
packet->setArrayLengthByName("loot_count", send_items.size());
for (int i = 0; i < send_items.size(); i++) {
Item* item = (send_items)[i];
packet->setArrayDataByName("name", item->name.c_str(), i);
packet->setArrayDataByName("item_id", item->details.item_id, i);
packet->setArrayDataByName("count", item->details.count, i);
@ -5412,18 +5607,162 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Spawn* entity) {
}
void Client::Loot(Spawn* entity, bool attemptDisarm) {
if (entity->IsNPC() && ((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer())) {
int32 total_coins = entity->GetLootCoins();
bool Client::LootSpawnByMethod(Spawn* entity) {
bool sentLoot = false;
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo();
if (gmi && gmi->group_id)
{
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
int8 auto_split_coin = group->GetGroupOptions()->auto_split;
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
int32 split_coin_per_player = 0;
int32 coins_remain_after_split = entity->GetLootCoins();
int32 total_coins = entity->GetLootCoins();
if (auto_split_coin) {
int8 members_in_zone = 0;
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if (!member || !member->IsPlayer())
continue;
if (member->GetZone() != GetPlayer()->GetZone())
continue;
members_in_zone++;
}
if (members_in_zone < 1) // this should not happen, but divide by zero checked
members_in_zone = 0;
split_coin_per_player = entity->GetLootCoins() / members_in_zone;
coins_remain_after_split = entity->GetLootCoins() - (split_coin_per_player * members_in_zone);
entity->SetLootCoins(0, false);
}
LogWrite(LOOT__INFO, 0, "Loot", "%s: Group LootSpawnByMethod %u, auto coin split %u, split coin per player %u, remaining coin after split %u", entity->GetName(), entity->GetLootMethod(), auto_split_coin, split_coin_per_player, coins_remain_after_split);
bool startWithLooter = true;
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if (!member || !member->IsPlayer())
continue;
if (member->GetZone() != GetPlayer()->GetZone())
continue;
// this will make sure we properly send the loot window to the initial requester if there is no item rarity matches
if (startWithLooter && member != GetPlayer())
continue;
else if (!startWithLooter && member == GetPlayer())
continue;
else if (startWithLooter) {
i = 0;
startWithLooter = false;
}
if (auto_split_coin && (split_coin_per_player + coins_remain_after_split) > 0) {
player->AddCoins(split_coin_per_player + coins_remain_after_split);
if (((Player*)member)->GetClient()) {
((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of %s from the corpse of %s is %s.", GetCoinMessage(total_coins).c_str(), entity->GetLootName(), GetCoinMessage(split_coin_per_player + coins_remain_after_split).c_str());
}
if (coins_remain_after_split > 0) // overflow of coin division went to the first player
coins_remain_after_split = 0;
}
switch (entity->GetLootMethod()) {
case GroupLootMethod::METHOD_LOTTO:
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
if (((Player*)member)->GetClient()) {
switch (member->GetInfoStruct()->get_group_auto_loot_method()) {
case 1: { // lotto, need
if (entity->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) {
entity->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID());
}
else { // *need* before greed
entity->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true);
}
entity->AddSpawnLootWindowCompleted(member->GetID(), true);
// if it already exists we have to override the setting
entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID());
break;
}
case 2: { // decline
entity->AddSpawnLootWindowCompleted(member->GetID(), true);
// if it already exists we have to override the setting
entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID());
break;
}
default: { // presume 0 or higher than 2
((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity, true);
break;
}
}
sentLoot = true;
}
break;
}
case GroupLootMethod::METHOD_ROUND_ROBIN: {
entity->AddSpawnLootWindowCompleted(member->GetID(), true);
sentLoot = true;
break;
}
case GroupLootMethod::METHOD_LEADER: {
if (member->GetGroupMemberInfo()->leader)
((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity);
break;
}
case GroupLootMethod::METHOD_FFA: {
if(member == GetPlayer()) {
((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity);
}
break;
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
return sentLoot;
}
void Client::LootSpawnRequest(Spawn* entity, bool attemptDisarm) {
bool lootAllowed = false;
bool sentLoot = false;
std::vector<int32> item_list;
if (entity->IsNPC()) {
entity->LockLoot();
Loot(total_coins, entity->GetLootItems(), entity);
lootAllowed = ((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer());
entity->UnlockLoot();
OpenChest(entity, attemptDisarm);
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not allowed to loot at this time.");
if (lootAllowed) {
OpenChest(entity, attemptDisarm);
}
else {
SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not allowed to loot at this time.");
return;
}
entity->LockLoot();
if (((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer())) {
lootAllowed = true;
if ((sentLoot = LootSpawnByMethod(entity))) {
entity->GetLootItemsList(&item_list);
}
else {
SendLootResponsePacket(entity->GetLootCoins(), entity->GetLootItems(), entity);
}
}
entity->UnlockLoot();
if (lootAllowed) {
entity->DistributeGroupLoot_RoundRobin(&item_list, true);
}
}
}
void Client::OpenChest(Spawn* entity, bool attemptDisarm)
@ -6572,7 +6911,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
}
if (rewarded_coin > coin)
coin = rewarded_coin;
if (!quest && !was_displayed) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (!quest && !was_displayed) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (coin > 0) {
player->AddCoins(coin);
PlaySound("coin_cha_ching");
@ -6582,7 +6921,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
packet2->setSubstructDataByName("reward_data", "reward", header);
packet2->setSubstructDataByName("reward_data", "max_coin", coin);
if (player->GetGuild() && !was_displayed) {
if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (!quest) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
player->GetInfoStruct()->add_status_points(status_points);
player->SetCharSheetChanged(true);
}
@ -6606,7 +6945,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
packet2->setArrayDataByName("reward_id", item->details.item_id, i);
packet2->setItemArrayDataByName("item", item, player, i, 0, -1);
}
if(!quest) //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it
}
}
@ -6617,7 +6956,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
packet2->setArrayDataByName("reward_id", item->details.item_id, i);
packet2->setItemArrayDataByName("item", item, player, i, 0, -1);
}
if(!quest) //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it
i++;
@ -6630,7 +6969,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
if (item) {
packet2->setArrayDataByName("select_reward_id", item->details.item_id, i);
packet2->setItemArrayDataByName("select_item", item, player, i, 0, -1);
if (!quest) //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
player->AddPendingSelectableItemReward(source_id, item); //item reference will be deleted after the player selects one
}
}
@ -6712,7 +7051,7 @@ void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string cus
if (!quest)
return;
if (GetVersion() <= 546) {
if (GetVersion() <= 561) {
DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), tempReward ? customDescription.c_str() : quest->GetCompletedDescription(), was_displayed);
return;
}
@ -6907,6 +7246,7 @@ void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector<C
packet->setDataByName("conversation_id", conversation_id);
packet->setDataByName("text", text);
packet->setDataByName("language", language); // default 0
packet->setDataByName("enable_blue_ui", 0); // default 0
packet->setDataByName("can_close", can_close); // default 1
conversation_map[conversation_id].clear();
if (conversations) {
@ -8046,7 +8386,11 @@ void Client::SendSellMerchantList(bool sell) {
vector<Item*> sellable_items;
map<int32, Item*>::iterator test_itr;
for (test_itr = items->begin(); test_itr != items->end(); test_itr++) {
if (test_itr->second && !test_itr->second->CheckFlag(NO_VALUE))
bool isbagwithitems = false;
if (test_itr->second && test_itr->second->IsBag() && (test_itr->second->details.num_slots - test_itr->second->details.num_free_slots != test_itr->second->details.num_slots))
isbagwithitems = true;
if (test_itr->second && !test_itr->second->CheckFlag(NO_VALUE) && (isbagwithitems == false) && (test_itr->second->details.inv_slot_id != -3) && (test_itr->second->details.inv_slot_id != -4))
sellable_items.push_back(test_itr->second);
}
packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn));
@ -9417,8 +9761,10 @@ void Client::SearchStore(int32 page) {
}
void Client::SetReadyForUpdates() {
if (!ready_for_updates)
if (!ready_for_updates) {
database.loadCharacterProperties(this);
SendDefaultGroupOptions();
}
ready_for_updates = true;

View file

@ -181,9 +181,9 @@ public:
void SendPlayerDeathWindow();
float DistanceFrom(Client* client);
void SendDefaultGroupOptions();
bool HandleLootItem(Spawn* entity, int32 item_id);
bool HandleLootItem(Spawn* entity, Item* item);
void HandleLoot(EQApplicationPacket* app);
bool HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target);
bool HandleLootItem(Spawn* entity, Item* item, Spawn* target=nullptr, bool overrideLootRestrictions = false);
void HandleLootItemRequestPacket(EQApplicationPacket* app);
void HandleSkillInfoRequest(EQApplicationPacket* app);
void HandleExamineInfoRequest(EQApplicationPacket* app);
void HandleQuickbarUpdateRequest(EQApplicationPacket* app);
@ -263,9 +263,9 @@ public:
void Save();
bool remove_from_list;
void CloseLoot(int32 spawn_id);
void SendPendingLoot(int32 total_coins, Spawn* entity);
void Loot(int32 total_coins, vector<Item*>* items, Spawn* entity);
void Loot(Spawn* entity, bool attemptDisarm=true);
void SendLootResponsePacket(int32 total_coins, vector<Item*>* items, Spawn* entity, bool ignore_loot_tier = false);
void LootSpawnRequest(Spawn* entity, bool attemptDisarm=true);
bool LootSpawnByMethod(Spawn* entity);
void OpenChest(Spawn* entity, bool attemptDisarm=true);
void CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier=1, float restrictiveRadius=0.0f);
void CheckPlayerQuestsKillUpdate(Spawn* spawn);

View file

@ -939,6 +939,8 @@ bool ZoneServer::AggroVictim(NPC* npc, Spawn* victim, Client* client)
else
npc->InCombat(true);
}
victim->CheckEncounterState((Entity*)npc, true);
return true;
}
@ -1270,10 +1272,201 @@ bool ZoneServer::CombatProcess(Spawn* spawn) {
if (spawn && spawn->IsEntity())
((Entity*)spawn)->ProcessCombat();
if (spawn && !spawn->Alive() && !spawn->IsLootDispensed()) {
LootProcess(spawn);
}
return ret;
}
void ZoneServer::LootProcess(Spawn* spawn) {
if (spawn->GetLootMethod() == GroupLootMethod::METHOD_ROUND_ROBIN) {
spawn->LockLoot();
if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO");
spawn->DisableLootTimer();
spawn->SetLootDispensed();
Spawn* looter = nullptr;
if (spawn->GetLootGroupID() < 1 && spawn->GetLootWindowList()->size() > 0) {
std::map<int32, bool>::iterator itr;
for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) {
Spawn* entry = GetSpawnByID(itr->first, true);
if (entry->IsPlayer()) {
looter = entry;
break;
}
}
int32 item_id = 0;
std::vector<int32> item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
std::vector<int32>::iterator item_itr;
for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) {
int32 item_id = *item_itr;
Item* tmpItem = master_item_list.GetItem(item_id);
bool skipItem = spawn->IsItemInLootTier(tmpItem);
if (skipItem)
continue;
if (looter) {
if (looter->IsPlayer()) {
Item* item = spawn->LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter));
if (!success)
spawn->AddLootItem(item);
}
else {
Item* item = spawn->LootItem(item_id);
safe_delete(item);
}
}
}
}
else if (spawn->GetLootGroupID() > 0) {
int32 item_id = 0;
std::vector<int32> item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
spawn->DistributeGroupLoot_RoundRobin(&item_list);
}
if (!spawn->HasLoot()) {
if (spawn->IsNPC())
RemoveDeadSpawn(spawn);
}
else {
spawn->LockLoot();
spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0);
spawn->SetLooterSpawnID(0);
spawn->UnlockLoot();
}
}
else {
spawn->UnlockLoot();
}
}
else if ((spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && spawn->IsLootTimerRunning()) {
spawn->LockLoot();
if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO");
spawn->DisableLootTimer();
spawn->SetLootDispensed();
// identify any clients that still have the loot window open, close it out
CloseSpawnLootWindow(spawn);
// lotto items while we have loot items in the list
int32 item_id = 0;
std::vector<int32> item_list;
spawn->GetLootItemsList(&item_list);
spawn->UnlockLoot();
std::vector<int32>::iterator item_itr;
for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) {
int32 item_id = *item_itr;
Item* tmpItem = master_item_list.GetItem(item_id);
bool skipItem = spawn->IsItemInLootTier(tmpItem);
if (skipItem)
continue;
std::map<int32, int32> out_entries;
std::map<int32, int32>::iterator out_itr;
bool itemNeed = true;
switch (spawn->GetLootMethod()) {
case GroupLootMethod::METHOD_LOTTO: {
spawn->GetSpawnLottoEntries(item_id, &out_entries);
break;
}
case GroupLootMethod::METHOD_NEED_BEFORE_GREED: {
spawn->GetSpawnNeedGreedEntries(item_id, true, &out_entries);
if (out_entries.size() < 1) {
spawn->GetSpawnNeedGreedEntries(item_id, false, &out_entries);
itemNeed = false;
}
break;
}
}
if (out_entries.size() < 1) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: No spawns matched for loot attempt of %s (%u), skip item.", spawn->GetName(), tmpItem ? tmpItem->name.c_str() : "Unknown", item_id);
continue;
}
Spawn* looter = nullptr;
int32 curWinValue = 0;
for (out_itr = out_entries.begin(); out_itr != out_entries.end(); out_itr++) {
Spawn* entry = GetSpawnByID(out_itr->first, true);
if ((out_itr->second > curWinValue) || looter == nullptr) {
curWinValue = out_itr->second;
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");
}
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");
}
}
if (looter) {
if (looter->IsPlayer()) {
Item* item = spawn->LootItem(item_id);
bool success = false;
success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter));
if (!success)
spawn->AddLootItem(item);
}
else {
Item* item = spawn->LootItem(item_id);
safe_delete(item);
}
}
}
if (!spawn->HasLoot()) {
if (spawn->IsNPC())
RemoveDeadSpawn(spawn);
}
else {
spawn->LockLoot();
spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0);
spawn->SetLooterSpawnID(0);
spawn->UnlockLoot();
}
}
else {
spawn->UnlockLoot();
}
}
}
void ZoneServer::CloseSpawnLootWindow(Spawn* spawn) {
if (spawn->GetLootWindowList()->size() > 0) {
std::map<int32, bool>::iterator itr;
for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) {
if (itr->second)
continue;
itr->second = true;
Spawn* looter = GetSpawnByID(itr->first, true);
if (looter && looter->IsPlayer() && ((Player*)looter)->GetClient()) {
LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Close loot for player %s.", spawn->GetName(), looter->GetName());
((Player*)looter)->GetClient()->CloseLoot(spawn->GetID());
}
}
}
}
void ZoneServer::AddPendingDelete(Spawn* spawn) {
MSpawnDeleteList.writelock(__FUNCTION__, __LINE__);
spawn->SetDeletedSpawn(true);
@ -2510,12 +2703,13 @@ void ZoneServer::ProcessSpawnLocations()
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__);
}
void ZoneServer::AddLoot(NPC* npc, Spawn* killer){
void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, int8 item_rarity, int32 group_id){
// this function is ran twice, first on spawn of mob, then at death of mob (gray mob check and no_drop_quest_completed_id check)
// first we see if the skipping of gray mobs loot is enabled, then we move all non body drops
if(killer)
{
npc->SetLootMethod(loot_method, item_rarity, group_id);
int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8();
if(skip_loot_gray_mob_flag) {
Player* player = 0;
@ -3297,6 +3491,10 @@ void ZoneServer::RemoveClient(Client* client)
if (!client->IsZoning())
{
client->SaveSpells();
client->GetPlayer()->DeleteSpellEffects(true);
if ((guild = client->GetPlayer()->GetGuild()) != NULL)
guild->GuildMemberLogoff(client->GetPlayer());
@ -3346,10 +3544,6 @@ void ZoneServer::RemoveClient(Client* client)
((Bot*)spawn)->Camp();
}
client->SaveSpells();
client->GetPlayer()->DeleteSpellEffects(true);
if(dismissPets) {
((Entity*)client->GetPlayer())->DismissAllPets();
}
@ -4699,7 +4893,19 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo
else {
Entity* hated = ((NPC*)dead)->Brain()->GetMostHated();
if(hated) {
AddLoot((NPC*)dead, hated);
GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA;
int8 item_rarity = 0;
if(hated->GetGroupMemberInfo()) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
PlayerGroup* group = world.GetGroupManager()->GetGroup(hated->GetGroupMemberInfo()->group_id);
if (group) {
loot_method = (GroupLootMethod)group->GetGroupOptions()->loot_method;
item_rarity = group->GetGroupOptions()->loot_items_rarity;
LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Loot method set to %u.", dead->GetName(), loot_method);
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
AddLoot((NPC*)dead, hated, loot_method, item_rarity, hated->GetGroupMemberInfo() ? hated->GetGroupMemberInfo()->group_id : 0);
}
}
}

View file

@ -306,7 +306,7 @@ public:
void ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value);
void SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client = 0);
void SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client = 0);
void AddLoot(NPC* npc, Spawn* killer = nullptr);
void AddLoot(NPC* npc, Spawn* killer = nullptr, GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA, int8 item_rarity = 0, int32 group_id = 0);
NPC* AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry);
Object* AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry);
@ -795,6 +795,8 @@ private:
/// <summary>Checks to see if it is time to remove a spawn and removes it</summary>
/// <param name='force_delete_all'>Forces all spawns scheduled to be removed regardless of time</param>
bool CombatProcess(Spawn* spawn); // never used outside zone server
void LootProcess(Spawn* spawn);
void CloseSpawnLootWindow(Spawn* spawn);
void InitWeather(); // never used outside zone server
///<summary>Dismiss all pets in the zone, useful when the spell process needs to be reloaded</summary>
void DismissAllPets(); // never used outside zone server

View file

@ -231,9 +231,8 @@ void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool
ds2->SetOversizedByte(ds->GetOversizedByte());
ds2->SetDefaultValue(ds->GetDefaultValue());
ds2->SetMaxArraySize(ds->GetMaxArraySize());
ds2->SetIfSetVariable(ds->GetIfSetVariable());
ds2->SetIfNotSetVariable(ds->GetIfNotSetVariable());
ds2->SetIfEqualsVariable(ds->GetIfEqualsVariable());
ds2->SetIfSetVariable(ds->GetIfSetVariable() ? ds->GetIfSetVariable() : if_variable);
ds2->SetIfNotSetVariable(ds->GetIfSetVariable() ? ds->GetIfNotSetVariable() : if_not_variable);
ds2->SetIfNotEqualsVariable(ds->GetIfNotEqualsVariable());
ds2->SetIfFlagNotSetVariable(ds->GetIfFlagNotSetVariable());
ds2->SetIfFlagSetVariable(ds->GetIfFlagSetVariable());

View file

@ -1130,6 +1130,7 @@ bool PacketStruct::LoadPacketData(uchar* data, int32 data_len, bool create_color
data_struct = *itr;
if (!data_struct->AddToStruct())
continue;
if (data_struct->GetIfSet() && data_struct->GetIfSetVariable()) {
string varname = string(data_struct->GetIfSetVariable());
if (varname.find(",") < 0xFFFFFFFF) {

View file

@ -88,7 +88,7 @@ to zero and treated like placeholders." />
<Data ElementName="body_size" Type="float" Size="1" />
<Data ElementName="body_age" Type="float" Size="1" />
</Struct>
<Struct Name="CreateCharacter" ClientVersion="547" OpcodeName="OP_CreateCharacterRequestMsg">
<Struct Name="CreateCharacter" ClientVersion="562" OpcodeName="OP_CreateCharacterRequestMsg">
<Data ElementName="unknown0" Type="int8" />
<Data ElementName="unknown1" Type="int32" />
<Data ElementName="account_id" Type="int32" />

View file

@ -12022,14 +12022,6 @@
<Data ElementName="duration" Type="float" Size="1" />
<Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
</Struct>
<Struct Name="WS_MerchantItemGeneric" ClientVersion="63119" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
<Data ElementName="header" Substruct="Substruct_MerchantItemDescription" Size="1" />
<Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
</Struct>
<Struct Name="WS_MerchantItemGeneric" ClientVersion="63119" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
<Data ElementName="header" Substruct="Substruct_MerchantItemDescription" Size="1" />
<Data ElementName="footer" Substruct="Substruct_ItemFooter" Size="1" />
</Struct>
<Struct Name="WS_MerchantItemDecoration" ClientVersion="63119" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqExamineInfoCmd">
<Data ElementName="header" Substruct="Substruct_MerchantItemDescription" Size="1" />
<Data ElementName="decoration_name" Type="EQ2_16Bit_String" Size="1" />

View file

@ -42,7 +42,7 @@ to zero and treated like placeholders." />
<Data ElementName="passCode" Type="int32" Size="1" />
<Data ElementName="version" Type="int16" />
</Struct>
<Struct Name="LS_LoginRequest" ClientVersion="547" OpcodeName="OP_LoginRequestMsg">
<Struct Name="LS_LoginRequest" ClientVersion="562" OpcodeName="OP_LoginRequestMsg">
<Data ElementName="accesscode" Type="EQ2_16BitString" />
<Data ElementName="unknown1" Type="EQ2_16BitString" />
<Data ElementName="username" Type="EQ2_16BitString" />
@ -95,7 +95,7 @@ to zero and treated like placeholders." />
<Data ElementName="allowed_races" Type="int32" Size="1" />
</Data>
</Struct>
<Struct Name="LS_WorldList" ClientVersion="547" OpcodeName="OP_WorldListMsg">
<Struct Name="LS_WorldList" ClientVersion="562" OpcodeName="OP_WorldListMsg">
<Data ElementName="num_worlds" Type="int8" />
<Data ElementName="world_list" Type="Array" ArraySizeVariable="num_worlds">
<Data ElementName="id" Type="int32" Size="1" />
@ -322,7 +322,7 @@ to zero and treated like placeholders." />
<Data ElementName="soga_hair_face_color" Type="EQ2_Color" />
<Data ElementName="soga_hair_face_highlight_color" Type="EQ2_Color" />
</Struct>
<Struct Name="CharSelectProfile" ClientVersion="547">
<Struct Name="CharSelectProfile" ClientVersion="562">
<Data ElementName="version" Type="int32" Size="1" />
<Data ElementName="charid" Type="int32" Size="1" />
<Data ElementName="server_id" Type="int32" Size="1" />

View file

@ -7245,34 +7245,34 @@ to zero and treated like placeholders." />
<Data ElementName="lotto_timeout" Type="int32" />
<Data ElementName="loot_id" Type="int32" />
</Struct>
<Struct Name="WS_LootType" ClientVersion="1" OpcodeName="OP_LootItemsRequestMsg" >
<Data ElementName="loot_id" Type="int32" />
<Data ElementName="loot_all" Type="int8" />
<Data ElementName="unknown2" Type="int32" />
</Struct>
<Struct Name="WS_LootType" ClientVersion="882" OpcodeName="OP_LootItemsRequestMsg" >
<Data ElementName="loot_id" Type="int32" />
<Data ElementName="unknown" Type="int8" />
<Data ElementName="loot_all" Type="int8" />
<Data ElementName="unknown2" Type="int32" />
</Struct>
<Struct Name="WS_LootItem" ClientVersion="1" OpcodeName="OP_LootItemsRequestMsg" >
<Data ElementName="loot_id" Type="int32" />
<Data ElementName="loot_all" Type="int8" />
<Data ElementName="unknown2" Type="int8" />
<Data ElementName="item_id" Type="int32" />
<Data ElementName="unknown3" Type="int8" />
<Data ElementName="unknown4" Type="int32" />
<Data ElementName="item_count" Type="int8" IfVariableNotSet="loot_all"/>
<Data ElementName="item_list" Type="Array" ArraySizeVariable="item_count" IfVariableNotSet="loot_all">
<Data ElementName="item_id" Type="int32" IfVariableNotSet="loot_all"/>
</Data>
<Data ElementName="target_id" Type="int32" />
</Struct>
<Struct Name="WS_LootItem" ClientVersion="546" OpcodeName="OP_LootItemsRequestMsg" >
<Data ElementName="loot_id" Type="int32" />
<Data ElementName="loot_all" Type="int8" />
<Data ElementName="item_count" Type="int8" IfVariableNotSet="loot_all"/>
<Data ElementName="item_list" Type="Array" ArraySizeVariable="item_count" IfVariableNotSet="loot_all">
<Data ElementName="item_id" Type="int32" IfVariableNotSet="loot_all"/>
</Data>
<Data ElementName="target_id" Type="int32" />
</Struct>
<Struct Name="WS_LootItem" ClientVersion="882" OpcodeName="OP_LootItemsRequestMsg" >
<Data ElementName="loot_id" Type="int32" />
<Data ElementName="unknown" Type="int8" />
<Data ElementName="loot_all" Type="int8" />
<Data ElementName="unknown2" Type="int8" />
<Data ElementName="item_id" Type="int32" />
<Data ElementName="unknown3" Type="int8" />
<Data ElementName="unknown4" Type="int32" />
<Data ElementName="item_count" Type="int8" IfVariableNotSet="loot_all"/>
<Data ElementName="item_list" Type="Array" ArraySizeVariable="item_count" IfVariableNotSet="loot_all">
<Data ElementName="item_id" Type="int32" IfVariableNotSet="loot_all"/>
</Data>
<Data ElementName="button_clicked" Type="int8" />
<Data ElementName="target_id" Type="int32" />
</Struct>
<Struct Name="WS_UpdateBank" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateBankCmd" >
<Data ElementName="spawn_id" Type="int32" />
@ -7815,8 +7815,8 @@ to zero and treated like placeholders." />
<Data ElementName="onscreen_update_text" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="onscreen_update_text2" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="onscreen_update_icon" Type="int16" Size="1" />
<Data ElementName="unknown6" Type="int8" Size="1" />
<Data ElementName="reward_data" Substruct="Substruct_JournalRewardData" IfVariableNotSet="complete" />
<Data ElementName="unknown6" Type="int8" Size="1" />
</Struct>
<Struct Name="WS_QuestJournalReply" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqQuestJournalReplyCmd" >
<Data ElementName="quest_id" Type="int32" Size="1" />
@ -9084,7 +9084,7 @@ to zero and treated like placeholders." />
<Data ElementName="response_array" Type="Array" ArraySizeVariable="num_responses">
<Data ElementName="response" Type="EQ2_16Bit_String" Size="1" />
</Data>
<Data ElementName="unknown3" Type="int8" />
<Data ElementName="enable_blue_ui" Type="int8" />
<Data ElementName="can_close" Type="int8" />
<Data ElementName="spawn_id" Type="int32" />
<Data ElementName="voice" Type="EQ2_16Bit_String" Size="1" />
@ -17960,23 +17960,23 @@ to zero and treated like placeholders." />
<Data ElementName="default_yell_method" Type="int8" Size="1" />
</Struct>
<Struct Name="WS_DefaultGroupOptions" ClientVersion="546" OpcodeName="OP_DefaultGroupOptionsMsg" >
<Data ElementName="loot_method" Type="int8" Size="1" />
<Data ElementName="loot_items_rarity" Type="int8" Size="1" />
<Data ElementName="auto_split_coin" Type="int8" Size="1" />
<Data ElementName="default_yell_method" Type="int8" Size="1" />
<Data ElementName="group_autolock" Type="int8" Size="1" />
<Data ElementName="solo_autolock" Type="int8" Size="1" />
<Data ElementName="loot_method" Type="int8" Size="1" /> <!-- 0 = leader, 1 = FFA, 2 = lotto -->
<Data ElementName="loot_items_rarity" Type="int8" Size="1" /> <!-- not available in DoF? -->
<Data ElementName="auto_split_coin" Type="int8" Size="1" /> <!-- auto split -->
<Data ElementName="default_yell_method" Type="int8" Size="1" /> <!-- 0 = leader only, 1 = group allowed -->
<Data ElementName="default_group_lock_method" Type="int8" Size="1" /> <!-- 0 = leader, 1 = anyone -->
<Data ElementName="group_autolock" Type="int8" Size="1" /> <!-- 0 = false, 1 = true, if set to 0 default_group_lock_method is not sent if changed after -->
</Struct>
<Struct Name="WS_DefaultGroupOptions" ClientVersion="547" OpcodeName="OP_DefaultGroupOptionsMsg" >
<Struct Name="WS_DefaultGroupOptions" ClientVersion="562" OpcodeName="OP_DefaultGroupOptionsMsg" >
<Data ElementName="loot_method" Type="int8" Size="1" />
<Data ElementName="loot_items_rarity" Type="int8" Size="1" />
<Data ElementName="auto_split_coin" Type="int8" Size="1" />
<Data ElementName="unknown3" Type="int8" Size="1" />
<Data ElementName="default_yell_method" Type="int8" Size="1" />
<Data ElementName="unknown5" Type="int8" Size="1" />
<Data ElementName="default_group_lock_method" Type="int8" Size="1" />
<Data ElementName="group_autolock" Type="int8" Size="1" />
<Data ElementName="solo_autolock" Type="int8" Size="1" />
<Data ElementName="unknown8" Type="int8" Size="1" />
<Data ElementName="auto_loot_method" Type="int8" Size="1" />
</Struct>
<Struct Name="WS_ChoiceWindow" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqChoiceWinCmd">
<Data ElementName="text" Type="EQ2_16Bit_String" />