Browse Source

- Fix #496 ITEM_STAT_ABILITYCASTINGSPEED (664) and ITEM_STAT_SPELLREUSESPEED (665) now supported
- Fix #109 Soulrend does not knock down target (finish spell cast, ZoneServer::SendCastSpellPacket spell_visual is disabled) when no damage applied
* alter table character_spell_effects add column has_damaged tinyint(3) unsigned not null default 0 after resisted;

- Fix #536, SpellDamage now can drain power. Also Fixed AoM and DoF client WS_HearSiphonSpellDamage
- SpellDamage LUA Function now returns a boolean whether damage is dealt (or spell resisted) -- (true is damage/false is no damage or resisted). See Spells/Fighter/Crusader/Shadowknight/Soulrend.lua for a sample.
- DamageSpawn LUA Function now returns a boolean whether damage is dealt, updated to allow take_power argument DamageSpawn(Attacker, Victim, victim, type, dmg_type, low_dmg, high_dmg, spell_name, crit_mod, is_tick, no_calcs, ignore_attacker, take_power)
- new LUA Functions (both can be used in and outside of a LUA Spell):
* SpellDamageExt(Target, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power, class_id_reqs...) -- extends support for take_power field (SpellDamage function does not have this and would break other potential spells)
* SendHearCast(Spawn, spell_visual_id, cast_time, Caster, Target) -- lets the Spawn see a spell visual on Target. If Caster is not defined, we use Spawn, same goes for Target.
- Fixed WS_HearHeal struct for DoF client (displays critically heal vs heal) and proper spell name. DoF does not support absorb or other types.
- Support for translation of spell_visual (spells table) aka spellcast.dat from assets vpl.

CREATE TABLE `spell_visuals` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL,
`alternate_spell_visual` varchar(128) DEFAULT NOT NULL '',
`spell_visual_id` int(10) unsigned NOT NULL DEFAULT 0,
`min_version_range` int(10) unsigned NOT NULL DEFAULT 0,
`max_version_range` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
** MAKE SURE TO GET spell_visuals sql included with update inserted!

- Fixed right-click inventory examine (again?) - tested and it is working for main invetory, bags and items in bags!
- DoF bags support up to 36 slots now instead of the restricted 20 for "classic" client
- DoF and classic equipment restricts to 22 slots instead of trying to send client 25 slots (the additional do not exist)
- Fix crash on signs due to lack of nullptr check on entity_command
- SetInfoStructString / GetInfoStructString now supports combat_action_state -- can be used without overriding action_state outside of combat.

Emagi 5 months ago
parent
commit
3944d57579

File diff suppressed because it is too large
+ 41 - 0
DB/updates/spell_visuals_nov12_2023.sql


+ 2 - 2
EQ2/source/WorldServer/Bots/BotBrain.cpp

@@ -147,7 +147,7 @@ bool BotBrain::ProcessSpell(Entity* target, float distance) {
 			Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
 			m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
 			// recast time
-			int32 time = Timer::GetCurrentTime2() + (spell->GetSpellData()->recast * 1000);
+			int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
 			Body->SetRecast(spell, time);
 
 			string str = "I am casting ";
@@ -191,7 +191,7 @@ bool BotBrain::ProcessOutOfCombatSpells() {
 			Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
 			m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
 			// recast time
-			int32 time = Timer::GetCurrentTime2() + (spell->GetSpellData()->recast * 1000);
+			int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
 			Body->SetRecast(spell, time);
 
 			string str = "I am casting ";

+ 43 - 14
EQ2/source/WorldServer/Combat.cpp

@@ -390,13 +390,18 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
 		SetAttackDelay(false, true);
 }
 
-bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs){
+bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){
 	if(!victim || !luaspell || !luaspell->spell)
 		return false;
 
 	Spell* spell = luaspell->spell;
 	Skill* skill = nullptr;
-
+	int8 packet_type = DAMAGE_PACKET_TYPE_SPELL_DAMAGE;
+	
+	if(override_packet_type) {
+		packet_type = override_packet_type;
+	}
+	
 	int8 hit_result = 0;
 	bool is_tick = false; // if spell is already active, this is a tick
 	if (GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell)){
@@ -404,14 +409,14 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 		is_tick = true;
 	}
 	else if(spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART)
-		hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, false);
+		hit_result = DetermineHit(victim, packet_type, damage_type, 0, false, luaspell);
 	else
-		hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, true, luaspell);
+		hit_result = DetermineHit(victim, packet_type, damage_type, 0, true, luaspell);
 	
 	if(victim->IsEntity()) {
 		CheckEncounterState((Entity*)victim);
 	}
-	
+	bool successful_hit = true;
 	if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) {
 		luaspell->last_spellattack_hit = true;
 		//If this spell is a tick and has already crit, force the tick to crit
@@ -421,8 +426,8 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 			else
 				crit_mod = 2;
 		}
-		if(DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, luaspell) && !luaspell->crit)
-			luaspell->crit = true;
+		DamageSpawn((Entity*)victim, packet_type, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, false, take_power, luaspell);
+		
 		CheckProcs(PROC_TYPE_OFFENSIVE, victim);
 		CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim);
 
@@ -447,6 +452,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 		}
 	}
 	else {
+		successful_hit = false;
 		if(hit_result == DAMAGE_PACKET_RESULT_RESIST)
 			luaspell->resisted = true;
 		if(victim->IsNPC())
@@ -518,7 +524,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
 		}
 	}
 
-	return true;
+	return successful_hit;
 }
 
 bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg) {
@@ -703,6 +709,9 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
 }
 
 int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell){
+	if(lua_spell) {
+		lua_spell->is_damage_spell = true;
+	}
 	if(!victim) {
 		return DAMAGE_PACKET_RESULT_MISS;
 	}
@@ -957,10 +966,16 @@ Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) {
 	return 0;
 }
 
-bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, LuaSpell* spell) {
+bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell) {
+	if(spell) {
+		spell->is_damage_spell = true;
+	}
+	
+	bool has_damaged = false;
+	
 	if(!victim || !victim->Alive() || victim->GetHP() == 0)
 		return false;
-
+	
 	int8 hit_result = 0;
 	int16 blow_type = 0;
 	sint32 damage = 0;
@@ -1070,8 +1085,21 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 
 			if (damage < (sint64)prevDmg)
 				useWards = true;
-
-			victim->TakeDamage(damage);
+			if(damage > 0 && spell) {
+				has_damaged = true;
+				spell->has_damaged = true;
+			}
+			if(take_power) {
+				sint32 curPower = victim->GetPower();
+				if(curPower < damage)
+					curPower = 0;
+				else
+					curPower -= damage;
+				victim->SetPower(curPower);
+			}
+			else {
+				victim->TakeDamage(damage);
+			}
 			victim->CheckProcs(PROC_TYPE_DAMAGED, this);
 
 			if (IsPlayer()) {
@@ -1124,8 +1152,9 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
 		else
 			victim->CheckProcs(PROC_TYPE_PHYSICAL_DEFENSIVE, this);
 	}
-	
-	return crit;
+	if(spell)
+		spell->crit = crit;
+	return has_damaged;
 }
 
 float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) {

+ 6 - 20
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -10628,26 +10628,8 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 			World::newValue = strtoull(sep->arg[1], NULL, 0);
 		}
 		else if (atoi(sep->arg[0]) == 29 && sep->IsNumber(1)) {
-			PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
-			if (packet) {
-				int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
-				int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
-				
-				packet->setDataByName("spawn_id", caster_id);
-				packet->setArrayLengthByName("num_targets", 1);
-				packet->setArrayDataByName("target", target_id);
-				packet->setDataByName("num_targets", 1);
-				packet->setDataByName("spell_visual", strtoull(sep->arg[1], NULL, 0)); //result
-				packet->setDataByName("cast_time", sep->IsNumber(2) ? strtof(sep->arg[2], NULL)*.01f : 2500); //delay
-				packet->setDataByName("spell_id", 1);
-				packet->setDataByName("spell_level", 1);
-				packet->setDataByName("spell_tier", 1);
-				EQ2Packet* outapp = packet->serialize();
-				
-				DumpPacket(outapp);
-				client->QueuePacket(outapp);
-				safe_delete(packet);
-			}
+			client->SendHearCast(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(),
+								 strtoull(sep->arg[1], NULL, 0), atoul(sep->arg[2]));
 		}
 		else if (atoi(sep->arg[0]) == 30) {
 			PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", client->GetVersion());
@@ -10677,6 +10659,10 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 				ClientPacketFunctions::SendServerControlFlags(client, param1, param2, paramval);
 			}
 		}
