Browse Source

Immunity support updates, player mail, spell heal pct

Fix #219
Fix #220
Fix #223
Fix #243
Fix #245
Image 3 years ago
parent
commit
362daada59

+ 7 - 3
EQ2/source/WorldServer/Combat.cpp

@@ -546,6 +546,7 @@ bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32
 	return true;
 }
 
+// this is used exclusively by LUA, heal_type is forced lower case via boost::lower(heal_type); in the LUA Functions used by this
 bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod, bool no_calcs, string custom_spell_name){
 	 if(!target || !luaspell || !luaspell->spell)
 		return false;
@@ -608,7 +609,7 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 	}
 
 	int16 type = 0;
-	if (heal_type == "Heal") {
+	if (heal_type == "heal") {
 		if(crit)
 			type = HEAL_PACKET_TYPE_CRIT_HEAL;
 		else
@@ -626,7 +627,7 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 			target->SetHP(target->GetHP() + heal_amt);
 		*/
 	}
-	else if (heal_type == "Power"){
+	else if (heal_type == "power"){
 		if(crit)
 			type = HEAL_PACKET_TYPE_CRIT_MANA;
 		else
@@ -735,6 +736,9 @@ int8 Entity::DetermineHit(Spawn* victim, int8 damage_type, float ToHitBonus, boo
 		if(rand() % roll_chance >= chance)
 			return DAMAGE_PACKET_RESULT_MISS; //successfully avoided
 
+		if(entity_victim->IsImmune(IMMUNITY_TYPE_RIPOSTE))
+		return DAMAGE_PACKET_RESULT_RIPOSTE;
+
 		skill = entity_victim->GetSkillByName("Parry", true);
 		if(skill){
 			if(rand()%roll_chance >= (chance - 5 - skill->current_val/25)){ //successful parry
@@ -921,7 +925,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 		int32 prevDmg = damage;
 		damage = victim->CheckWards(this, damage, damage_type);
 
-		if (damage < prevDmg)
+		if (damage < (sint64)prevDmg)
 			useWards = true;
 
 		victim->TakeDamage(damage);

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

@@ -107,6 +107,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["gender"] = l::bind(&InfoStruct::get_gender, &info_struct);
 	get_int16_funcs["level"] = l::bind(&InfoStruct::get_level, &info_struct);
 	get_int16_funcs["max_level"] = l::bind(&InfoStruct::get_max_level, &info_struct);
+	get_int16_funcs["effective_level"] = l::bind(&InfoStruct::get_effective_level, &info_struct);
 	get_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::get_tradeskill_level, &info_struct);
 	get_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::get_tradeskill_max_level, &info_struct);
 	get_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::get_cur_concentration, &info_struct);
@@ -240,6 +241,7 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["gender"] = l::bind(&InfoStruct::set_gender, &info_struct, l::_1);
 	set_int16_funcs["level"] = l::bind(&InfoStruct::set_level, &info_struct, l::_1);
 	set_int16_funcs["max_level"] = l::bind(&InfoStruct::set_max_level, &info_struct, l::_1);
+	set_int16_funcs["effective_level"] = l::bind(&InfoStruct::set_effective_level, &info_struct, l::_1);
 	set_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::set_tradeskill_level, &info_struct, l::_1);
 	set_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::set_tradeskill_max_level, &info_struct, l::_1);
 	set_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::set_cur_concentration, &info_struct, l::_1);
