Browse Source

- Fixed Cure not working if there was no levels table on the detrimental spell
- Fix #329 /cureplayer implementation (minus RAID support)
update commands set handler=531 where command='cureplayer';

- cures will now remove the maintained spell of the caster if all targets are gone
- target support for item scripts 'used' function, eg.

function used(Item, Player, Target)
-- do stuff
end

- effect_type added to items table, currently used to map to cure types
alter table items add column effect_type int(10) unsigned not null default 0;

NO_EFFECT_TYPE=0,
EFFECT_CURE_TYPE_ALL=1,
EFFECT_CURE_TYPE_ARCANE=2,
EFFECT_CURE_TYPE_TRAUMA=3,
EFFECT_CURE_TYPE_NOXIOUS=4,
EFFECT_CURE_TYPE_CURSE=5,
EFFECT_CURE_TYPE_MAGIC=6
- created sample cure using item id 48868 Translucent Remedy of the Gods
update items set lua_script='ItemScripts/cure_test.lua' where id=48868;
update items set usable=1,effect_type=1 where id=48868;
I know, not the items real purpose, but this was just a fun test since the other effect cure types would be specific to the type
- removed dump packet of lower layer packet opcode 3 (reduce noise in console)

Emagi 1 year ago
parent
commit
6fbc15029d

+ 2 - 0
DB/updates/cure_test_spell48868_july_1st_2022.sql

@@ -0,0 +1,2 @@
+update items set lua_script='ItemScripts/cure_test.lua' where id=48868;
+update items set usable=1,effect_type=7 where id=48868;

+ 1 - 0
DB/updates/cureplayer_command_update_july_1st_2022.sql

@@ -0,0 +1 @@
+update commands set handler=531 where command='cureplayer';

+ 1 - 0
DB/updates/items_table_effect_type_july_1st_2022.sql

@@ -0,0 +1 @@
+alter table items add column effect_type int(10) unsigned not null default 0;

+ 131 - 55
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -18,6 +18,9 @@ You should have received a copy of the GNU General Public License
 along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 */
 #include <sys/types.h>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string.hpp>
+
 #include "Commands.h"
 #include "../ClientPacketFunctions.h"
 #include "../../common/version.h"
@@ -2154,7 +2157,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 							client->Message(0, "%s requires a good race.", item->name.c_str());
 					}
 					else {
-						lua_interface->RunItemScript(item->GetItemScript(), "used", item, player);
+						lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, player->GetTarget());
 					}
 				}
 			}
@@ -2164,59 +2167,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			if (sep && sep->arg[0] && sep->IsNumber(0)) {
 				int32 item_index = atoul(sep->arg[0]);
 				Item* item = player->item_list.GetItemFromIndex(item_index);
-				if (item && item->GetItemScript()) {
-					if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) {
-						client->SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used");
-					}
-					else if (item->CheckFlag(EVIL_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) {
-							client->Message(0, "%s requires an evil race.", item->name.c_str());
-					}
-					else if (item->CheckFlag(GOOD_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) {
-							client->Message(0, "%s requires a good race.", item->name.c_str());
-					}
-					else if (item->generic_info.max_charges == 0 || item->generic_info.max_charges == 0xFFFF)
-						lua_interface->RunItemScript(item->GetItemScript(), "used", item, player);
-					else {
-						if (item->details.count > 0) {
-							std::string itemName = string(item->name);
-							int32 item_id = item->details.item_id;
-							sint64 flags = 0;
-							if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, &flags) && flags >= 0)
-							{
-								//reobtain item make sure it wasn't removed
-								item = player->item_list.GetItemFromIndex(item_index);
-								if(!item)
-									LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", client->GetPlayer()->GetName(), itemName.c_str(), item_id);
-								else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1) {
-									client->Message(CHANNEL_NARRATIVE, "%s is out of charges.  It has been removed.", item->name.c_str());
-									client->RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
-								}
-								else
-								{
-									item->details.count--; // charges
-									item->save_needed = true;
-									client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer()));
-
-									if(!item->details.count) {
-										client->Message(CHANNEL_NARRATIVE, "%s is out of charges.  It has been removed.", item->name.c_str());
-										client->RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
-									}
-								}
-							}
-							else
-									LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, after it returned %i, bypassing any removal/update of items.", client->GetPlayer()->GetName(), itemName.c_str(), item_id, flags);
-						}
-						else
-						{
-							//reobtain item make sure it wasn't removed
-							item = player->item_list.GetItemFromIndex(item_index);
-							client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is out of charges.");
-							if(item) {
-								LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however details.count is 0.", client->GetPlayer()->GetName(), item->name.c_str(), item->details.item_id);
-							}
-						}
-					}
-				}
+				client->UseItem(item, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer());
 			}
 			break;
 							   }