+		else if (atoi(sep->arg[0]) == 33 && sep->IsNumber(1) && sep->IsNumber(2)) {
+			client->GetCurrentZone()->SendHealPacket(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(),
+													 atoul(sep->arg[1]), atoul(sep->arg[2]), "TestSpell");
+		}
 	}
 	else {
 			PacketStruct* packet2 = configReader.getStruct("WS_ExamineSpellInfo", client->GetVersion());

+ 4 - 2
EQ2/source/WorldServer/Entity.cpp

@@ -346,6 +346,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &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);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -537,6 +538,7 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &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);
 
 }
 
@@ -1335,7 +1337,7 @@ void Entity::CalculateBonuses(){
 	info->set_potency(0);
 	info->set_hate_mod(0);
 	info->set_reuse_speed(0);
-	info->set_casting_speed(0);
+//	info->set_casting_speed(0);
 	info->set_recovery_speed(0);
 	info->set_spell_reuse_speed(0);
 	info->set_spell_multi_attack(0);
@@ -2161,7 +2163,7 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 			}
 
 			if (attacker && spell->caster)
-				attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, spell);
+				attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, false, spell);
 		}
 
 		bool shouldRemoveSpell = false;

+ 13 - 7
EQ2/source/WorldServer/Entity.h

@@ -471,6 +471,7 @@ struct InfoStruct{
 		reload_player_spells_ = oldStruct->get_reload_player_spells();
 		
 		action_state_ = oldStruct->get_action_state();
+		combat_action_state_ = oldStruct->get_combat_action_state();
 
 	}
 	//mutable std::shared_mutex mutex_;
@@ -681,6 +682,8 @@ struct InfoStruct{
 	
 	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_; }
+	
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
 	void	set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@@ -883,11 +886,11 @@ struct InfoStruct{
 	void	add_crit_bonus(float value) { std::lock_guard<std::mutex> lk(classMutex); if(crit_bonus_ + value < 0.0f) crit_bonus_ = 0.0f; else crit_bonus_ += value; }
 	void	add_potency(float value) { std::lock_guard<std::mutex> lk(classMutex); if(potency_ + value < 0.0f) potency_ = 0.0f; else potency_ += value; }
 	void	add_hate_mod(float value) { std::lock_guard<std::mutex> lk(classMutex); if(hate_mod_ + value < 0.0f) hate_mod_ = 0.0f; else hate_mod_ += value; }
-	void	add_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(reuse_speed_ + value < 0.0f) reuse_speed_ = 0.0f; else reuse_speed_ += value; }
-	void	add_casting_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(casting_speed_ + value < 0.0f) casting_speed_ = 0.0f; else casting_speed_ += value; }
-	void	add_recovery_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(recovery_speed_ + value < 0.0f) recovery_speed_ = 0.0f; else recovery_speed_ += value; }
-	void	add_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(spell_reuse_speed_ + value < 0.0f) spell_reuse_speed_ = 0.0f; else spell_reuse_speed_ += value; }
-	void	add_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); if(spell_multi_attack_ + value < 0.0f) spell_multi_attack_ = 0.0f; else spell_multi_attack_ += value; }
+	void	add_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); reuse_speed_ += value; }
+	void	add_casting_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); casting_speed_ += value; }
+	void	add_recovery_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); recovery_speed_ += value; }
+	void	add_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_reuse_speed_ += value; }
+	void	add_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_multi_attack_ += value; }
 	void	add_dps(float value) { std::lock_guard<std::mutex> lk(classMutex); if(dps_ + value < 0.0f) dps_ = 0.0f; else dps_ += value; }
 	void	add_dps_multiplier(float value) { std::lock_guard<std::mutex> lk(classMutex); if(dps_multiplier_ + value < 0.0f) dps_multiplier_ = 0.0f; else dps_multiplier_ += value; }
 	void	add_attackspeed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(attackspeed_ + value < 0.0f) attackspeed_ = 0.0f; else attackspeed_ += value; }
@@ -975,6 +978,8 @@ struct InfoStruct{
 
 	void	set_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); action_state_ = value; }
 	
+	void	set_combat_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); combat_action_state_ = value; }
+	
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -1183,6 +1188,7 @@ private:
 	int8			reload_player_spells_;
 	
 	std::string		action_state_;
+	std::string		combat_action_state_;
 	
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
@@ -1424,13 +1430,13 @@ public:
 	bool			RangeWeaponReady();
 	void			MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false);
 	void			RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false);
-	bool			SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false);
+	bool			SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false);
 	bool			ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg);
 	bool            SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name="");
 	int8			DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr);
 	float			GetDamageTypeResistPercentage(int8 damage_type);
 	Skill*			GetSkillByWeaponType(int8 type, int8 damage_type, bool update);
-	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, LuaSpell* spell = 0);
+	bool			DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0);
 	float			CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false);
 	void			AddHate(Entity* attacker, sint32 hate);
 	bool			CheckInterruptSpell(Entity* attacker);

+ 30 - 28
EQ2/source/WorldServer/Items/Items.cpp

@@ -2098,7 +2098,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 					tradeskill_class_levels[i] = tmp_level;
 			}
 		}
-		bool simplified_display = false;
 		if (client->GetVersion() <= 546) { //simplify display (if possible)
 			map<int8, int16> new_adv_class_levels;
 			for (int i = 1; i <= 31; i += 10) {
@@ -2115,7 +2114,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 				}
 			}
 			if (new_adv_class_levels.size() > 0) {
-				simplified_display = true;
 				int8 i = 0;
 				for (itr = new_adv_class_levels.begin(); itr != new_adv_class_levels.end(); itr++) {
 					i = itr->first;
@@ -2302,9 +2300,11 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 			}
 			case ITEM_TYPE_BAG:{
 				if(bag_info){
+					
+					int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
+					if (bag_info->num_slots > max_slots)
+						bag_info->num_slots = max_slots;
 					if (client->GetVersion() <= 546) {
-						if (bag_info->num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
-							bag_info->num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
 						packet->setSubstructDataByName("details", "num_slots", bag_info->num_slots);
 						packet->setSubstructDataByName("details", "weight_reduction", bag_info->weight_reduction);
 					}
@@ -2375,12 +2375,14 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 							packet->setSubstructDataByName("header_info", "footer_type", 0);
 							
 							spell->SetPacketInformation(packet, player->GetZone()->GetClientBySpawn(player));
-							if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true))
+							if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) {
 								packet->setDataByName("scribed", 1);
+							}
 
 								if (packet->GetVersion() >= 927){
-									if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true))
+									if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) {
 										packet->setAddToPacketByName("scribed_better_version", 1);// need to confirm
+									}
 								}
 								else
 									packet->setAddToPacketByName("scribed_better_version", 0); //if not scribed
@@ -2576,7 +2578,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 	packet->setSubstructDataByName("footer", "description", description.c_str());
 
 	LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__);
-
 #if EQDEBUG >= 9
 	packet->PrintPacket();
 #endif
@@ -3547,9 +3548,12 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 		menu_data -= ITEM_MENU_TYPE_GENERIC;
 
 	if (item->details.num_slots > 0) {
-		if (packet->GetVersion() <= 546 && item->details.num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
-			item->details.num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
+		int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
+		if (item->details.num_slots > max_slots)
+			item->details.num_slots = max_slots;
+		
 		menu_data += ITEM_MENU_TYPE_BAG;
+		
 		if (item->details.num_free_slots == item->details.num_slots)
 			menu_data += ITEM_MENU_TYPE_EMPTY_BAG;
 	}
@@ -3653,24 +3657,24 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 		packet->setSubstructArrayDataByName("items", "unique_id", item->details.item_id, 0, i);
 	else
 		packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i);
+	packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i);
 	packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i);
 	packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
-	if (overflow) {
+	if (overflow)
 		packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i);
-	}
 	else {
-		
-		if(i < 6) {
-			packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id ? item->details.bag_id : i, 0, i);
-			packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
-			
+		if(packet->GetVersion() <= 546) {
+				/* DoF client and earlier side automatically assigns indexes
+				** we have to send 0xFF or else all index is set to 255 on client
+				** and then examine inventory won't work */
+				packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
 		}
 		else {
-			packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i);
-			packet->setSubstructArrayDataByName("items", "index", i, 0, i);
+				packet->setSubstructArrayDataByName("items", "index", i, 0, i);
 		}