@@ -567,9 +569,13 @@ void Entity::ChangePrimaryWeapon(){
 		melee_combat_data.wield_type = item->weapon_info->wield_type;
 	}
 	else{
+		int16 effective_level = GetInfoStruct()->get_effective_level();
+		if ( !effective_level )
+			effective_level = GetLevel();
+
 		melee_combat_data.primary_weapon_delay = 2000;
-		melee_combat_data.primary_weapon_damage_low = (int32)(1 + GetLevel() * .2);
-		melee_combat_data.primary_weapon_damage_high = (int32)(5 + GetLevel() * (GetLevel()/5));
+		melee_combat_data.primary_weapon_damage_low = (int32)(1 + effective_level * .2);
+		melee_combat_data.primary_weapon_damage_high = (int32)(5 + effective_level * (effective_level/5));
 		if(IsNPC())
 			melee_combat_data.primary_weapon_type = ((NPC*)this)->GetAttackType();
 		else
@@ -591,9 +597,13 @@ void Entity::ChangeSecondaryWeapon(){
 		melee_combat_data.secondary_weapon_type = item->GetWeaponType();
 	}
 	else{
+		int16 effective_level = GetInfoStruct()->get_effective_level();
+		if ( !effective_level )
+			effective_level = GetLevel();
+
 		melee_combat_data.secondary_weapon_delay = 2000;
-		melee_combat_data.secondary_weapon_damage_low = (int32)(1 + GetLevel() * .2);
-		melee_combat_data.secondary_weapon_damage_high = (int32)(5 + GetLevel() * (GetLevel()/6));
+		melee_combat_data.secondary_weapon_damage_low = (int32)(1 + effective_level * .2);
+		melee_combat_data.secondary_weapon_damage_high = (int32)(5 + effective_level * (effective_level/6));
 		melee_combat_data.secondary_weapon_type = 1;
 	}
 	if(IsNPC())
@@ -740,7 +750,10 @@ void Entity::DoRegenUpdate(){
 		return;
 	sint32 hp = GetHP();
 	sint32 power = GetPower();
-	int16 level = GetLevel();
+
+	int16 effective_level = GetInfoStruct()->get_effective_level();
+	if(!effective_level)
+		effective_level = GetLevel();
 
 	// No regen for NPC's while in combat
 	// Temp solution for now
@@ -749,7 +762,7 @@ void Entity::DoRegenUpdate(){
 
 	if(hp < GetTotalHP()){
 		if(regen_hp_rate == 0)
-			regen_hp_rate = (int)(level*.75)+(int)(level/10) + 1;
+			regen_hp_rate = (int)(effective_level*.75)+(int)(effective_level/10) + 1;
 		int16 temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN];
 		if((hp + temp) > GetTotalHP())
 			SetHP(GetTotalHP());
@@ -761,7 +774,7 @@ void Entity::DoRegenUpdate(){
 	}
 	if(GetPower() < GetTotalPower()){
 		if(regen_power_rate == 0)
-			regen_power_rate = level + (int)(level/10) + 1;
+			regen_power_rate = effective_level + (int)(effective_level/10) + 1;
 		cout << "regen_power_rate: " << regen_power_rate << endl;
 		if((power + regen_power_rate) > GetTotalPower())
 			SetPower(GetTotalPower());
@@ -2494,6 +2507,28 @@ bool Entity::IsDazeImmune(){
 	return (immunities[IMMUNITY_TYPE_DAZE] && immunities[IMMUNITY_TYPE_DAZE]->size(true) > 0);
 }
 
+void Entity::AddImmunity(LuaSpell* spell, int16 type){
+	if (!spell)
+		return;
+
+	if (!immunities[type])
+		immunities[type] = new MutexList<LuaSpell*>;
+
+	immunities[type]->Add(spell);
+}
+
+void Entity::RemoveImmunity(LuaSpell* spell, int16 type){
+	MutexList<LuaSpell*>* list = immunities[type];
+	if (!list || list->size(true) == 0)
+		return;
+
+	list->Remove(spell);
+}
+
+bool Entity::IsImmune(int16 type){
+	return (immunities[type] && immunities[type]->size(true) > 0);
+}
+
 void Entity::RemoveEffectsFromLuaSpell(LuaSpell* spell){
 	if (!spell)
 		return;

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

@@ -122,6 +122,7 @@ struct InfoStruct{
 		gender_ = 0;
 		level_ = 0;
 		max_level_ = 0;
+		effective_level_ = 0;
 		tradeskill_level_ = 0;
 		tradeskill_max_level_ = 0;
 		cur_concentration_ = 0;
@@ -263,6 +264,7 @@ struct InfoStruct{
 		gender_ = oldStruct->get_gender();
 		level_ = oldStruct->get_level();
 		max_level_ = oldStruct->get_max_level();
+		effective_level_ = oldStruct->get_effective_level();
 		tradeskill_level_ = oldStruct->get_tradeskill_level();
 		tradeskill_max_level_ = oldStruct->get_tradeskill_max_level();
 		cur_concentration_ = oldStruct->get_cur_concentration();
@@ -398,6 +400,7 @@ struct InfoStruct{
 	int8	 get_gender() { std::lock_guard<std::mutex> lk(classMutex); return gender_; }
 	int16 	 get_level() { std::lock_guard<std::mutex> lk(classMutex); return level_; }
 	int16	 get_max_level() { std::lock_guard<std::mutex> lk(classMutex); return max_level_; }
+	int16	 get_effective_level() { std::lock_guard<std::mutex> lk(classMutex); return effective_level_; } 
 	int16	 get_tradeskill_level() { std::lock_guard<std::mutex> lk(classMutex); return tradeskill_level_; }
 	int16	 get_tradeskill_max_level() { std::lock_guard<std::mutex> lk(classMutex); return tradeskill_max_level_; }
 
@@ -549,6 +552,7 @@ struct InfoStruct{
 	void	set_gender(int8 value) { std::lock_guard<std::mutex> lk(classMutex); gender_ = value; }
 	void	set_level(int16 value) { std::lock_guard<std::mutex> lk(classMutex); level_ = value; }
 	void	set_max_level(int16 value) { std::lock_guard<std::mutex> lk(classMutex); max_level_ = value; }
+	void	set_effective_level(int16 value) { std::lock_guard<std::mutex> lk(classMutex); effective_level_ = value; }
 
 	void	set_cur_concentration(int8 value) { std::lock_guard<std::mutex> lk(classMutex); cur_concentration_ = value; }
 	void	set_max_concentration(int8 value) { std::lock_guard<std::mutex> lk(classMutex); max_concentration_ = value; }
@@ -795,6 +799,7 @@ private:
 	int8			gender_;
 	int16			level_;
 	int16			max_level_;
+	int16			effective_level_;
 	int16			tradeskill_level_;
 	int16			tradeskill_max_level_;
 	
@@ -1016,6 +1021,8 @@ struct ThreatTransfer {
 #define IMMUNITY_TYPE_ROOT 5
 #define IMMUNITY_TYPE_FEAR 6
 #define IMMUNITY_TYPE_AOE 7
+#define IMMUNITY_TYPE_TAUNT 8
+#define IMMUNITY_TYPE_RIPOSTE 9
 
 //class Spell;
 //class ZoneServer;
@@ -1467,6 +1474,9 @@ public:
 	void AddDazeImmunity(LuaSpell* spell);
 	void RemoveDazeImmunity(LuaSpell* spell);
 	bool IsDazeImmune();
+	void AddImmunity(LuaSpell* spell, int16 type);
+	void RemoveImmunity(LuaSpell* spell, int16 type);
+	bool IsImmune(int16 type);
 	void AddFlightSpell(LuaSpell* spell);
 	void RemoveFlightSpell(LuaSpell* spell);
 	void AddSafefallSpell(LuaSpell* spell);

+ 189 - 181
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -911,23 +911,23 @@ int EQ2Emu_lua_StartConversation(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	vector<ConversationOption>* conversation = lua_interface->GetConversation(state);
-	Spawn* npc = lua_interface->GetSpawn(state, 2);
+	Spawn* source = lua_interface->GetSpawn(state, 2);
 	Spawn* player = lua_interface->GetSpawn(state, 3);
 	string text = lua_interface->GetStringValue(state, 4);
 	string mp3 = lua_interface->GetStringValue(state, 5);
 	int32 key1 = lua_interface->GetInt32Value(state, 6);
 	int32 key2 = lua_interface->GetInt32Value(state, 7);
-	if (conversation && conversation->size() > 0 && text.length() > 0 && npc && npc->IsEntity() && player && player->IsPlayer()) {
-		Client* client = npc->GetZone()->GetClientBySpawn(player);
+	if (conversation && conversation->size() > 0 && text.length() > 0 && source && player && player->IsPlayer()) {
+		Client* client = source->GetZone()->GetClientBySpawn(player);
 		if (mp3.length() > 0)
-			client->DisplayConversation((Entity*)npc, 1, conversation, text.c_str(), mp3.c_str(), key1, key2);
+			client->DisplayConversation(source, 1, conversation, text.c_str(), mp3.c_str(), key1, key2);
 		else
-			client->DisplayConversation((Entity*)npc, 1, conversation, text.c_str());
+			client->DisplayConversation(source, 1, conversation, text.c_str());
 		safe_delete(conversation);
 		lua_interface->SetConversationValue(state, NULL);
 	}
 	else
-		LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in StartConversation, potentially AddConversationOption not yet called or the StartConversation arguments are incorrect, text: %s, conversationSize: %i.", npc ? npc->GetName() : "UNKNOWN", text.size() ? text.c_str() : "", conversation ? conversation->size() : -1);
+		LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in StartConversation, potentially AddConversationOption not yet called or the StartConversation arguments are incorrect, text: %s, conversationSize: %i.", source ? source->GetName() : "UNKNOWN", text.size() ? text.c_str() : "", conversation ? conversation->size() : -1);
 	return 0;
 }
 
@@ -1149,6 +1149,8 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 	bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1;
 	string custom_spell_name = lua_interface->GetStringValue(state, 7);//custom spell name
 	lua_interface->ResetFunctionStack(state);
+	
+	boost::to_lower(heal_type);
 	if (caster && caster->IsEntity()) {
 		bool success = false;
 		luaspell->resisted = false;
@@ -1178,6 +1180,97 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_SpellHealPct(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	if (!luaspell)
+		return 0;
+	Spawn* caster = luaspell->caster;
+	string heal_type = lua_interface->GetStringValue(state);//power, heal ect
+	float percentage = lua_interface->GetFloatValue(state, 2);
+	bool current_value = lua_interface->GetBooleanValue(state, 3);
+	bool caster_value = lua_interface->GetBooleanValue(state, 4);
+	Spawn* target = lua_interface->GetSpawn(state, 5);
+	int8 crit_mod = lua_interface->GetInt32Value(state, 6);
+	bool no_calcs = lua_interface->GetInt32Value(state, 7) == 1;
+	string custom_spell_name = lua_interface->GetStringValue(state, 8);//custom spell name
+	lua_interface->ResetFunctionStack(state);
+
+	boost::to_lower(heal_type);
+	int32 min_heal = 0, max_heal = 0;
+	if (caster && caster->IsEntity() && target) {
+		if(percentage <= 0.0f)
+		{
+			LogWrite(LUA__ERROR, 0, "LUA", "Error applying SpellHealPct on '%s'.  percentage %f is less than or equal to 0.",target->GetName(),percentage);
+			return 0;
+		}
+
+		if(heal_type == "power")
+		{
+			if(current_value)
+			{
+				if(caster_value)
+					min_heal = max_heal = (int32)(float)caster->GetPower() * (percentage / 100.0f);
+				else
+					min_heal = max_heal = (int32)(float)target->GetPower() * (percentage / 100.0f);
+			}
+			else
+			{
+				if(caster_value)
+					min_heal = max_heal = (int32)(float)caster->GetTotalPower() * (percentage / 100.0f);
+				else
+					min_heal = max_heal = (int32)(float)target->GetTotalPower() * (percentage / 100.0f);
+			}
+
+		}
+		else
+		{
+			if(current_value)
+			{
+				if(caster_value)
+					min_heal = max_heal = (int32)(float)caster->GetHP() * (percentage / 100.0f);
+				else
+					min_heal = max_heal = (int32)(float)target->GetHP() * (percentage / 100.0f);
+			}
+			else
+			{
+				if(caster_value)
+					min_heal = max_heal = (int32)(float)caster->GetTotalHP() * (percentage / 100.0f);
+				else
+					min_heal = max_heal = (int32)(float)target->GetTotalHP() * (percentage / 100.0f);
+			}
+		}
+
+		bool success = false;
+		luaspell->resisted = false;
+		if (target) {
+			float distance = caster->GetDistance(target, true);
+			if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name))
+				success = true;
+		}
+		if (luaspell->targets.size() > 0) {
+			Spawn* target = 0;
+			ZoneServer* zone = luaspell->caster->GetZone();
+			luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
+			for (int32 i = 0; i < luaspell->targets.size(); i++) {
+				if ((target = zone->GetSpawnByID(luaspell->targets[i]))) {
+					float distance = caster->GetDistance(target, true);
+					((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name);
+				}
+			}
+			luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
+			success = true;
+		}
+		if (success) {
+			if (caster->GetZone())
+				caster->GetZone()->TriggerCharSheetTimer();
+		}
+	}
+	return 0;
+}
+
 int EQ2Emu_lua_AddItem(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -1492,9 +1585,6 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 	if (!lua_interface)
 		return 0;
 	Spawn* target = lua_interface->GetSpawn(state);
-	int32 target_id = 0;
-	if (target)
-		target_id = target->GetID();
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
 	if (!luaspell)
 		return 0;
@@ -1530,9 +1620,6 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 		bool race_match = false;
 		bool success = false;
 		luaspell->resisted = false;
-		if (luaspell->initial_target == target_id) {
-			int xxx = 0;
-		}
 		if (luaspell->targets.size() > 0) {
 			ZoneServer* zone = luaspell->caster->GetZone();
 			Spawn* target = 0;
@@ -1542,8 +1629,6 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 
 					if (race_req.size() > 0) {
 						for (int8 i = 0; i < race_req.size(); i++) {
-
-							int32 xxx = target->GetLuaRaceId();
 							if (target->GetLuaRaceId() == race_req[i]) {
 								race_match = true;
 							}
@@ -3483,8 +3568,6 @@ int EQ2Emu_lua_AddQuestStepKill(lua_State* state) {
 			taskgroup = str_taskgroup.c_str();
 		int32 npc_id = 0;
 		vector<int32>* ids = 0;
-
-		Spawn* spawn = nullptr;
 		int i = 0;
 		while ((npc_id = lua_interface->GetInt32Value(state, 8 + i))) {
 			if (ids == 0)
@@ -3856,7 +3939,6 @@ int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state) {
 	string select_rewards_str = lua_interface->GetStringValue(state, 6);
 	string factions_map_str = lua_interface->GetStringValue(state, 7);
 	string text = lua_interface->GetStringValue(state, 8);
-	int32 source_id = 0;
 	if (playerSpawn && playerSpawn->IsPlayer()) {
 		Player* player = (Player*)playerSpawn;
 		Client* client = player->GetZone()->GetClientBySpawn(player);
@@ -7446,7 +7528,6 @@ int EQ2Emu_lua_Knockback(lua_State* state) {
 	float vertical = lua_interface->GetFloatValue(state, 4);
 	float horizontal = lua_interface->GetFloatValue(state, 5);
 	bool use_heading = lua_interface->GetInt8Value(state, 6) == 1 ? true : false;
-	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
 
 	if (!target_spawn) {
 		lua_interface->LogError("%s: LUA Knockback command error: target_spawn is not valid", lua_interface->GetScriptName(state));
@@ -8502,6 +8583,22 @@ int EQ2Emu_lua_CopySpawnAppearance(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_HasSpellImmunity(lua_State* state) {
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 type = lua_interface->GetInt8Value(state, 2);
+	if (!spawn) {
+		lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn does not exist.", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	else if (!spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn %s is not an entity.", lua_interface->GetScriptName(state), spawn->GetName());
+		return 0;
+	}
+	
+	lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsImmune(type));
+	return 1;
+}
+
 int EQ2Emu_lua_AddImmunitySpell(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -8521,97 +8618,17 @@ int EQ2Emu_lua_AddImmunitySpell(lua_State* state) {
 			return 0;
 		}
 		Entity* entity = ((Entity*)spawn);
-		switch (type) {
-		case IMMUNITY_TYPE_AOE:
-			entity->AddAOEImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_AOE_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_AOE_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_STUN:
-			entity->AddStunImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_STUN_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_STUN_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_ROOT:
-			entity->AddRootImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_ROOT_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_ROOT_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_DAZE:
-			entity->AddDazeImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_DAZE_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_DAZE_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_FEAR:
-			entity->AddFearImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_FEAR_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_FEAR_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_MEZ:
-			entity->AddMezImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_MEZ_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_MEZ_IMMUNE;
-			break;
-		case IMMUNITY_TYPE_STIFLE:
-			entity->AddStifleImmunity(spell);
-			if (!(spell->effect_bitmask & EFFECT_FLAG_STIFLE_IMMUNE))
-				spell->effect_bitmask += EFFECT_FLAG_STIFLE_IMMUNE;
-			break;
-		default:
-			lua_interface->LogError("%s: LUA AddImmunitySpell command error: invalid immunity type", lua_interface->GetScriptName(state));
-		}
+		entity->AddImmunity(spell, type);
 	}
 	else {
-		bool should_break = false;
 		spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 		for (int8 i = 0; i < spell->targets.size(); i++) {
 			spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i));
 			if (!spawn || !spawn->IsEntity())
 				continue;
 			Entity* entity = ((Entity*)spawn);
-			switch (type) {
-			case IMMUNITY_TYPE_AOE:
-				entity->AddAOEImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_AOE_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_AOE_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_STUN:
-				entity->AddStunImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_STUN_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_STUN_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_ROOT:
-				entity->AddRootImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_ROOT_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_ROOT_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_DAZE:
-				entity->AddDazeImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_DAZE_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_DAZE_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_FEAR:
-				entity->AddFearImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_FEAR_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_FEAR_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_MEZ:
-				entity->AddMezImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_MEZ_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_MEZ_IMMUNE;
-				break;
-			case IMMUNITY_TYPE_STIFLE:
-				entity->AddStifleImmunity(spell);
-				if (!(spell->effect_bitmask & EFFECT_FLAG_STIFLE_IMMUNE))
-					spell->effect_bitmask += EFFECT_FLAG_STIFLE_IMMUNE;
-				break;
-			default:
-				lua_interface->LogError("%s: LUA AddImmunitySpell command error: invalid immunity type", lua_interface->GetScriptName(state));
-				should_break = true;
+			entity->AddImmunity(spell, type);
 			}
-			if (should_break)
-				break;
-		}
 		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 	}
 
@@ -8637,68 +8654,16 @@ int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state) {
 			return 0;
 		}
 		Entity* entity = ((Entity*)spawn);
-		switch (type) {
-		case IMMUNITY_TYPE_AOE:
-			entity->RemoveAOEImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_STUN:
-			entity->RemoveStunImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_ROOT:
-			entity->RemoveRootImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_DAZE:
-			entity->RemoveDazeImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_FEAR:
-			entity->RemoveFearImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_MEZ:
-			entity->RemoveMezImmunity(spell);
-			break;
-		case IMMUNITY_TYPE_STIFLE:
-			entity->RemoveStifleImmunity(spell);
-			break;
-		default:
-			lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: invalid immunity type", lua_interface->GetScriptName(state));
-		}
+		entity->RemoveImmunity(spell, type);
 	}
 	else {
-		bool should_break = false;
 		spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 		for (int8 i = 0; i < spell->targets.size(); i++) {
 			spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i));
 			if (!spawn || !spawn->IsEntity())
 				continue;
 			Entity* entity = ((Entity*)spawn);
-			switch (type) {
-			case IMMUNITY_TYPE_AOE:
-				entity->RemoveAOEImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_STUN:
-				entity->RemoveStunImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_ROOT:
-				entity->RemoveRootImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_DAZE:
-				entity->RemoveDazeImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_FEAR:
-				entity->RemoveFearImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_MEZ:
-				entity->RemoveMezImmunity(spell);
-				break;
-			case IMMUNITY_TYPE_STIFLE:
-				entity->RemoveStifleImmunity(spell);
-				break;
-			default:
-				lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: invalid immunity type", lua_interface->GetScriptName(state));
-				should_break = true;
-			}
-			if (should_break)
-				break;
+			entity->RemoveImmunity(spell, type);
 		}
 		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 	}
@@ -10015,33 +9980,6 @@ int EQ2Emu_lua_ProcHate(lua_State* state) {
 	return 0;
 }
 
-int EQ2Emu_lua_AddLootToObject(lua_State* state) {
-	if (!lua_interface)
-		return 0;
-	Spawn* object = lua_interface->GetSpawn(state);
-	Spawn* player = lua_interface->GetSpawn(state, 2);
-	if (object && player && player->IsPlayer()) {
-		int32 coins = lua_interface->GetInt32Value(state, 3);
-		vector<Item*>* items = 0;
-		int i = 0;
-		int32 item_id = 0;
-		while ((item_id = lua_interface->GetInt32Value(state, 4 + i))) {
-			if (items == 0)
-				items = new vector<Item*>;
-			if (master_item_list.GetItem(item_id))
-				items->push_back(master_item_list.GetItem(item_id));
-			i++;
-		}
-		Client* client = 0;
-		client = object->GetZone()->GetClientBySpawn(player);
-		if (client) {
-			((Player*)player)->AddPendingLootItems(object->GetID(), items);
-		}
-		safe_delete(items);
-	}
-	return 0;
-}
-
 int EQ2Emu_lua_GiveExp(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -11568,3 +11506,73 @@ int EQ2Emu_lua_SetCharSheetChanged(lua_State* state) {
 	return 0;
 }
 
+int EQ2Emu_lua_AddPlayerMail(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	std::string fromName = lua_interface->GetStringValue(state, 2);
+	std::string subjectName = lua_interface->GetStringValue(state, 3);
+	std::string mailBody = lua_interface->GetStringValue(state, 4);
+	int8 mailType = lua_interface->GetInt8Value(state, 5);
+
+	int32 copper = lua_interface->GetInt32Value(state, 6);
+	int32 silver = lua_interface->GetInt32Value(state, 7);
+	int32 gold = lua_interface->GetInt32Value(state, 8);
+	int32 platinum = lua_interface->GetInt32Value(state, 9);
+	
+	int32 item_id = lua_interface->GetInt32Value(state, 10);
+
+	int16 stack_size = lua_interface->GetInt32Value(state, 11);
+
+	int32 expire_time = lua_interface->GetInt32Value(state, 12);
+	
+	int32 sent_time = lua_interface->GetInt32Value(state, 13);
+
+	lua_interface->ResetFunctionStack(state);
+	
+	if(!spawn || !spawn->IsPlayer())
+	{
+		lua_interface->LogError("%s: LUA AddPlayerMail command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state));
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+	}
+
+	int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp();
+	int32 char_id = ((Player*)spawn)->GetCharacterID();
+
+	((Player*)spawn)->GetClient()->CreateAndUpdateMail(fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time);
+	lua_interface->SetBooleanValue(state, true);
+	return 1;
+}
+
+
+int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	int32 char_id = lua_interface->GetInt32Value(state);
+	std::string fromName = lua_interface->GetStringValue(state, 2);
+	std::string subjectName = lua_interface->GetStringValue(state, 3);
+	std::string mailBody = lua_interface->GetStringValue(state, 4);
+	int8 mailType = lua_interface->GetInt8Value(state, 5);
+
+	int32 copper = lua_interface->GetInt32Value(state, 6);
+	int32 silver = lua_interface->GetInt32Value(state, 7);
+	int32 gold = lua_interface->GetInt32Value(state, 8);
+	int32 platinum = lua_interface->GetInt32Value(state, 9);
+	
+	int32 item_id = lua_interface->GetInt32Value(state, 10);
+
+	int16 stack_size = lua_interface->GetInt32Value(state, 11);
+
+	int32 expire_time = lua_interface->GetInt32Value(state, 12);
+	
+	int32 sent_time = lua_interface->GetInt32Value(state, 13);
+
+	lua_interface->ResetFunctionStack(state);
+	
+	int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp();
+
+	Client::CreateMail(char_id, fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time);
+	lua_interface->SetBooleanValue(state, true);
+	return 1;
+}

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

@@ -161,6 +161,7 @@ int EQ2Emu_lua_ModifyHP(lua_State* state);
 int EQ2Emu_lua_ModifyTotalPower(lua_State* state);
 int EQ2Emu_lua_ModifyTotalHP(lua_State* state);
 int EQ2Emu_lua_SpellHeal(lua_State* state);
+int EQ2Emu_lua_SpellHealPct(lua_State* state);
 
 int EQ2Emu_lua_AddItem(lua_State* state);
 int EQ2Emu_lua_SummonItem(lua_State* state);
@@ -189,7 +190,6 @@ int EQ2Emu_lua_HasLootItem(lua_State* state);
 int EQ2Emu_lua_RemoveLootItem(lua_State* state);
 int EQ2Emu_lua_AddLootCoin(lua_State* state);
 int EQ2Emu_lua_GiveLoot(lua_State* state);
-int EQ2Emu_lua_AddLootToObject(lua_State* state);
 int EQ2Emu_lua_HasPendingLoot(lua_State* state);
 int EQ2Emu_lua_HasPendingLootItem(lua_State* state);
 int EQ2Emu_lua_CreateConversation(lua_State* state);
@@ -413,6 +413,7 @@ int EQ2Emu_lua_CopySpawnAppearance(lua_State* state);
 int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state);
 int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state);
 int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state);
+int EQ2Emu_lua_HasSpellImmunity(lua_State* state);
 int EQ2Emu_lua_AddImmunitySpell(lua_State* state);
 int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state);
 int EQ2Emu_lua_SetSpellSnareValue(lua_State* state);
@@ -544,4 +545,7 @@ int EQ2Emu_lua_SetInfoStructSInt(lua_State* state);
 int EQ2Emu_lua_SetInfoStructFloat(lua_State* state);
 
 int EQ2Emu_lua_SetCharSheetChanged(lua_State* state);
+
+int EQ2Emu_lua_AddPlayerMail(lua_State* state);
+int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state);
 #endif

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

@@ -819,6 +819,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SpellDamage", EQ2Emu_lua_SpellDamage);
 	lua_register(state, "CastSpell", EQ2Emu_lua_CastSpell);	
 	lua_register(state, "SpellHeal", EQ2Emu_lua_SpellHeal);
+	lua_register(state, "SpellHealPct", EQ2Emu_lua_SpellHealPct);
 	lua_register(state, "AddItem", EQ2Emu_lua_AddItem);
 	lua_register(state, "SummonItem", EQ2Emu_lua_SummonItem);
 	lua_register(state, "RemoveItem", EQ2Emu_lua_RemoveItem);
@@ -1127,6 +1128,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SetSpellTriggerCount", EQ2Emu_lua_SetSpellTriggerCount);
 	lua_register(state, "GetSpellTriggerCount", EQ2Emu_lua_GetSpellTriggerCount);
 	lua_register(state, "RemoveTriggerFromSpell", EQ2Emu_lua_RemoveTriggerFromSpell);
+	lua_register(state, "HasSpellImmunity", EQ2Emu_lua_HasSpellImmunity);
 	lua_register(state, "AddImmunitySpell", EQ2Emu_lua_AddImmunitySpell);
 	lua_register(state, "RemoveImmunitySpell", EQ2Emu_lua_RemoveImmunitySpell);
 	lua_register(state, "SetSpellSnareValue", EQ2Emu_lua_SetSpellSnareValue);
@@ -1178,7 +1180,6 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "ProcHate", EQ2Emu_lua_ProcHate);
 
 	lua_register(state, "GetRandomSpawnByID", EQ2Emu_lua_GetRandomSpawnByID);
-	lua_register(state, "AddLootToObject", EQ2Emu_lua_AddLootToObject);
 	lua_register(state, "ShowLootWindow", EQ2Emu_lua_ShowLootWindow);
 	lua_register(state, "AddPrimaryEntityCommandAllSpawns", EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns);
 	lua_register(state, "InstructionWindow", EQ2Emu_lua_InstructionWindow);
@@ -1258,6 +1259,9 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "SetInfoStructFloat", EQ2Emu_lua_SetInfoStructFloat);
 	
 	lua_register(state, "SetCharSheetChanged", EQ2Emu_lua_SetCharSheetChanged);
+	
+	lua_register(state, "AddPlayerMail", EQ2Emu_lua_AddPlayerMail);
+	lua_register(state, "AddPlayerMailByCharID", EQ2Emu_lua_AddPlayerMailByCharID);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 8 - 0
EQ2/source/WorldServer/NPC_AI.cpp

@@ -196,6 +196,14 @@ void Brain::AddHate(Entity* entity, sint32 hate) {
 	if (m_body->IsNPC() && ((NPC*)m_body)->m_runningBack)
 		return;
 
+	if(m_body->IsImmune(IMMUNITY_TYPE_TAUNT))
+	{
+		LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is immune to taunt from entity %s.", m_body->GetName(), entity ? entity->GetName() : "(null)");
+		if(entity && entity->IsPlayer())
+			((Player*)entity)->GetClient()->GetCurrentZone()->SendDamagePacket((Spawn*)entity, (Spawn*)m_body, DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG, DAMAGE_PACKET_RESULT_IMMUNE, 0, 0, "Hate");
+		return;
+	}
+	
 	// Lock the hate list, we are altering the list so use write lock
 	MHateList.writelock(__FUNCTION__, __LINE__);
 

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

@@ -353,7 +353,7 @@ PacketStruct* PlayerInfo::serialize2(int16 version){
 		packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2());
 		packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3());
 		packet->setDataByName("level", info_struct->get_level());
-		packet->setDataByName("effective_level", info_struct->get_level());
+		packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level());
 		packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level());
 		packet->setDataByName("account_age_base", info_struct->get_account_age_base());
 
@@ -654,7 +654,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2());
 		packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3());
 		packet->setDataByName("level", info_struct->get_level());
-		packet->setDataByName("effective_level", info_struct->get_level());
+		packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level());
 		packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level());
 		//		packet->setDataByName("unknown_1_2_MJ", 98);  //unknown_1_2_MJ
 		packet->setDataByName("account_age_base", info_struct->get_account_age_base());
@@ -699,7 +699,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV 
 		packet->setDataByName("avoidance_pct", 0);//avoidance_pct 192 = 19.2% // confirmed DoV
 		packet->setDataByName("avoidance_base", info_struct->get_avoidance_base()); // confirmed DoV
-		packet->setDataByName("avoidance", 71);
+		packet->setDataByName("avoidance", info_struct->get_cur_avoidance());
 		//		packet->setDataByName("unknown_1096_1_MJ", 90);//unknown_1096_1_MJ
 		packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV
 		//		packet->setDataByName("unknown_1096_2_MJ", 89);//unknown_1096_2_MJ

+ 3 - 2
EQ2/source/WorldServer/Spawn.cpp

@@ -2278,8 +2278,9 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	if (!IsPlayer())
 		packet->setDataByName("npc", 1);
 	if (GetMerchantID() > 0)
-		packet->setDataByName("merchant", 1);		
-	packet->setDataByName("effective_level", (int8)GetLevel());
+		packet->setDataByName("merchant", 1);
+		
+	packet->setDataByName("effective_level", spawn->IsEntity() && ((Entity*)spawn)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)spawn)->GetInfoStruct()->get_effective_level() : (int8)GetLevel());
 	packet->setDataByName("level", (int8)GetLevel());
 	packet->setDataByName("unknown4", (int8)GetLevel());
 	packet->setDataByName("difficulty", appearance.encounter_level); //6);