@@ -5606,7 +5557,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			client->GetPlayer()->MentorTarget();
 			break;
 		}
-		case COMMAND_CANCEL_EFFECT	: { Command_CancelEffect(client, sep); break; }
+		case COMMAND_CANCEL_EFFECT: { Command_CancelEffect(client, sep); break; }
+		case COMMAND_CUREPLAYER: { Command_CurePlayer(client, sep); break; }
 		default: 
 		{
 			LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@@ -11735,3 +11687,127 @@ void Commands::Command_CancelEffect(Client* client, Seperator* sep)
 			client->Message(CHANNEL_COLOR_RED, "The spell effect could not be cancelled.");
 	}
 }
+
+
+/* 
+	Function: Command_CurePlayer()
+	Purpose	: Identifies spell to cast for cure based on type
+	Example	: /cureplayer ??
+	https://eq2.fandom.com/wiki/Update:58
+	New command /cureplayer [playername|group or raid position][trauma|arcane|noxious|elemental|curse] optional [spell|potion]
+
+    Example: /cureplayer g0 noxious spell
+        Will attempt to cure yourself of a noxious detriment with only spells and without using potions (even if you have them).
+    Example: /cureplayer r4 noxious
+        Will attempt to cure the character in raid slot 4 of a noxious detriment using a spell or potion (whichever is available).
+*/ 
+void Commands::Command_CurePlayer(Client* client, Seperator* sep)
+{
+	Entity* target = nullptr;
+	bool use_spells = true;
+	bool use_potions = true;
+	if (sep && sep->arg[0] && sep->arg[1]) {
+		if(sep->arg[2]) {
+			std::string type(sep->arg[2]);
+			boost::algorithm::to_lower(type);
+			if(type == "potion")
+				use_spells = false;
+			else if(type == "spell")
+				use_potions = false;
+		}
+		
+		if(strlen(sep->arg[0]) > 1 && isdigit(sep->arg[0][1])) {
+			int32 mapped_position = (int32)(sep->arg[0][1]) - 48;
+			
+			// TODO: RAID Support ('r' argument)
+			if(sep->arg[0][0] == 'g' && !mapped_position) {
+				target = (Entity*)client->GetPlayer();
+			}
+			else {
+				GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
+				if (gmi && gmi->group_id) {
+					PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
+					if(group) {
+						target = group->GetGroupMemberByPosition(client->GetPlayer(), mapped_position);
+					}
+				}
+			}
+		}
+		else {
+				Client* target_client = zone_list.GetClientByCharName(sep->arg[0]);
+				if(target_client && target_client->GetPlayer() && target_client->GetPlayer()->GetZone() == client->GetPlayer()->GetZone()) {
+					target = (Entity*)target_client->GetPlayer();
+				}
+		}
+		
+		ItemEffectType type = EFFECT_CURE_TYPE_ALL;
+		bool successful_spell = false;
+		bool successful_potion = false;
+		if(target) {
+			SpellBookEntry* entry = nullptr;
+			std::string str(sep->arg[1]);
+			boost::algorithm::to_lower(str);
+					if(str == "arcane") {
+						type = EFFECT_CURE_TYPE_ARCANE;
+						// cure arcane spell missing in DB?
+						if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()) {
+							entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()); // cure noxious
+						}
+					}
+					else if(str == "trauma") {
+						type = EFFECT_CURE_TYPE_TRAUMA;
+						// cure trauma spell missing in DB?
+						if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()) {
+							entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()); // cure noxious
+						}
+					}
+					else if(str == "noxious") {
+						type = EFFECT_CURE_TYPE_NOXIOUS;
+						if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()) {
+							entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()); // cure noxious
+						}
+					}
+					else if(str == "curse") {
+						type = EFFECT_CURE_TYPE_CURSE;
+						if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()) {
+							entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()); // cure curse
+						}
+					}
+					else if(str == "magic") {
+						type = EFFECT_CURE_TYPE_MAGIC;
+						if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()) {
+							entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()); // cure magic
+						}
+					}
+				
+				if(use_spells) {
+					// check if any of the specific cure types are available, if not then check the base cures
+					if(!entry && rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()) {
+						entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()); // cure
+					}
+					
+					if(entry) {
+						Spell* spell = master_spell_list.GetSpell(entry->spell_id, entry->tier);
+						target->GetZone()->ProcessSpell(spell, (Entity*)client->GetPlayer(), target, true, false);
+						successful_spell = true;
+					}
+				}
+		}
+		
+		if(!successful_spell && use_potions && target && type != NO_EFFECT_TYPE) {
+			vector<Item*>* potential_items = client->GetItemsByEffectType(type, EFFECT_CURE_TYPE_ALL);
+			if (potential_items && potential_items->size() > 0) {
+				vector<Item*>::iterator itr;
+				for (itr = potential_items->begin(); itr != potential_items->end(); itr++)
+				{
+					Item* item = *itr;
+					if(client->UseItem(item, target)) {
+						successful_potion = true;
+						break;
+					}
+				}
+			}
+			safe_delete(potential_items);
+		}
+	}
+}

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