-		item->details.index = i;
 	}
+	item->details.index = i;
+	
 	packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
 	packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i); // inventory doesn't convert slots
 	if (client->GetVersion() <= 1208) {
@@ -3692,11 +3696,6 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 	packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i);
 	//need broker id
 	packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i);
-
-	
-		
-
-
 	
 }
 
@@ -4147,7 +4146,7 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 		PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version);
 		packet_size = packet2->GetTotalPacketSize();
 		safe_delete(packet2);
-		int8 num_slots = NUM_SLOTS;
+		int8 num_slots = player->GetNumSlotsEquip(version);
 		packet->setArrayLengthByName("item_count", num_slots);
 		if(!orig_packet){
 			xor_packet = new uchar[packet_size* num_slots];
@@ -4161,7 +4160,7 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 
 		int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0;
 
-		for(int16 i=0;i<NUM_SLOTS;i++){
+		for(int16 i=0;i<num_slots;i++){
 			// override the item slot we currently check as the client has different ordering, we need to match it
 			int16 itemIdx = player->ConvertSlotFromClient(i, version);
 			
@@ -4171,9 +4170,12 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
 				if(item->slot_data.size() > 0)
 					menu_data -= ITEM_MENU_TYPE_GENERIC;
 				if (item->details.num_slots > 0) {
-					if (packet->GetVersion() <= 546 && item->details.num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
-						item->details.num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
+					int8 max_slots = player->GetMaxBagSlots(version);
+					if (item->details.num_slots > max_slots)
+						item->details.num_slots = max_slots;
+					
 					menu_data += ITEM_MENU_TYPE_BAG;
+					
 					if (item->details.num_free_slots == item->details.num_slots)
 						menu_data += ITEM_MENU_TYPE_EMPTY_BAG;
 				}

+ 4 - 3
EQ2/source/WorldServer/Items/Items.h

@@ -24,7 +24,6 @@
 #include <ctime>
 #include "../../common/types.h"
 #include "../../common/DataBuffer.h"
-#include "../../common/MiscFunctions.h"
 #include "../Commands/Commands.h"
 #include "../../common/ConfigReader.h"
 
@@ -111,8 +110,10 @@ extern MasterItemList master_item_list;
 #define DOF_DRINK_SLOT 2097152
 
 #define CLASSIC_EQ_MAX_BAG_SLOTS 20
+#define DOF_EQ_MAX_BAG_SLOTS 36
 #define NUM_BANK_SLOTS 12
 #define NUM_SHARED_BANK_SLOTS 8
+#define CLASSIC_NUM_SLOTS 22
 #define NUM_SLOTS 25
 #define NUM_INV_SLOTS 6
 #define INV_SLOT1 0
@@ -204,8 +205,8 @@ extern MasterItemList master_item_list;
 
 
 
-#define ITEM_MENU_TYPE_GENERIC			1 //0
-#define ITEM_MENU_TYPE_EQUIP			2 //1
+#define ITEM_MENU_TYPE_GENERIC			1 //0 (NON_EQUIPABLE)
+#define ITEM_MENU_TYPE_EQUIP			2 //1 (This is SLOT_FULL for classic)
 #define ITEM_MENU_TYPE_BAG				4//2
 #define ITEM_MENU_TYPE_HOUSE			8 //3 Place
 #define ITEM_MENU_TYPE_EMPTY_BAG		16	//4

+ 157 - 8
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1854,7 +1854,8 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
 	if(!luaspell || luaspell->resisted) {
 		lua_interface->ResetFunctionStack(state);
-		return 0;
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
 	}
 	Spawn* caster = luaspell->caster;
 	sint32 type = lua_interface->GetSInt32Value(state, 2);
@@ -1936,6 +1937,7 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 					success = true;
 			}
 		}
+		lua_interface->SetBooleanValue(state, luaspell->has_damaged);
 		if (success) {
 			Spell* spell = luaspell->spell;
 			if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art
@@ -1945,7 +1947,118 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 			}
 		}
 	}
-	return 0;
+	else {
+		lua_interface->SetBooleanValue(state, false);
+	}
+	return 1;
+}
+
+int EQ2Emu_lua_SpellDamageExt(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* target = lua_interface->GetSpawn(state);
+	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	if(!luaspell || luaspell->resisted) {
+		lua_interface->ResetFunctionStack(state);
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+	}
+	Spawn* caster = luaspell->caster;
+	sint32 type = lua_interface->GetSInt32Value(state, 2);
+	int32 min_damage = lua_interface->GetInt32Value(state, 3);
+	int32 max_damage = lua_interface->GetInt32Value(state, 4);
+	int8 crit_mod = lua_interface->GetInt32Value(state, 5);
+	bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1;
+	int32 override_packet_type = lua_interface->GetInt32Value(state, 7);
+	bool take_power = lua_interface->GetInt32Value(state, 8) == 1;
+	//lua_interface->ResetFunctionStack(state);
+	int32 class_id = lua_interface->GetInt32Value(state, 9);
+	vector<int16> faction_req;
+	vector<int16> race_req;
+	int32 class_req = 0;
+	int32 i = 0;
+	int8 f = 0;
+	int8 r = 0;
+	while ((class_id = lua_interface->GetInt32Value(state, 9 + i))) {
+		if (class_id < 100) {
+			class_req += pow(2.0, double(class_id - 1));
+		}
+		else if (class_id > 100 && class_id < 1000) {
+			race_req.push_back(class_id);
+			r++;
+		}
+		else {
+			faction_req.push_back(class_id);
+			f++;
+		}
+		i++;
+	}
+	lua_interface->ResetFunctionStack(state);
+	if (caster && caster->IsEntity()) {
+		bool race_match = false;
+		bool success = false;
+		luaspell->resisted = false;
+		if (luaspell->targets.size() > 0) {
+			ZoneServer* zone = luaspell->caster->GetZone();
+			Spawn* target = 0;
+			luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
+			for (int32 i = 0; i < luaspell->targets.size(); i++) {
+				if ((target = zone->GetSpawnByID(luaspell->targets[i]))) {
+
+					if (race_req.size() > 0) {
+						for (int8 i = 0; i < race_req.size(); i++) {
+						if(race_req[i] == target->GetRace() ||
+							race_req[i] == race_types_list.GetRaceType(target->GetModelType()) ||
+							race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) {
+								race_match = true;
+							}
+						}
+					}
+					else
+						race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true
+					if (race_match == true) {
+						float distance = caster->GetDistance(target, true);
+						((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power);
+					}
+				}
+			}
+			success = true;
+			luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
+		}
+		else if (target) {
+
+			//check class and race/faction here
+			if (race_req.size() > 0) {
+				for (int8 i = 0; i < race_req.size(); i++) {
+					if(race_req[i] == target->GetRace() ||
+						race_req[i] == race_types_list.GetRaceType(target->GetModelType()) ||
+						race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) {
+						race_match = true;
+					}
+				}
+			}
+			else
+				race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true
+			if (race_match == true) {
+				float distance = caster->GetDistance(target, true);
+				if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power))
+					success = true;
+			}
+		}
+		lua_interface->SetBooleanValue(state, luaspell->has_damaged);
+		if (success) {
+			Spell* spell = luaspell->spell;
+			if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art
+				((Player*)caster)->InCombat(true);
+				if (caster->GetZone())
+					caster->GetZone()->TriggerCharSheetTimer();
+			}
+		}
+	}
+	else {
+		lua_interface->SetBooleanValue(state, false);
+	}
+	return 1;
 }
 int EQ2Emu_lua_ModifyPower(lua_State* state) {
 	if (!lua_interface)
@@ -12424,30 +12537,39 @@ int EQ2Emu_lua_DamageSpawn(lua_State* state) {
 	bool is_tick = (lua_interface->GetInt8Value(state, 9) == 1);
 	bool no_calcs = (lua_interface->GetInt8Value(state, 10) == 1);
 	bool ignore_attacker = (lua_interface->GetInt8Value(state, 11) == 1);
+	bool take_power = (lua_interface->GetInt8Value(state, 12) == 1);
 
 	lua_interface->ResetFunctionStack(state);
 	if (!attacker) {
 		lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state));
-		return 0;
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+
 	}
 
 	if (!attacker->IsEntity()) {
 		lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state));