+ 21 - 5
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -4137,9 +4137,15 @@ void WorldDatabase::SavePlayerMail(Mail* mail) {
 	Query query_update;
 	Query query_insert;
 	if (mail) {
-		query_update.AddQueryAsync(mail->player_to_id, this, Q_UPDATE, "UPDATE `character_mail` SET `already_read`=%u, `coin_copper`=%u, `coin_silver`=%u, `coin_gold`=%u, `coin_plat`=%u WHERE `id`=%u", mail->already_read, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->mail_id);
-		if (query_update.GetAffectedRows() == 0)
-			query_insert.AddQueryAsync(mail->player_to_id, this, Q_UPDATE, "INSERT INTO `character_mail` (`player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time`) VALUES (%u, '%s', '%s', '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u)", mail->player_to_id, mail->player_from.c_str(), getSafeEscapeString(mail->subject.c_str()).c_str(), getSafeEscapeString(mail->mail_body.c_str()).c_str(), mail->already_read, mail->mail_type, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->stack, mail->postage_cost, mail->attachment_cost, mail->char_item_id, mail->time_sent, mail->expire_time);
+		if(mail->mail_id > 0)
+			query_update.RunQuery2(Q_UPDATE, "UPDATE `character_mail` SET `already_read`=%u, `coin_copper`=%u, `coin_silver`=%u, `coin_gold`=%u, `coin_plat`=%u WHERE `id`=%u", mail->already_read, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->mail_id);
+		if (mail->mail_id == 0 || query_update.GetAffectedRows() == 0)
+		{
+			query_insert.RunQuery2(Q_INSERT, "INSERT INTO `character_mail` (`player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time`) VALUES (%u, '%s', '%s', '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u)", mail->player_to_id, mail->player_from.c_str(), getSafeEscapeString(mail->subject.c_str()).c_str(), getSafeEscapeString(mail->mail_body.c_str()).c_str(), mail->already_read, mail->mail_type, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->stack, mail->postage_cost, mail->attachment_cost, mail->char_item_id, mail->time_sent, mail->expire_time);
+			
+			if(!mail->mail_id)
+				mail->mail_id = query_insert.GetLastInsertedID();
+		}
 		mail->save_needed = false;
 	}
 }