@@ -447,6 +447,7 @@ public:
 	void Command_Bot_Help(Client* client, Seperator* sep);
 	
 	void Command_CancelEffect(Client* client, Seperator* sep);
+	void Command_CurePlayer(Client* client, Seperator* sep);
 
 	// AA Commands
 	void Get_AA_Xml(Client* client, Seperator* sep);
@@ -929,6 +930,7 @@ private:
 #define COMMAND_UNMENTOR		        529
 
 #define COMMAND_CANCEL_EFFECT			530
+#define COMMAND_CUREPLAYER			531
 
 
 #define GET_AA_XML						750

+ 7 - 5
EQ2/source/WorldServer/Entity.cpp

@@ -2224,7 +2224,9 @@ void Entity::CureDetrimentByType(int8 cure_count, int8 det_type, string cure_nam
 			caster_class2 = info_struct->get_class2();
 			caster_class3 = info_struct->get_class3();
 			pass_level_check = false;
+			bool has_level_checks = false;
 			for (int32 x = 0; x < levels->size(); x++){
+				has_level_checks = true;
 				level_class = levels->at(x)->adventure_class;
 				if (!cure_level || ((caster_class1 == level_class || caster_class2 == level_class || caster_class3 == level_class)
 					&& cure_level >= (levels->at(x)->spell_level / 10))){
@@ -2232,7 +2234,8 @@ void Entity::CureDetrimentByType(int8 cure_count, int8 det_type, string cure_nam
 					break;
 				}
 			}
-			if (pass_level_check){
+			
+			if (pass_level_check || !has_level_checks){
 				remove_list.push_back(det->spell);
 				cure_count--;
 				if (cure_count == 0)
@@ -2244,11 +2247,10 @@ void Entity::CureDetrimentByType(int8 cure_count, int8 det_type, string cure_nam
 
 	for (int32 i = 0; i<remove_list.size(); i++){
 		spell = remove_list.at(i);
+		
+		LogWrite(PLAYER__ERROR, 0, "Debug", "Remove spell %s", remove_list.at(i)->spell->GetName());
 		GetZone()->SendDispellPacket(caster, this, cure_name, (string)remove_list.at(i)->spell->GetName(), DISPELL_TYPE_CURE);
-		if (GetZone())
-			GetZone()->RemoveTargetFromSpell(spell, this);
-		RemoveSpellEffect(spell);
-		RemoveDetrimentalSpell(spell);
+		GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "cured", false, true, this);
 	}
 	remove_list.clear();
 }

+ 2 - 0
EQ2/source/WorldServer/Items/Items.cpp

@@ -841,6 +841,7 @@ Item::Item(){
 	no_buy_back = false;
 	no_sale = false;
 	created = std::time(nullptr);
+	effect_type = NO_EFFECT_TYPE;
 }
 
 Item::Item(Item* in_item){
@@ -860,6 +861,7 @@ Item::Item(Item* in_item){
 	no_sale = in_item->no_sale;
 	created = in_item->created;
 	grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end());
+	effect_type = in_item->effect_type;
 }
 
 Item::~Item(){

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

@@ -614,6 +614,16 @@ extern MasterItemList master_item_list;
 #define	DISPLAY_FLAG_NOT_FOR_SALE		64
 #define	DISPLAY_FLAG_NO_BUY				128 // disables buying on merchant 'buy' list
 
+enum ItemEffectType {
+	NO_EFFECT_TYPE=0,
+	EFFECT_CURE_TYPE_TRAUMA=1,
+	EFFECT_CURE_TYPE_ARCANE=2,
+	EFFECT_CURE_TYPE_NOXIOUS=3,
+	EFFECT_CURE_TYPE_ELEMENTAL=4,
+	EFFECT_CURE_TYPE_CURSE=5,
+	EFFECT_CURE_TYPE_MAGIC=6,
+	EFFECT_CURE_TYPE_ALL=7
+};
 #pragma pack(1)
 struct ItemStatsValues{
 	sint16			str;
@@ -922,6 +932,7 @@ public:
 	bool 					needs_deletion;
 	std::time_t				created;
 	std::map<int32, bool>	grouped_char_ids;
+	ItemEffectType			effect_type;
 	
 	void AddEffect(string effect, int8 percentage, int8 subbulletflag);
 	void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);

+ 2 - 0
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -187,6 +187,8 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
 		item->SetItemScript(string(result->GetStringStr("lua_script")));
 		LogWrite(ITEM__DEBUG, 5, "LUA", "--Loading LUA Item Script: '%s'", item->item_script.c_str());
 	}
+	
+	item->effect_type 							= (ItemEffectType)result->GetInt32Str("effect_type");
 
 	if(item->generic_info.max_charges > 0)
 		item->details.count						= item->generic_info.max_charges;

+ 35 - 11
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -7818,6 +7818,20 @@ int EQ2Emu_lua_GetItemType(lua_State* state) {
 	return 1;
 }
 
+int EQ2Emu_lua_GetItemEffectType(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Item* item = lua_interface->GetItem(state);
+
+	if (!item) {
+		lua_interface->LogError("%s: LUA GetItemEffectType command error: item pointer is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	lua_interface->SetInt32Value(state, item->effect_type);
+	return 1;
+}
+
 int EQ2Emu_lua_AddTransportSpawn(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -8909,18 +8923,14 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
 		return 0;
 
 	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
-	if (!spell) {
-		lua_interface->LogError("%s: LUA CureByType command error: can only be used in a spell script", lua_interface->GetScriptName(state));
-		return 0;
-	}
-
 	int8 cure_count = lua_interface->GetInt8Value(state);
 	int8 cure_type = lua_interface->GetInt8Value(state, 2);
 	string cure_name = lua_interface->GetStringValue(state, 3);
 	int8 cure_level = lua_interface->GetInt8Value(state, 4);
 	Spawn* target = lua_interface->GetSpawn(state, 5);
+	Spawn* caster = lua_interface->GetSpawn(state, 6); // optional
 	
-	if(!spell || spell->resisted) {
+	if(spell && spell->resisted) {
 		return 0;
 	}
 	
@@ -8930,13 +8940,21 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
 			return 0;
 		}
 
-		if (((Entity*)target)->GetDetTypeCount(cure_type) > 0)
-			((Entity*)target)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level);
+		if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) {
+			std::string alternate_name = "item";
+			if(spell)
+				alternate_name = std::string(spell->spell->GetName());
+			
+			if(!caster && spell)
+				caster = (Spawn*)spell->caster;
+			
+			((Entity*)target)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : alternate_name, (Entity*)caster, cure_level);
+		}
 	}
 	else {
 		ZoneServer* zone = spell->caster->GetZone();
 		vector<int32> targets = spell->targets;
-
+		vector<Entity*> targets_to_cure;
 		spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 		for (int8 i = 0; i < targets.size(); i++) {
 			target = zone->GetSpawnByID(targets.at(i));
@@ -8944,10 +8962,16 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
 			if (!target || !target->IsEntity())
 				continue;
 
-			if (((Entity*)target)->GetDetTypeCount(cure_type) > 0)
-				((Entity*)target)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level);
+			if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) {
+				targets_to_cure.push_back((Entity*)target);
+			}
 		}
 		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
+		
+		vector<Entity*>::iterator itr;
+		for(itr = targets_to_cure.begin(); itr != targets_to_cure.end(); itr++) {
+			((Entity*)*itr)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level);
+		}
 	}
 	return 0;
 }

