Browse Source

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

Emagi 2 months ago
parent
commit
47196d6b67

+ 100 - 32
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -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,49 +12053,101 @@ 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 alreadyYelled = false;
-	do {
-	if(!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list
 
+	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(!res || prev == res) {
+
+	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;
 	}
 
-	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());
+	bool alreadyYelled = false;
+	do {
+		if (!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list
+
 		}
-		alreadyYelled = true;
-		
-		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()) {
+		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;
+
+			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);
 			player->info_changed = true;
 		}
 	}
+}
+
+
+/* 
+	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();
+	}
 }

+ 2 - 0
EQ2/source/WorldServer/Commands/Commands.h

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

+ 18 - 0
EQ2/source/WorldServer/Entity.cpp

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

+ 36 - 0
EQ2/source/WorldServer/Entity.h

@@ -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_; }
@@ -975,6 +993,15 @@ struct InfoStruct{
 	void	set_first_world_login(int8 value) { std::lock_guard<std::mutex> lk(classMutex); first_world_login_ = value; }
 	
 	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; }
 	
@@ -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_;
 	

+ 1 - 6
EQ2/source/WorldServer/Items/Items.cpp

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

+ 2 - 1
EQ2/source/WorldServer/Items/Loot.cpp

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

+ 3 - 3
EQ2/source/WorldServer/LuaFunctions.cpp

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

+ 16 - 6
EQ2/source/WorldServer/NPC_AI.cpp

@@ -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__);
@@ -608,7 +618,7 @@ bool Brain::CheckLootAllowed(Entity* entity) {
 			break;
 		}
 	}
-	MEncounter.releasereadlock(__FUNCTION__, __LINE__);	
+	MEncounter.releasereadlock(__FUNCTION__, __LINE__);
 
 	return ret;
 }

+ 0 - 1
EQ2/source/WorldServer/Player.cpp

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

+ 72 - 2
EQ2/source/WorldServer/PlayerGroups.cpp

@@ -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,12 +329,23 @@ 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;
 
 	// Add the leader to the group
 	new_group->AddMember(leader);
-
+	
 	leader->GetGroupMemberInfo()->leader = true;
 }
 
@@ -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();
+}

+ 11 - 0
EQ2/source/WorldServer/PlayerGroups.h

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

+ 342 - 18
EQ2/source/WorldServer/Spawn.cpp

@@ -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,33 +3713,81 @@ 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);
 		}
-		
+
 		int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8();
-		
+
 		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,14 +3795,14 @@ void Spawn::CheckEncounterState(Entity* victim) {
 			}
 		}
 		else {
-			if(attacker->IsPlayer() || attacker->IsBot()) {
+			if (attacker->IsPlayer() || attacker->IsBot()) {
 				new_enc_state = ENCOUNTER_STATE_LOCKED;
 			}
 			else {
 				new_enc_state = ENCOUNTER_STATE_BROKEN;
 			}
 		}
-		
+
 		victim->SetLockedNoLoot(new_enc_state);
 		victim->UpdateEncounterState(new_enc_state);
 	}
@@ -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;
 
@@ -4702,4 +4766,264 @@ void Spawn::SendGroupUpdate() {
 		else
 			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);
+			}
+		}
+	}
 }

+ 95 - 5
EQ2/source/WorldServer/Spawn.h

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

+ 42 - 2
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -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))
@@ -2093,7 +2093,7 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
 		else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG))
 		{
 			int8 val = atoul(prop_value);
-			
+
 			client->SetRegionDebug(val == 1);
 			if (val)
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!");
@@ -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;

+ 9 - 0
EQ2/source/WorldServer/WorldDatabase.h

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

+ 521 - 175
EQ2/source/WorldServer/client.cpp

@@ -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;
 								}
 							}
 						}
@@ -2712,15 +2829,15 @@ 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);
-			
-			CheckPlayerQuestsItemUpdate(item);
-			
-			if(GetVersion() <= 546) {
-				EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+				lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, lootingPlayer);
+
+			lootingClient->CheckPlayerQuestsItemUpdate(item);
+
+			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(GetVersion() <= 546) {
-											EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-											if (outapp)
-												QueuePacket(outapp);
-										}
-									}
-								}
-							}
-						}
-						safe_delete(items);
-					}
-				}
-				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;
-									}
+			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);
 								}
-								safe_delete(items);
 							}
 						}
-						safe_delete(packet);
+
+						if (!loot_all)
+							break;
 					}
 				}
-				if(GetVersion() > 546) {
-					EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-					if (outapp)
-						QueuePacket(outapp);
-				}
-				Loot(0, player->GetPendingLootItems(loot_id), spawn);
+				safe_delete(items);
+				safe_delete(packet);
+				return;
 			}
-			else {
-				if (spawn && !spawn->Alive() && spawn->IsNPC() && ((NPC*)spawn)->Brain()->CheckLootAllowed(player)) {
-					if (loot_all) {
+
+			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 = HandleLootItem(spawn, item_id);
+							loot_all = HandleLootItemByID(spawn, item_id, GetPlayer());
 						}
+						break;
 					}
-					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);
+					}
+					spawn->UnlockLoot();
+					if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) {
+						CloseLoot(loot_id);
+					}
+				}
+				else {
+					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;
 							}
-							safe_delete(packet);
+							target = destTarget;
+						}
+						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(GetVersion() > 546) {
-						EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-						if (outapp)
-							QueuePacket(outapp);
+					if (!unlockedLoot) {
+						spawn->UnlockLoot();
 					}
-					Loot(spawn);
-					if (!spawn->HasLoot()) {
+					if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO ||
+						(spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED && item_count >= spawn->GetLootCount())) {
 						CloseLoot(loot_id);
-						if (spawn->IsNPC())
-							GetCurrentZone()->RemoveDeadSpawn(spawn);
 					}
 				}
+
+				if (GetVersion() > 546) {
+					EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
+					if (outapp)
+						QueuePacket(outapp);
+				}
+				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) {
@@ -4499,7 +4673,9 @@ 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;
 	

+ 6 - 6
EQ2/source/WorldServer/client.h

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

+ 213 - 7
EQ2/source/WorldServer/zoneserver.cpp

@@ -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;
@@ -3296,7 +3490,11 @@ void ZoneServer::RemoveClient(Client* client)
 		loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID());
 
 		if (!client->IsZoning()) 
-		{
+		{			
+			client->SaveSpells();
+			
+			client->GetPlayer()->DeleteSpellEffects(true);
+			
 			if ((guild = client->GetPlayer()->GetGuild()) != NULL)
 				guild->GuildMemberLogoff(client->GetPlayer());
 
@@ -3345,10 +3543,6 @@ void ZoneServer::RemoveClient(Client* client)
 			if (spawn)
 				((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);
 				}
 			}
 		}

+ 3 - 1
EQ2/source/WorldServer/zoneserver.h

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

+ 3 - 4
EQ2/source/common/ConfigReader.cpp

@@ -222,7 +222,7 @@ void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool
 								new_name = string(name).append("_").append(ds->GetStringName());
 							else
 								new_name = string(name).append("_").append(ds->GetStringName()).append("_").append(tmp);
-							
+
 							DataStruct* ds2 = new DataStruct(new_name.c_str(), ds->GetType(),ds->GetLength(), ds->GetType2());
 
 							if(!array_packet && strlen(ds->GetArraySizeVariable()) > 1)
@@ -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());

+ 1 - 0
EQ2/source/common/PacketStruct.cpp

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

+ 1 - 1
server/CommonStructs.xml

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

+ 0 - 8
server/ItemStructs.xml

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

+ 3 - 3
server/LoginStructs.xml

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

+ 30 - 30
server/WorldStructs.xml

@@ -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" >
+<Struct Name="WS_LootItem" ClientVersion="1" 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" />
+<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="1" OpcodeName="OP_LootItemsRequestMsg" >
+<Struct Name="WS_LootItem" ClientVersion="546" 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="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" />
-</Struct>
-<Struct Name="WS_DefaultGroupOptions" ClientVersion="547" OpcodeName="OP_DefaultGroupOptionsMsg" >
+<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="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" />