-		return 0;
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+
 	}
 
 	if (!victim) {
 		lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state));
-		return 0;
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
+
 	}
 
 	if (!victim->IsEntity()) {
 		lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state));
-		return 0;
+		lua_interface->SetBooleanValue(state, false);
+		return 1;
 	}
 
-	((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker);
-	return 0;
+	bool has_damaged = ((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker, take_power);
+	lua_interface->SetBooleanValue(state, has_damaged);
+	return 1;
 }
 
 int EQ2Emu_lua_IsInvulnerable(lua_State* state) {
@@ -13878,3 +14000,30 @@ int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state) {
 	lua_interface->SetBooleanValue(state, true);
 	return 1;
 }
+
+int EQ2Emu_lua_SendHearCast(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int32 spell_visual_id = lua_interface->GetInt32Value(state, 2);
+	int16 cast_time = lua_interface->GetInt16Value(state, 3);
+	Spawn* caster = lua_interface->GetSpawn(state, 4);
+	Spawn* target = lua_interface->GetSpawn(state, 5);
+	lua_interface->ResetFunctionStack(state);
+	if(spell && spawn && spawn->IsEntity()) {
+		ZoneServer* zone = spawn->GetZone();
+		if(zone) {
+			zone->SendCastSpellPacket(spell, caster && caster->IsEntity() ? (Entity*)caster : (Entity*)spawn, spell_visual_id, cast_time > 0 ? cast_time : 0xFFFF);
+		}
+	}
+	else if (spawn) {
+		if (spawn->IsPlayer()) {
+			Client* client = ((Player*)spawn)->GetClient();
+			if (client) {
+				client->SendHearCast(caster ? caster : client->GetPlayer(), target ? target : client->GetPlayer(), spell_visual_id, cast_time);
+			}
+		}
+	}
+	return 0;
+}

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

@@ -183,6 +183,7 @@ int EQ2Emu_lua_Spawn(lua_State* state);
 int EQ2Emu_lua_AddSpawnAccess(lua_State* state);
 int EQ2Emu_lua_CastSpell(lua_State* state);
 int EQ2Emu_lua_SpellDamage(lua_State* state);
+int EQ2Emu_lua_SpellDamageExt(lua_State* state);
 int EQ2Emu_lua_FaceTarget(lua_State* state);
 int EQ2Emu_lua_MoveToLocation(lua_State* state);
 int EQ2Emu_lua_ClearRunningLocations(lua_State* state);
@@ -646,4 +647,6 @@ int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state);
 int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state);
 int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state);
 int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state);
+
+int EQ2Emu_lua_SendHearCast(lua_State* state);
 #endif

+ 7 - 0
EQ2/source/WorldServer/LuaInterface.cpp

@@ -267,6 +267,8 @@ bool LuaInterface::LoadLuaSpell(const char* name) {
 		spell->caster = 0;
 		spell->initial_target = 0;
 		spell->resisted = false;
+		spell->has_damaged = false;
+		spell->is_damage_spell = false;
 		spell->interrupted = false;
 		spell->last_spellattack_hit = false;
 		spell->crit = false;
@@ -987,6 +989,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "GetSpeed", EQ2Emu_lua_GetSpeed);
 	lua_register(state, "HasMoved", EQ2Emu_lua_HasMoved);
 	lua_register(state, "SpellDamage", EQ2Emu_lua_SpellDamage);
+	lua_register(state, "SpellDamageExt", EQ2Emu_lua_SpellDamageExt);
 	lua_register(state, "CastSpell", EQ2Emu_lua_CastSpell);	
 	lua_register(state, "SpellHeal", EQ2Emu_lua_SpellHeal);
 	lua_register(state, "SpellHealPct", EQ2Emu_lua_SpellHealPct);
@@ -1516,6 +1519,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "ReplaceWidgetFromClient", EQ2Emu_lua_ReplaceWidgetFromClient);
 	lua_register(state, "RemoveWidgetFromSpawnMap", EQ2Emu_lua_RemoveWidgetFromSpawnMap);
 	lua_register(state, "RemoveWidgetFromZoneMap", EQ2Emu_lua_RemoveWidgetFromZoneMap);
+	
+	lua_register(state, "SendHearCast", EQ2Emu_lua_SendHearCast);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {
@@ -1980,6 +1985,8 @@ LuaSpell* LuaInterface::GetSpell(const char* name)  {
 		new_spell->timer = spell->timer;
 		new_spell->timer.Disable();
 		new_spell->resisted = false;
+		new_spell->is_damage_spell = false;
+		new_spell->has_damaged = false;
 		new_spell->interrupted = false;
 		new_spell->crit = false;
 		new_spell->last_spellattack_hit = false;

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

@@ -91,6 +91,8 @@ struct LuaSpell{
 	int8            slot_pos;
 	int32           damage_remaining;
 	bool			resisted;
+	bool			has_damaged;
+	bool			is_damage_spell;
 	bool			interrupted;
 	bool            crit;
 	bool            last_spellattack_hit;

+ 45 - 18
EQ2/source/WorldServer/Player.cpp

@@ -1396,6 +1396,25 @@ int16 Player::ConvertSlotFromClient(int8 slot, int16 version) {
 	return slot;
 }
 
+int16 Player::GetNumSlotsEquip(int16 version) {
+	if(version <= 546) {
+		return CLASSIC_NUM_SLOTS;
+	}
+	
+	return NUM_SLOTS;
+}
+
+int8 Player::GetMaxBagSlots(int16 version) {
+	if(version <= 283) {
+		return CLASSIC_EQ_MAX_BAG_SLOTS;
+	}
+	else if(version = 546) {
+		return DOF_EQ_MAX_BAG_SLOTS;
+	}
+	
+	return 255;
+}
+
 vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type, bool send_item_updates) {
 	vector<EQ2Packet*>	packets;
 	EquipmentItemList* equipList = &equipment_list;
@@ -1403,7 +1422,7 @@ vector<EQ2Packet*>	Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
 	if(appearance_type)
 		equipList = &appearance_equipment_list;
 		
-	if(index >= NUM_SLOTS) {
+	if(index >= GetNumSlotsEquip(version)) {
 		LogWrite(PLAYER__ERROR, 0, "Player", "%u index is out of range for equip items, bag_id: %i, slot: %u, version: %u, appearance: %u", index, bag_id, slot, version, appearance_type);
 		return packets;
 	}
@@ -2653,33 +2672,26 @@ vector<Spell*> Player::GetSpellBookSpellsByTimer(int32 timerID) {
 }
 
 void Player::ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
-	if (modify_recast) {
-		spell->recast = recast;
-		spell->recast_available = Timer::GetCurrentTime2()	+ (recast * 100);
-	}
+	SetSpellEntryRecast(spell, modify_recast, recast);
 	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status += value; // use set/remove spell status now
 	}
 }
+
 void Player::AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
-	if (modify_recast) {
-		spell->recast = recast;
-		spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
-	}
+	SetSpellEntryRecast(spell, modify_recast, recast);
 	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status = spell->status | value;
 	}
 }
+
 void Player::RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
-	if (modify_recast) {
-		spell->recast = recast;
-		spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
-	}
+	SetSpellEntryRecast(spell, modify_recast, recast);
 	if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
 		spell->status = spell->status & ~value;
 	}
-
 }
+
 void Player::SetSpellStatus(Spell* spell, int8 status){
 	MSpellsBook.lock();
 	vector<SpellBookEntry*>::iterator itr;
@@ -2694,6 +2706,21 @@ void Player::SetSpellStatus(Spell* spell, int8 status){
 	MSpellsBook.unlock();
 }
 
+void Player::SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast) {
+	if (modify_recast) {
+		spell->recast = recast;
+		float override_recast = static_cast<float>(recast);
+		Spell* spell_ = master_spell_list.GetSpell(spell->spell_id, spell->tier);
+		if(spell_) {
+			int32 recast_time = spell_->CalculateRecastTimer(this, override_recast);
+			spell->recast_available = Timer::GetCurrentTime2() + (recast_time / 10);
+		}
+		else {
+			spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
+		}
+	}
+}
+
 vector<SpellBookEntry*>* Player::GetSpellsSaveNeeded(){
 	vector<SpellBookEntry*>* ret = 0;
 	vector<SpellBookEntry*>::iterator itr;
@@ -6947,13 +6974,13 @@ void Player::SaveSpellEffects()
 				continue;
 			
 			savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, 
-			"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", 
+			"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", 
 			database.getSafeEscapeString(info->spell_effects[i].spell->spell->GetName()).c_str(), caster_char_id,
 			target_char_id,  0 /*no target_type for spell_effects*/, DB_TYPE_SPELLEFFECTS /* db_effect_type for spell_effects */, info->spell_effects[i].spell->spell->IsCopiedSpell() ? info->spell_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->spell_effects[i].spell_id, i, info->spell_effects[i].spell->slot_pos, 
 			info->spell_effects[i].icon, info->spell_effects[i].icon_backdrop, 0 /* no conc_used for spell_effects */, info->spell_effects[i].tier, 
 			info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), 
 			info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers,