+ 1 - 0
EQ2/source/WorldServer/LuaFunctions.h

@@ -146,6 +146,7 @@ int EQ2Emu_lua_UnequipSlot(lua_State* state);
 int EQ2Emu_lua_SetEquipment(lua_State* state);
 int EQ2Emu_lua_GetItemByID(lua_State* state);
 int EQ2Emu_lua_GetItemType(lua_State* state);
+int EQ2Emu_lua_GetItemEffectType(lua_State* state);
 int EQ2Emu_lua_GetSpellName(lua_State* state);
 
 //Misc

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

@@ -966,6 +966,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "GetEquippedItemBySlot", EQ2Emu_lua_GetEquippedItemBySlot);
 	lua_register(state, "GetItemByID", EQ2Emu_lua_GetItemByID);
 	lua_register(state, "GetItemType", EQ2Emu_lua_GetItemType);
+	lua_register(state, "GetItemEffectType", EQ2Emu_lua_GetItemEffectType);
 	lua_register(state, "GetSpellName", EQ2Emu_lua_GetSpellName);
 	lua_register(state, "PerformCameraShake", EQ2Emu_lua_PerformCameraShake);
 	lua_register(state, "GetModelType", EQ2Emu_lua_GetModelType);
@@ -2162,7 +2163,7 @@ lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool
 	return ret;
 }
 
-bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn, sint64* returnValue) {
+bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn, Spawn* target, sint64* returnValue) {
 	if(!item)
 		return false;
 	lua_State* state = GetItemScript(script_name.c_str(), true, true);
@@ -2188,6 +2189,10 @@ bool LuaInterface::RunItemScript(string script_name, const char* function_name,
 			SetSpawnValue(state, spawn);
 			num_parms++;
 		}
+		if(target){
+			SetSpawnValue(state, target);
+			num_parms++;
+		}
 		if(!CallItemScript(state, num_parms, returnValue)){
 			if(mutex)
 				mutex->releasereadlock(__FUNCTION__, __LINE__);

+ 1 - 1
EQ2/source/WorldServer/LuaInterface.h

@@ -252,7 +252,7 @@ public:
 	const char*		GetScriptName(lua_State* state);
 
 	void			RemoveSpawnScript(const char* name);
-	bool			RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, sint64* returnValue = 0);
+	bool			RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, Spawn* target = 0, sint64* returnValue = 0);
 	bool			RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, std::string* returnValue = 0);
 	bool			CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue = 0);
 	bool			CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0);

+ 25 - 1
EQ2/source/WorldServer/PlayerGroups.cpp

@@ -885,4 +885,28 @@ void PlayerGroup::UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked) {
 
 	if(!groupMembersLocked)
 		MGroupMembers.releasewritelock();
-}
+}
+
+Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_position) {
+	Entity* ret = nullptr;
+
+	deque<GroupMemberInfo*>::iterator itr;
+	
+	MGroupMembers.readlock();
+	
+	int32 count = 1;
+	for (itr = m_members.begin(); itr != m_members.end(); itr++) {
+		if ((*itr)->member == seeker) {
+			continue;
+		}
+		count++;
+		if(count >= mapped_position) {
+			ret = (Entity*)(*itr)->member;
+			break;
+		}
+	}
+	
+	MGroupMembers.releasereadlock();
+
+	return ret;
+}

+ 2 - 1
EQ2/source/WorldServer/PlayerGroups.h

@@ -86,7 +86,8 @@ public:
 
 	void RemoveClientReference(Client* remove);
 	void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked=false);
-
+	Entity* GetGroupMemberByPosition(Entity* seeker, int32 mapped_position);
+	
 	Mutex MGroupMembers;				// Mutex for the group members
 private:
 	int32					m_id;		// ID of this group

+ 8 - 1
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -339,7 +339,14 @@ void RuleManager::Init()
 	RULE_INIT(R_Spells, PlayerSpellSaveStateWaitInterval, "100"); // time in milliseconds we wait before performing a save when the spell save trigger is activated, allows additional actions to take place until the cap is hit
 	RULE_INIT(R_Spells, PlayerSpellSaveStateCap, "1000"); // sets a maximum wait time before we queue a spell state save to the DB, given a lot can go on in a short period with players especially in combat, maybe good to have this at a higher interval.
 	RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master