@@ -4167,8 +4173,15 @@ void WorldDatabase::LoadPlayerMail(Client* client, bool new_only) {
 			result = query.RunQuery2(Q_SELECT, "SELECT `id`, `player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time` FROM `character_mail` WHERE `player_to_id`=%u", client->GetCharacterID());
 		if (result && mysql_num_rows(result) > 0) {
 			MYSQL_ROW row;
-			client->SimpleMessage(CHANNEL_NARRATIVE, "You've got mail! :)");
+			bool hasMail = false;
 			while (result && (row = mysql_fetch_row(result))) {
+
+				int32 time_sent = atoul(row[15]);
+				if ( time_sent > Timer::GetUnixTimeStamp() )
+					continue; // should have not been received yet
+
+
+				hasMail = true;
 				Mail* mail = new Mail;
 				mail->mail_id = atoul(row[0]);
 				mail->player_to_id = atoul(row[1]);
@@ -4186,7 +4199,7 @@ void WorldDatabase::LoadPlayerMail(Client* client, bool new_only) {
 				mail->postage_cost = atoul(row[12]);
 				mail->attachment_cost = atoul(row[13]);
 				mail->char_item_id = atoul(row[14]);
-				mail->time_sent = atoul(row[15]);
+				mail->time_sent = time_sent;
 				mail->expire_time = atoul(row[16]);
 				mail->save_needed = false;
 				client->GetPlayer()->AddMail(mail);
@@ -4194,6 +4207,9 @@ void WorldDatabase::LoadPlayerMail(Client* client, bool new_only) {
 				LogWrite(PLAYER__DEBUG, 5, "Player", "Loaded Mail ID %i, to: %i, from: %s", atoul(row[0]), atoul(row[1]), string(row[2]).c_str());
 
 			}
+			
+			if(hasMail)
+				client->SimpleMessage(CHANNEL_NARRATIVE, "You've got mail! :)");
 		}
 	}
 }

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

@@ -5895,22 +5895,22 @@ void Client::DisplayConversation(Item* item, vector<ConversationOption>* convers
 
 }
 
-void Client::DisplayConversation(Entity* npc, int8 type, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2) {
-	if (!npc || !(type == 1 || type == 2 || type == 3) || !text /*|| !conversations || conversations->size() == 0*/) {
+void Client::DisplayConversation(Spawn* src, int8 type, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2) {
+	if (!src || !(type == 1 || type == 2 || type == 3) || !text /*|| !conversations || conversations->size() == 0*/) {
 		return;
 	}
-	int32 conversation_id = GetConversationID(npc, 0);
+	int32 conversation_id = GetConversationID(src, 0);
 	if (conversation_id == 0) {
 		next_conversation_id++;
 		conversation_id = next_conversation_id;
 	}
-	conversation_spawns[conversation_id] = npc;
+	conversation_spawns[conversation_id] = src;
 
-	/* NPCs can start two different types of conversations.
+	/* Spawns can start two different types of conversations.
 	 * Type 1: The chat type with bubbles.
 	 * Type 2: The dialog type with the blue box. */
 	if (type == 1)
-		DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(npc), conversations, text, mp3, key1, key2);
+		DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(src), conversations, text, mp3, key1, key2);
 	else if (type == 2)
 		DisplayConversation(conversation_id, 0xFFFFFFFF, conversations, text, mp3, key1, key2);
 	else //if (type == 3)
@@ -7307,7 +7307,11 @@ void Client::SendMailList() {
 				p->setArrayDataByName("subject", mail->subject.c_str(), i);
 				p->setArrayDataByName("unknown1", 0x0000, i);
 				p->setArrayDataByName("already_read", mail->already_read, i);
-				p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i);
+				if(mail->expire_time)
+					p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i);
+				else
+					p->setArrayDataByName("mail_deletion", 0, i);
+				
 				p->setArrayDataByName("mail_type", mail->mail_type, i);
 				p->setArrayDataByName("mail_expire", 0xFFFFFFFF, i);
 				p->setArrayDataByName("unknown1a", 0xFFFFFFFF, i);
@@ -7393,6 +7397,7 @@ void Client::DisplayMailMessage(int32 mail_id) {
 				mail->save_needed = true;
 				QueuePacket(packet->serialize());
 				safe_delete(packet);
+				// trying to update this causes the window not to open
 				//SendMailList();
 			}
 		}