-			info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str());
+			info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str());
 		}
 		if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
 			Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
@@ -6975,12 +7002,12 @@ void Player::SaveSpellEffects()
 			if(info->maintained_effects[i].spell->spell->GetSpellData() && !info->maintained_effects[i].spell->spell->GetSpellData()->duration_until_cancel)
 				timestamp = info->maintained_effects[i].expire_timestamp - Timer::GetCurrentTime2();
 			savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, 
-			"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", 
+			"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", 
 			database.getSafeEscapeString(info->maintained_effects[i].name).c_str(), caster_char_id, target_char_id,  info->maintained_effects[i].target_type, DB_TYPE_MAINTAINEDEFFECTS /* db_effect_type for maintained_effects */, info->maintained_effects[i].spell->spell->IsCopiedSpell() ? info->maintained_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->maintained_effects[i].spell_id, i, info->maintained_effects[i].slot_pos, 
 			info->maintained_effects[i].icon, info->maintained_effects[i].icon_backdrop, info->maintained_effects[i].conc_used, info->maintained_effects[i].tier, 
 			info->maintained_effects[i].total_time, timestamp, database.getSafeEscapeString(info->maintained_effects[i].spell->file_name.c_str()).c_str(), info->maintained_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), 
 			info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers,
-			info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str());
+			info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str());
 
 			info->maintained_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 			std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values ");

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

@@ -513,6 +513,8 @@ public:
 	vector<EQ2Packet*>	UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true);
 	int16 ConvertSlotToClient(int8 slot, int16 version);
 	int16 ConvertSlotFromClient(int8 slot, int16 version);
+	int16 GetNumSlotsEquip(int16 version);
+	int8 GetMaxBagSlots(int16 version);
 	EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype);
 	EQ2Packet*	RemoveInventoryItem(int8 bag_slot, int8 slot);
 	EQ2Packet*	SendInventoryUpdate(int16 version);
@@ -1181,6 +1183,7 @@ private:
 	void ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
 	void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
 	void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
+	void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast);
 	void InitXPTable();
 	map<int8, int32> m_levelXPReq;
 

+ 1 - 1
EQ2/source/WorldServer/Sign.cpp

@@ -301,7 +301,7 @@ void Sign::HandleUse(Client* client, string command)
 
 
 		//devn00b: Add support for marking objects
-		if (strcmp(entity_command->command.c_str(), "mark") == 0) {
+		if (entity_command && strcmp(entity_command->command.c_str(), "mark") == 0) {
 			LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateMarkReqested Sign - Command: '%s' (Should read mark)", entity_command->command.c_str());
 			int32 char_id = client->GetCharacterID();
 			database.SaveSignMark(char_id, GetWidgetID(), database.GetCharacterName(char_id), client);

+ 12 - 6
EQ2/source/WorldServer/Spawn.cpp

@@ -2392,21 +2392,27 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	else
 		packet->setDataByName("soga_model_type", sogaModelType);
 
-	if (GetTempActionState() >= 0)
-		packet->setDataByName("action_state", GetTempActionState());
-	else {
+	int16 action_state = appearance.action_state;
+	if(IsEntity()) {
+		std::string actionState = "";
+		if (GetTempActionState() >= 0) {
+			action_state = GetTempActionState();
+			actionState = ((Entity*)this)->GetInfoStruct()->get_combat_action_state();
+		}
+		else {
+			actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
+		}
+		
 		Client* client = spawn->GetClient();
-		int16 action_state = appearance.action_state;
 		if(IsEntity() && client) {
-			std::string actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
 			if(actionState.size() > 0) {
 				Emote* emote = visual_states.FindEmote(actionState, client->GetVersion());
 				if(emote != NULL)
 					action_state = emote->GetVisualState();
 			}
 		}
-		packet->setDataByName("action_state", action_state);
 	}
+	packet->setDataByName("action_state", action_state);
 	
 	bool scaredOfPlayer = false;
 	

+ 4 - 2
EQ2/source/WorldServer/Spawn.h

@@ -1091,11 +1091,13 @@ public:
 	bool	HasMovementLoop(){ return movement_loop.size() > 0; }
 	bool	HasMovementLocations() { 
 			bool hasLocations = false;
-			if (MMovementLocations)
+			if (MMovementLocations) {
 				MMovementLocations->readlock(__FUNCTION__, __LINE__);
+			}
 				hasLocations = movement_locations ? movement_locations->size() > 0 : false;
-			if (MMovementLocations)
+			if (MMovementLocations) {
 				MMovementLocations->releasereadlock(__FUNCTION__, __LINE__);
+			}
 			return hasLocations;
 	}
 

+ 18 - 15
EQ2/source/WorldServer/SpellProcess.cpp

@@ -307,10 +307,15 @@ void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_overrid
 		else
 			timer->client = 0;
 		timer->spell = spell;
-		if(timer_override == 0)
-			timer->timer = new Timer((int32)(spell->GetSpellData()->recast*1000));
-		else
-			timer->timer = new Timer((int32)(timer_override*1000));
+		int32 recast_time = spell->GetSpellData()->recast * 1000;
+		if(timer_override == 0) {
+			recast_time = spell->CalculateRecastTimer(caster);
+			timer->timer = new Timer(recast_time);
+		}
+		else {
+			recast_time = spell->CalculateRecastTimer(caster, timer_override);
+			timer->timer = new Timer(recast_time);
+		}
 		
 		timer->type_group_spell_id = spell->GetSpellData()->type_group_spell_id;
 		timer->linked_timer = spell->GetSpellData()->linked_timer;
@@ -318,11 +323,7 @@ void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_overrid
 
 		recast_timers.Add(timer);
 		if(caster->IsPlayer()){
-			if(timer_override == 0)
-				((Player*)caster)->LockSpell(spell, (int16)(spell->GetSpellData()->recast * 10));
-			else
-				((Player*)caster)->LockSpell(spell, timer_override * 10);
-
+			((Player*)caster)->LockSpell(spell, (int16)(recast_time / 100));
 			if (check_linked_timers && spell->GetSpellData()->linked_timer > 0) {
 				vector<Spell*> linkedSpells = ((Player*)caster)->GetSpellBookSpellsByTimer(spell->GetSpellData()->linked_timer);
 				for (int8 i = 0; i < linkedSpells.size(); i++) {
@@ -620,11 +621,13 @@ bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, boo
 			lua_interface->ResetFunctionStack(lua_spell->state);
 	}
 
-	caster->GetZone()->SendCastSpellPacket(lua_spell, caster);
-
-	if (!remove)
-		return CastProcessedSpell(lua_spell, passive);
+	bool result = CastProcessedSpell(lua_spell, passive);
 
+	caster->GetZone()->SendCastSpellPacket(lua_spell, caster);
+	
+	if(!remove)
+		return result;
+	
 	lua_interface->RemoveSpell(lua_spell, true, SpellScriptTimersHasSpell(lua_spell));
 	return true;
 }
@@ -651,12 +654,12 @@ void SpellProcess::SendFinishedCast(LuaSpell* spell, Client* client){
 			UnlockAllSpells(client);
 		
 		if(spell->resisted && spell->spell->GetSpellData()->recast > 0)
-			CheckRecast(spell->spell, client->GetPlayer(), 0.5); // half sec recast on resisted spells
+			CheckRecast(spell->spell, client->GetPlayer(), 0.5f); // half sec recast on resisted spells
 		else if (!spell->interrupted)
 			CheckRecast(spell->spell, client->GetPlayer());
 		else if(spell->caster && spell->caster->IsPlayer())
 		{
-			((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->GetSpellData()->recast * 10));
+			((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->CalculateRecastTimer(spell->caster) / 100));
 		}
 		PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion());
 		if(packet){

+ 34 - 6
EQ2/source/WorldServer/Spells.cpp

@@ -810,7 +810,7 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
 	else {
 		packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time);
 	}
-	packet->setSubstructDataByName("spell_info", "recast", spell->recast);
+	packet->setSubstructDataByName("spell_info", "recast", CalculateRecastTimer(client->GetPlayer())/1000);
 	packet->setSubstructDataByName("spell_info", "radius", spell->radius);
 	packet->setSubstructDataByName("spell_info", "req_concentration", spell->req_concentration);
 	//packet->setSubstructDataByName("spell_info","req_concentration2", 2);
@@ -2220,12 +2220,40 @@ bool Spell::IsCopiedSpell() {
 void Spell::ModifyCastTime(Entity* caster){
 	int16 cast_time = spell->cast_time;
 	float cast_speed = caster->GetInfoStruct()->get_casting_speed();
-	if (cast_time > 0){
-		if (cast_speed > 0) // casting speed can only reduce up to half a cast time
-			spell->cast_time *= max((float) 0.5, (float) (1 / (1 + (cast_speed * .01))));
-		else if (cast_speed < 0) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
-			spell->cast_time *= min((float) 1.5, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
+	if (cast_speed > 0.0f){
+		bool modifiedSpeed = false;
+		if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time
+			spell->cast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01))));
+		else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
+			spell->cast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
+		
+		if(modifiedSpeed) {
+			LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s cast time %u to %u based on cast_speed %f", GetName(), caster->GetName(), cast_time, spell->cast_time, cast_speed);
+		}
+	}
+}
+
+int32 Spell::CalculateRecastTimer(Entity* caster, float override_timer) {
+	int32 original_recast = static_cast<int>(GetSpellData()->recast) * 1000;
+	
+	if(override_timer > 0.0f) {
+		original_recast = static_cast<int>(override_timer) * 1000;
+	}
+	
+	int32 recast_time = original_recast;
+	float cast_speed = caster->GetInfoStruct()->get_spell_reuse_speed();
+	if (cast_speed > 0.0f){
+		bool modifiedSpeed = false;
+		if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time
+			recast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01))));
+		else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
+			recast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
+		
+		if(modifiedSpeed) {
+			LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s recast time %u to %u based on spell_reuse_time %f", GetName(), caster->GetName(), original_recast, recast_time, cast_speed);
+		}
 	}