-
+	RULE_INIT(R_Spells, CureSpellID, "110003");
+	RULE_INIT(R_Spells, CureCurseSpellID, "110004");
+	RULE_INIT(R_Spells, CureNoxiousSpellID, "110005");
+	RULE_INIT(R_Spells, CureMagicSpellID, "210006");
+	RULE_INIT(R_Spells, CureTraumaSpellID, "0");
+	RULE_INIT(R_Spells, CureArcaneSpellID, "0");
+	
+	
 	RULE_INIT(R_Expansion, GlobalExpansionFlag, "0");
 	RULE_INIT(R_Expansion, GlobalHolidayFlag, "0");
 

+ 6 - 0
EQ2/source/WorldServer/Rules/Rules.h

@@ -188,6 +188,12 @@ enum RuleType {
 	PlayerSpellSaveStateWaitInterval,
 	PlayerSpellSaveStateCap,
 	RequirePreviousTierScribe,
+	CureSpellID,
+	CureCurseSpellID,
+	CureNoxiousSpellID,
+	CureMagicSpellID,
+	CureTraumaSpellID,
+	CureArcaneSpellID,
 
 	/* ZONE TIMERS */
 	RegenTimer,

+ 101 - 7
EQ2/source/WorldServer/client.cpp

@@ -6865,7 +6865,7 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
 			bool itemDeleted = false;
 			bool itemAdded = false;
 			sint64 dispFlags = 0;
-			if (item && item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buyback_display_flags", item, player, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY))
+			if (item && item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buyback_display_flags", item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY))
 				SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item.");
 			else if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item))
 				SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
@@ -6943,7 +6943,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
 					quantity = total_available;
 			}
 			sint64 dispFlags = 0;
-			if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buy_display_flags", master_item, player, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY))
+			if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buy_display_flags", master_item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY))
 			{
 				SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item.");
 				return;
@@ -7323,7 +7323,7 @@ void Client::SendBuyMerchantList(bool sell) {
 						item_difficulty = ARROW_COLOR_WHITE;
 
 					sint64 overrideValue = 0;
-					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue))
+					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue))
 						item_difficulty = (sint8)overrideValue;
 
 					item_difficulty -= 6;
@@ -7336,7 +7336,7 @@ void Client::SendBuyMerchantList(bool sell) {
 					packet->setArrayDataByName("stack_size2", item->stack_count, i);
 
 					sint64 dispFlags = 0;
-					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, &dispFlags))
+					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, nullptr, &dispFlags))
 						packet->setArrayDataByName("display_flags", (int8)dispFlags, i);
 					
 					std::string overrideValueStr;
@@ -7510,7 +7510,7 @@ void Client::SendSellMerchantList(bool sell) {
 						item_difficulty = ARROW_COLOR_WHITE;
 					
 					sint64 overrideValue = 0;
-					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue))
+					if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue))
 						item_difficulty = (sint8)overrideValue;
 					
 					item_difficulty -= 6;
@@ -7587,7 +7587,7 @@ void Client::SendBuyBackList(bool sell) {
 						item_difficulty = ARROW_COLOR_WHITE;
 					
 					sint64 overrideValue = 0;
-					if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, &overrideValue))
+					if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, nullptr, &overrideValue))
 						item_difficulty = (sint8)overrideValue;
 
 					item_difficulty -= 6;