@@ -9843,3 +9848,58 @@ void Client::TempRemoveGroup()
 		world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
 	}
 }
+
+void Client::CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, 
+int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time)
+{
+	Mail mail;
+	memset(&mail,0,sizeof(Mail));
+	mail.player_to_id = charID;
+	mail.player_from = fromName;
+	mail.subject = subjectName;
+	mail.mail_body = mailBody;
+
+	mail.mail_type = mailType;
+
+	mail.coin_copper = copper;
+	mail.coin_silver = silver;
+	mail.coin_gold = gold;
+	mail.coin_plat = platinum;
+
+	mail.char_item_id = item_id;
+	mail.stack = stack_size;
+
+	mail.time_sent = time_sent;
+	mail.expire_time = expire_time;
+
+	database.SavePlayerMail(&mail);	
+}
+
+void Client::CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, 
+int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time)
+{
+	Mail* mail = new Mail();
+	mail->player_to_id = GetCharacterID();
+	mail->player_from = fromName;
+	mail->subject = subjectName;
+	mail->mail_body = mailBody;
+
+	mail->mail_type = mailType;
+
+	mail->coin_copper = copper;
+	mail->coin_silver = silver;
+	mail->coin_gold = gold;
+	mail->coin_plat = platinum;
+
+	mail->char_item_id = item_id;
+	mail->stack = stack_size;
+
+	mail->time_sent = time_sent;
+	mail->expire_time = expire_time;
+
+	mail->postage_cost = 0;
+	mail->save_needed = 1;
+	mail->already_read = 0;
+	database.SavePlayerMail(mail);
+	GetPlayer()->AddMail(mail);
+}

+ 7 - 1
EQ2/source/WorldServer/client.h

@@ -279,7 +279,7 @@ public:
 	Quest*	GetActiveQuest(int32 quest_id);
 	void	DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2);
 	void	DisplayConversation(Item* item, vector<ConversationOption>* conversations, const char* text, int8 type, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0);
-	void	DisplayConversation(Entity* npc, int8 type, vector<ConversationOption>* conversations, const char* text, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0);
+	void	DisplayConversation(Spawn* src, int8 type, vector<ConversationOption>* conversations, const char* text, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0);
 	void	CloseDialog(int32 conversation_id);
 	int32	GetConversationID(Spawn* spawn, Item* item);
 	void	CombineSpawns(float radius, Spawn* spawn);
@@ -460,6 +460,12 @@ public:
 	bool ShowPathToTarget(Spawn* spawn);
 
 	void SetRegionDebug(bool val) { regionDebugMessaging = val; }
+
+	static void CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, 
+		int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time);
+	void CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, 
+		int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time);
+
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);