+	return recast_time;
 }
 
 vector <SpellDisplayEffect*>* Spell::GetSpellEffects(){

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

@@ -364,6 +364,7 @@ public:
 	bool IsOffenseSpell();
 	bool IsCopiedSpell();
 	void ModifyCastTime(Entity* caster);
+	int32 CalculateRecastTimer(Entity* caster, float override_timer = 0.0f);
 	bool CastWhileStunned();
 	bool CastWhileMezzed();
 	bool CastWhileStifled();

+ 64 - 4
EQ2/source/WorldServer/VisualStates.h

@@ -61,7 +61,7 @@ public:
 		if(in_targeted_message)
 			targeted_message = string(in_targeted_message);
 	}
-	int GetVisualState() { return visual_state; }
+	int32 GetVisualState() { return visual_state; }
 	const char* GetName() { return name.c_str(); }
 	const char* GetMessage() { return message.c_str(); }
 	const char* GetTargetedMessage() { return targeted_message.c_str(); }
@@ -70,7 +70,7 @@ public:
 	string GetMessageString() { return message; }
 	string GetTargetedMessageString() { return targeted_message; }
 private:
-	int visual_state;
+	int32 visual_state;
 	string name;
 	string message;
 	string targeted_message;
@@ -98,7 +98,7 @@ public:
 	}
 
 	void AddVersionRange(int32 min_version, int32 max_version,
-		char* in_name, int in_visual_state, char* in_message, char* in_targeted_message)
+		char* in_name, int in_visual_state, char* in_message = nullptr, char* in_targeted_message = nullptr)
 	{
 		map<VersionRange*, Emote*>::iterator itr = FindVersionRange(min_version, max_version);
 		if (itr != version_map.end())
@@ -164,8 +164,9 @@ public:
 	void Reset(){
 		ClearVisualStates();
 		ClearEmotes();
+		ClearSpellVisuals();
 	}
-
+	
 	void ClearEmotes(){
 		map<string, EmoteVersionRange*>::iterator map_list;
 		for(map_list = emoteMap.begin(); map_list != emoteMap.end(); map_list++ )
@@ -215,8 +216,67 @@ public:
 		}
 		return 0;
 	}
+	
+	void InsertSpellVisualRange(EmoteVersionRange* emote, int32 spell_visual_id) {
+		spellMap[emote->GetName()] = emote;
+		spellMapID[spell_visual_id] = emote;
+	}
+
+	EmoteVersionRange* FindSpellVisualRange(string var) {
+		if (spellMap.count(var) > 0)
+		{
+			return spellMap[var];
+		}
+		return 0;
+	}
+
+	EmoteVersionRange* FindSpellVisualRangeByID(int32 id) {
+		if (spellMapID.count(id) > 0)
+		{
+			return spellMapID[id];
+		}
+		return 0;
+	}
+
+	Emote* FindSpellVisual(string var, int32 version){
+		if (spellMap.count(var) > 0)
+		{
+			map<VersionRange*,Emote*>::iterator itr = spellMap[var]->FindEmoteVersion(version);
+
+			if (itr != spellMap[var]->GetRangeEnd())
+			{
+				Emote* emote = itr->second;
+				return emote;
+			}
+		}
+		return 0;
+	}
+	
+	Emote* FindSpellVisualByID(int32 visual_id, int32 version){
+		if (spellMapID.count(visual_id) > 0)
+		{
+			map<VersionRange*,Emote*>::iterator itr = spellMapID[visual_id]->FindEmoteVersion(version);
+
+			if (itr != spellMapID[visual_id]->GetRangeEnd())
+			{
+				Emote* emote = itr->second;
+				return emote;
+			}
+		}
+		return 0;
+	}
+
+	void ClearSpellVisuals(){
+		map<string, EmoteVersionRange*>::iterator map_list;
+		for(map_list = spellMap.begin(); map_list != spellMap.end(); map_list++ )
+			safe_delete(map_list->second);
+		spellMap.clear();
+		spellMapID.clear();
+	}
 private:
 	map<string,VisualState*> visualStateMap;
 	map<string,EmoteVersionRange*> emoteMap;
+	map<string,EmoteVersionRange*> spellMap;
+	map<int32,EmoteVersionRange*> spellMapID;
 };
 

+ 24 - 3
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -459,11 +459,29 @@ void WorldDatabase::LoadVisualStates()
 			visual_states.InsertEmoteRange(range);
 		}
 		
-		range->AddVersionRange(atoul(row[4]),atoul(row[5]), row[0], atoi(row[1]), row[2], row[3]);
+		range->AddVersionRange(atoul(row[4]),atoul(row[5]), row[0], atoul(row[1]), row[2], row[3]);
 		total++;
-		LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row[1], atoi(row[0]));
+		LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row[0], atoul(row[0]));
 	}
 	LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u emote state(s)", total);
+	
+	
+	total = 0;
+	result = query2.RunQuery2(Q_SELECT, "SELECT name, spell_visual_id, alternate_spell_visual, min_version_range, max_version_range FROM spell_visuals");
+	while(result && (row = mysql_fetch_row(result)))
+	{
+		EmoteVersionRange* range = 0;
+		if ((range = visual_states.FindSpellVisualRange(string(row[0]))) == NULL)
+		{
+			range = new EmoteVersionRange(row[0]);
+			visual_states.InsertSpellVisualRange(range, atoul(row[1]));
+		}
+		
+		range->AddVersionRange(atoul(row[3]),atoul(row[4]), row[0], atoul(row[1]), row[2]);
+		total++;
+		LogWrite(WORLD__DEBUG, 5, "World", "---Loading spell visual state: '%s' (%u)", row[1], atoul(row[0]));
+	}
+	LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u spell visual state(s)", total);
 }
 
 void WorldDatabase::LoadSubCommandList() 
@@ -7632,7 +7650,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 
 	multimap<LuaSpell*, Entity*> restoreSpells;
 	// Use -1 on type and subtype to turn the enum into an int and make it a 0 index