@@ -7596,7 +7596,7 @@ void Client::SendBuyBackList(bool sell) {
 					packet->setArrayDataByName("item_difficulty", item_difficulty, i);
 
 					sint64 dispFlags = 0;
-					if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buyback_display_flags", master_item, player, &dispFlags))
+					if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buyback_display_flags", master_item, player, nullptr, &dispFlags))
 						packet->setArrayDataByName("display_flags", (int8)dispFlags, i);
 					
 					if (buyback->quantity == 1)
@@ -7904,6 +7904,35 @@ vector<Item*>* Client::GetRepairableItems() {
 	return repairable_items;
 }
 
+
+vector<Item*>* Client::GetItemsByEffectType(ItemEffectType type, ItemEffectType type2) {
+	if(type == NO_EFFECT_TYPE)
+		return nullptr;
+	
+	vector<Item*>* return_items = new vector<Item*>;
+	vector<Item*>* equipped_items = player->GetEquipmentList()->GetAllEquippedItems();
+	map<int32, Item*>* items = player->GetItemList();
+	if (equipped_items && equipped_items->size() > 0) {
+		for (int32 i = 0; i < equipped_items->size(); i++) {
+			Item* item = equipped_items->at(i);
+			if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2)))
+				return_items->push_back(item);
+		}
+	}
+	if (items && items->size() > 0) {
+		map<int32, Item*>::iterator itr;
+		for (itr = items->begin(); itr != items->end(); itr++) {
+			Item* item = itr->second;
+			if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2)))
+				return_items->push_back(item);
+		}
+	}
+	safe_delete(equipped_items);
+	safe_delete(items);
+
+	return return_items;
+}
+
 void Client::SendMailList() {
 	int32 kiosk_id = player->GetIDWithPlayerSpawn(GetMailTransaction());
 	if (kiosk_id > 0) {
@@ -10902,3 +10931,68 @@ void Client::SetPlayer(Player* new_player) {
 	player = new_player;
 	player->SetClient(this);
 }
+
+bool Client::UseItem(Item* item, Spawn* target) {
+	if (item && item->GetItemScript()) {
+		int16 item_index = item->details.index;
+		if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) {
+			SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used");
+		}
+		else if (item->CheckFlag(EVIL_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) {
+			Message(0, "%s requires an evil race.", item->name.c_str());
+		}
+		else if (item->CheckFlag(GOOD_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) {
+			Message(0, "%s requires a good race.", item->name.c_str());
+		}
+		else if (item->generic_info.max_charges == 0 || item->generic_info.max_charges == 0xFFFF) {
+			lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target);
+			return true;
+		}
+		else {
+			if (item->details.count > 0) {
+				std::string itemName = string(item->name);
+				int32 item_id = item->details.item_id;
+				sint64 flags = 0;
+				if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0)
+				{
+					//reobtain item make sure it wasn't removed
+					item = player->item_list.GetItemFromIndex(item_index);
+					if(!item) {
+						LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", GetPlayer()->GetName(), itemName.c_str(), item_id);
+						return true;
+					}
+					else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1) {
+						Message(CHANNEL_NARRATIVE, "%s is out of charges.  It has been removed.", item->name.c_str());
+						RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
+						return true;
+					}
+					else
+					{
+						item->details.count--; // charges
+						item->save_needed = true;
+						QueuePacket(item->serialize(GetVersion(), false, GetPlayer()));
+						if(!item->details.count) {
+							Message(CHANNEL_NARRATIVE, "%s is out of charges.  It has been removed.", item->name.c_str());
+							RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity
+						}
+						return true;
+					}
+				}
+				else {
+					LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, after it returned %i, bypassing any removal/update of items.", GetPlayer()->GetName(), itemName.c_str(), item_id, flags);
+					return true;
+				}
+			}
+			else
+			{
+				//reobtain item make sure it wasn't removed
+				item = player->item_list.GetItemFromIndex(item_index);
+				SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is out of charges.");
+				if(item) {
+					LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however details.count is 0.", GetPlayer()->GetName(), item->name.c_str(), item->details.item_id);
+				}
+			}
+		}
+	}
+	return false;
+}

+ 3 - 0
EQ2/source/WorldServer/client.h

@@ -322,6 +322,7 @@ public:
 	void	AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed = true);
 	deque<BuyBackItem*>*	GetBuyBacks();
 	vector<Item*>* GetRepairableItems();
+	vector<Item*>* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE);
 	void	SendMailList();
 	void	DisplayMailMessage(int32 mail_id);
 	void	HandleSentMail(EQApplicationPacket* app);
@@ -537,6 +538,8 @@ public:
 		zoning_z = z;
 		zoning_h = h;
 	}
+	
+	bool	UseItem(Item* item, Spawn* target = nullptr);
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);

+ 0 - 2
EQ2/source/common/EQStream.cpp

@@ -1653,8 +1653,6 @@ DumpPacket(buffer, length);
 		if(opcode > 0 && opcode <= OP_OutOfSession)
 		{
 			EQProtocolPacket p(newbuffer,newlength);
-			if(opcode == 3)
-				DumpPacket(newbuffer, newlength);
 			ProcessPacket(&p);
 		}
 		else