-	if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) {
+	if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) {
 		LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
 		return;
 	}
@@ -7668,6 +7686,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 		int8 last_spellattack_hit = result.GetInt32Str("last_spellattack_hit");
 		int8 interrupted = result.GetInt32Str("interrupted");
 		int8 resisted = result.GetInt32Str("resisted");
+		int8 has_damaged = result.GetInt32Str("has_damaged");
 		std::string custom_function = std::string(result.GetStringStr("custom_function"));
 		LuaSpell* lua_spell = 0;
 		if(custom_spell)
@@ -7788,6 +7807,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			lua_spell->interrupted = interrupted;
 			lua_spell->last_spellattack_hit = last_spellattack_hit;
 			lua_spell->num_triggers = num_triggers;
+			lua_spell->has_damaged = has_damaged;
+			lua_spell->is_damage_spell = has_damaged;
 		}
 
 		if(lua_spell->initial_target == 0 && target_char_id == 0xFFFFFFFF && player->HasPet())

+ 47 - 6
EQ2/source/WorldServer/client.cpp

@@ -86,6 +86,7 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #include "Tradeskills/Tradeskills.h"
 #include "AltAdvancement/AltAdvancement.h"
 #include "Bots/Bot.h"
+#include "VisualStates.h"
 
 extern WorldDatabase database;
 extern const char* ZONE_NAME;
@@ -119,6 +120,7 @@ extern MasterAAList master_aa_list;
 extern MasterAAList master_tree_nodes;
 extern ChestTrapList chest_trap_list;
 extern MasterRecipeBookList master_recipebook_list;
+extern VisualStates visual_states;
 
 using namespace std;
 
@@ -3036,7 +3038,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			sent_item_details[id] = true;
 			MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
 			EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
-			//DumpPacket(app);
+			
 			QueuePacket(app);
 			if (wasSpawn)
 				delete item;
@@ -3070,7 +3072,6 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 			sent_item_details[id] = true;
 			MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
 			EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
-			//DumpPacket(app);
 			QueuePacket(app);
 		}
 		else {
@@ -3099,7 +3100,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
 		if (item) {
 			//only display popup for non merchant links
 			EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 546 ? true : false);
-			//DumpPacket(app);
+			
 			QueuePacket(app);
 		}
 		else {
@@ -7965,9 +7966,7 @@ void Client::SendBuyMerchantList(bool sell) {
 					else
 						packet->setDataByName("type", 2);
 				}
-				packet->PrintPacket();
 				EQ2Packet* outapp = packet->serialize();
-				//	DumpPacket(outapp);
 				QueuePacket(outapp);
 				safe_delete(packet);
 			}
@@ -8541,7 +8540,6 @@ void Client::SendMailList() {
 				p->setArrayDataByName("player_to_id", mail->player_to_id, i);
 				p->setArrayDataByName("player_from", mail->player_from.c_str(), i);
 				p->setArrayDataByName("subject", mail->subject.c_str(), i);
-				p->setArrayDataByName("unknown1", 0x0000, i);
 				p->setArrayDataByName("already_read", mail->already_read, i);
 				if(mail->expire_time)
 					p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i);
@@ -8598,6 +8596,8 @@ void Client::SendMailList() {
 			p->setDataByName("unknown3", 0x01F4);
 			p->setDataByName("unknown4", 0x01000000);
 			EQ2Packet* pack = p->serialize();
+			//DumpPacket(pack);
+			//p->PrintPacket();
 			QueuePacket(pack);
 			safe_delete(p);
 		}
@@ -12415,4 +12415,45 @@ ZoneServer* Client::GetHouseZoneServer(int32 spawn_id, int64 house_id) {
 	}
 	
 	return nullptr;
+}
+
+void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time) {
+	PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", GetVersion());
+	if (packet) {
+		int32 caster_id = GetPlayer()->GetIDWithPlayerSpawn(caster);
+		int32 target_id = GetPlayer()->GetIDWithPlayerSpawn(target);
+		
+		packet->setDataByName("spawn_id", caster_id);
+		packet->setArrayLengthByName("num_targets", 1);
+		packet->setArrayDataByName("target", target_id);
+		packet->setDataByName("num_targets", 1);
+
+		int32 visual = GetSpellVisualOverride(spell_visual);
+		
+		packet->setDataByName("spell_visual", visual); //result
+		packet->setDataByName("cast_time", cast_time*.01f); //delay
+		packet->setDataByName("spell_id", 1);
+		packet->setDataByName("spell_level", 1);
+		packet->setDataByName("spell_tier", 1);
+		EQ2Packet* outapp = packet->serialize();
+		
+		DumpPacket(outapp);
+		QueuePacket(outapp);
+		safe_delete(packet);
+	}
+}
+
+int32 Client::GetSpellVisualOverride(int32 spell_visual) {
+	int32 visual = spell_visual;
+	if(GetVersion() <= 546) { // spell's spell_visual field is based on newer clients, DoF has to convert
+		Emote* spellVisualEmote = visual_states.FindSpellVisualByID(visual, 60085);
+		if(spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) {
+			spellVisualEmote = visual_states.FindSpellVisual(spellVisualEmote->GetMessageString(), GetVersion());
+			if(spellVisualEmote) {
+				visual = (int32)spellVisualEmote->GetVisualState();
+			}
+		}
+	}
+	
+	return visual;
 }

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

@@ -585,6 +585,9 @@ public:
 	
 	void	SendReplaceWidget(int32 widget_id, bool delete_widget, float x=0.0f, float y=0.0f, float z=0.0f, int32 grid_id=0);
 	void	ProcessZoneIgnoreWidgets();
+	
+	void	SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time);
+	int32	GetSpellVisualOverride(int32 spell_visual);
 private:
 	void	AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i);
 	void    SavePlayerImages();

+ 21 - 4
EQ2/source/WorldServer/zoneserver.cpp

@@ -5249,13 +5249,18 @@ void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool f
 	safe_delete(packet);
 }
 
-void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
+void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override, int16 casttime_override){
 	EQ2Packet* outapp = 0;
 	PacketStruct* packet = 0;
 	Client* client = 0;
 	if(!caster || !spell || !spell->spell || spell->interrupted)
 		return;
 
+	if(spell->is_damage_spell && (!spell->has_damaged || spell->resisted)) {
+		// we did not successfully hit target, so we should not send the visual
+		return;
+	}
+	
 	vector<Client*>::iterator client_itr;
 
 	MClientList.readlock(__FUNCTION__, __LINE__);
@@ -5284,8 +5289,17 @@ void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
 					packet->setArrayDataByName("target", 0xFFFFFFFF, i);
 				}
 			}
-			packet->setDataByName("spell_visual", spell->spell->GetSpellData()->spell_visual); //result
-			packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
+			
+			int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual;
+			int32 visual = client->GetSpellVisualOverride(visual_to_use);
+			
+			packet->setDataByName("spell_visual", visual); //result
+			if(casttime_override != 0xFFFF) {
+				packet->setDataByName("cast_time", casttime_override*.01f); //delay
+			}
+			else {
+				packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
+			}
 			packet->setDataByName("spell_id", spell->spell->GetSpellID());
 			packet->setDataByName("spell_level", 1);
 			packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier);
@@ -5330,7 +5344,10 @@ void ZoneServer::SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* c
 				}
 				packet->setArrayLengthByName("num_targets", 1);
 				packet->setArrayDataByName("target", target_id);
-				packet->setDataByName("spell_visual", spell_visual);
+
+				int32 visual = client->GetSpellVisualOverride(spell_visual);
+				
+				packet->setDataByName("spell_visual", visual);
 				packet->setDataByName("cast_time", 0);
 				packet->setDataByName("spell_id", 0);
 				packet->setDataByName("spell_level", 0);

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

@@ -355,7 +355,7 @@ public:
 	void	SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name);
 	void    SendHealPacket(Spawn* caster, Spawn* target, int16 type, int32 heal_amt, const char* spell_name);
 	
-	void	SendCastSpellPacket(LuaSpell* spell, Entity* caster);
+	void	SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override = 0, int16 casttime_override = 0xFFFF);
 	void	SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster = 0);
 	void	SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id);
 	void	TriggerCharSheetTimer();

+ 5 - 4
EQ2/source/common/EQStream.cpp

@@ -279,7 +279,7 @@ bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 len
 			if(valid)
 				return true;
 		}
-		else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff) {
+		else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff && p->size > (1+offset)) {
 			uint8 new_length = 0;
 			
 			memcpy(&new_length, p->pBuffer+offset, sizeof(int8));
@@ -291,7 +291,7 @@ bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 len
 			delete subp;
 			return true;
 			}
-		}		
+		}
 	}
 	return false;
 }
@@ -1467,8 +1467,9 @@ void EQStream::Write(int eq_fd)
 		//no more data to send
 		if (GetState() == CLOSING) {
 			MOutboundQueue.lock();
-			if (SequencedQueue.size() > 0 )
-				LogWrite(PACKET__DEBUG, 9, "Packet",  "All outgoing data flushed, client should be disconnecting, awaiting acknowledgement of SequencedQueue.");
+			if (SequencedQueue.size() > 0 ) {
+				// retransmission attempts
+			}
 			else
 			{
 				LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client.");

+ 15 - 13
server/ItemStructs.xml

@@ -1029,6 +1029,7 @@
 <Data ElementName="index" Type="int16" Size="1" />
 <Data ElementName="icon" Type="int16" Size="1" />
 <Data ElementName="count" Type="int8" Size="1" />
+<Data ElementName="unknown" Type="int8" Size="1" />
 <Data ElementName="level" Type="int8" Size="1" />
 <Data ElementName="tier" Type="int8" Size="1" />
 <Data ElementName="num_slots" Type="int8" Size="1" />
@@ -1036,19 +1037,20 @@
 <Data ElementName="name" Type="char" Size="81" />
 </Struct>
 <Struct Name="Substruct_Item" ClientVersion="546" >
-<Data ElementName="unique_id" Type="int32" Size="1" />
-<Data ElementName="bag_id" Type="int32" Size="1" />
-<Data ElementName="inv_slot_id" Type="int32" Size="1" />
-<Data ElementName="menu_type" Type="int32" Size="1" />
-<Data ElementName="slot_id" Type="int8" Size="1" />
-<Data ElementName="index" Type="int16" Size="1" />
-<Data ElementName="icon" Type="int16" Size="1" />
-<Data ElementName="count" Type="int8" Size="1" />
-<Data ElementName="level" Type="int8" Size="1" />
-<Data ElementName="tier" Type="int8" Size="1" />
-<Data ElementName="num_slots" Type="int8" Size="1" />
-<Data ElementName="item_id" Type="sint32" Size="1" />
-<Data ElementName="name" Type="char" Size="81" />
+<Data ElementName="unique_id" Type="int32" Size="1" /><!-- 4 -->
+<Data ElementName="bag_id" Type="int32" Size="1" /><!-- 8 -->
+<Data ElementName="inv_slot_id" Type="int32" Size="1" /><!-- 12 -->
+<Data ElementName="menu_type" Type="int32" Size="1" /><!-- 16 -->
+<Data ElementName="slot_id" Type="int8" Size="1" /><!-- 17 -->
+<Data ElementName="index" Type="int16" Size="1" /><!-- 19 -->
+<Data ElementName="icon" Type="int16" Size="1" /> <!-- 21 -->
+<Data ElementName="count" Type="int8" Size="1" /> <!-- 23 -->
+<Data ElementName="level" Type="int8" Size="1" /> <!-- 24 -->
+<Data ElementName="tier" Type="int8" Size="1" /> <!-- 25 -->
+<Data ElementName="num_slots" Type="int8" Size="1" /> <!-- 26 -->
+<Data ElementName="item_id" Type="sint32" Size="1" /> <!-- 27 -->
+<Data ElementName="name" Type="char" Size="64" />
+<Data ElementName="unknown6" Type="int8" Size="17" />
 </Struct>
 <Struct Name="Substruct_Item" ClientVersion="547" >
 <Data ElementName="unique_id" Type="int32" Size="1" />

+ 4 - 4
server/Spells/Fighter/Crusader/Shadowknight/Soulrend.lua

@@ -8,11 +8,12 @@
 
 function cast(Caster, Target, DmgType, MinVal, MaxVal)
 
+	local damageInflicted = false;
     -- Inflicts 12 - 20 piercing damage on target
     if MaxVal ~= nil and MinVal < MaxVal then
-        SpellDamage(Target, DmgType, math.random(MinVal, MaxVal))
+        damageInflicted = SpellDamage(Target, DmgType, math.random(MinVal, MaxVal))
     else
-        SpellDamage(Target, DmgType, MinVal)
+        damageInflicted = SpellDamage(Target, DmgType, MinVal)
     end
 
     -- Applies Knockdown on termination.  Lasts for 1.5 seconds.
@@ -20,8 +21,7 @@ function cast(Caster, Target, DmgType, MinVal, MaxVal)
     --     Stuns target
     --     Blurs vision of target
     --     Does not affect Epic targets
-    if not IsEpic(Target) then
-        Knockback(Caster, Target, 1500)
+    if damageInflicted and not IsEpic(Target) then
         AddControlEffect(Target, 4)
         BlurVision(Target, 1.0)
         AddSpellTimer(1500, "RemoveStunBlur")

+ 27 - 4
server/WorldStructs.xml

@@ -2938,6 +2938,21 @@ to zero and treated like placeholders." />
 <Data ElementName="spell" Type="int8" />
 <Data ElementName="spell_name" Type="EQ2_16Bit_String" Size="1" />
 </Struct>
+<Struct Name="WS_HearSiphonSpellDamage" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearCombatCmd">
+<Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
+<Data ElementName="num_dmg" Type="int8" />
+<Data ElementName="siphon_type" Type="int8" />
+<Data ElementName="dmg_array" Type="Array" ArraySizeVariable="num_dmg">
+	<Data ElementName="damage_type" Type="int8" />
+	<Data ElementName="damage" Type="int16" />
+	<Data ElementName="unknown1" Type="int8" />
+	<Data ElementName="unknown2" Type="int8" />
+	<Data ElementName="crit_flag" Type="int8" /> <!-- 4==crit -->
+	<Data ElementName="unknown4" Type="int8" />
+</Data>
+<Data ElementName="spell" Type="int8" />
+<Data ElementName="spell_name" Type="EQ2_16Bit_String" Size="1" />
+</Struct>
 <Struct Name="WS_HearSiphonSpellDamage" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearCombatCmd">
 <Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
 <Data ElementName="siphon_type" Type="int8" />
@@ -3094,8 +3109,8 @@ to zero and treated like placeholders." />
 <Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
 <Data ElementName="siphon_type" Type="int8" />
 <Data ElementName="siphon_subtype" Type="int8" />
-<Data ElementName="damage" Type="int16" />
-<Data ElementName="unknown1" Type="int8" Size="2" />
+<Data ElementName="damage" Type="int32" />
+<Data ElementName="unknown1" Type="int8" Size="5" />
 <Data ElementName="spell_name" Type="EQ2_8Bit_String" Size="1" />
 <!-- All Hear spell damages so far seem to have new bytes at the end (who knows for how long) -->
 <Data ElementName="unknown2" Type="int8" Size="5" />
@@ -3132,12 +3147,20 @@ to zero and treated like placeholders." />
 <!-- All Hear spell damages so far seem to have new bytes at the end (who knows for how long) -->
 <Data ElementName="unknown2" Type="int8" Size="5" />
 </Struct>
+<!-- WS_HearHeal may be innaccurate, copied from DoF -->
 <Struct Name="WS_HearHeal" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
 <Data ElementName="caster" Type="int32" />
 <Data ElementName="target" Type="int32" />
-<Data ElementName="heal_amt" Type="int32" />
+<Data ElementName="heal_amt" Type="int16" />
 <Data ElementName="spellname" Type="EQ2_16Bit_String" Size="1"/>
-<Data ElementName="type" Type="int16" />
+<Data ElementName="type" Type="int8" />
+</Struct>
+<Struct Name="WS_HearHeal" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
+<Data ElementName="caster" Type="int32" />
+<Data ElementName="target" Type="int32" />
+<Data ElementName="heal_amt" Type="int16" />
+<Data ElementName="spellname" Type="EQ2_16Bit_String" Size="1"/>
+<Data ElementName="type" Type="int8" />
 </Struct>
 <Struct Name="WS_HearHeal" ClientVersion="57048" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
 <Data ElementName="caster" Type="int32" />

Some files were not shown because too many files changed in this diff