Browse Source

Sirius Updates #2

Fix #289 - pets don't follow
Fix #288 - FaceTarget(Originator, Target, disable_action_state) - disable_action_state defaults to true
Fix #287 - pause on hail
	RULE_INIT(R_Spawn, HailMovementPause, "5000"); // time in milliseconds the spawn is paused on hail
	RULE_INIT(R_Spawn, HailDistance, "5"); // max distance to hail a spawn/npc
Fix #286 - PauseMovement(Spawn, time_in_ms) added
Fix #261 - spawn_npcs added water_type and flying_type, temp rules added also

alter table spawn_npcs add column water_type tinyint(1) unsigned not null default 0;
alter table spawn_npcs add column flying_type tinyint(1) unsigned not null default 0;
    RULE_INIT(R_Spawn, UseHardCodeWaterModelType, "1"); // uses alternate method of setting water type by model type (hardcoded) versus relying on just DB
    RULE_INIT(R_Spawn, UseHardCodeFlyingModelType, "1"); // uses alternate method of setting flying type by model type (hardcoded) versus relying on just DB
Image 3 years ago
parent
commit
0e86dac628

+ 1 - 4
EQ2/source/WorldServer/Combat.cpp

@@ -1162,10 +1162,7 @@ void Entity::KillSpawn(Spawn* dead, int8 damage_type, int16 kill_blow_type) {
 		((NPC*)dead)->GetOwner()->DismissPet((NPC*)dead, true, true);
 	else if (dead->IsEntity()) {
 		// remove all pets for this entity
-		((Entity*)dead)->DismissPet((NPC*)((Entity*)dead)->GetPet(), false, true);
-		((Entity*)dead)->DismissPet((NPC*)((Entity*)dead)->GetCharmedPet(), false, true);
-		((Entity*)dead)->DismissPet((NPC*)((Entity*)dead)->GetDeityPet(), false, true);
-		((Entity*)dead)->DismissPet((NPC*)((Entity*)dead)->GetCosmeticPet(), false, true);
+		((Entity*)dead)->DismissAllPets(false, true);
 	}
 
 	// If not in combat and no one in the encounter list add this killer to the list

+ 13 - 4
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -1748,13 +1748,20 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				if (spawn->IsNPC())
 					show_bubble = false;
 				client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, tmp, HEAR_SPAWN_DISTANCE, 0, show_bubble);
-				if(spawn->IsPlayer() == false && spawn->GetDistance(client->GetPlayer()) < 30){
+				if(spawn->IsPlayer() == false && spawn->GetDistance(client->GetPlayer()) < rule_manager.GetGlobalRule(R_Spawn, HailDistance)->GetInt32()){
 					if(spawn->IsNPC() && ((NPC*)spawn)->EngagedInCombat())
 						spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED_BUSY, client->GetPlayer());
 					else
-						spawn->ProcessMovement();
-					LogWrite(MISC__TODO, 0, "Check", "ProcessMovement has been called");
-						spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED, client->GetPlayer());
+					{
+						// prime runback as the heading or anything can be altered when hailing succeeds
+						if(spawn->IsNPC())
+							((NPC*)spawn)->StartRunback();
+
+						if(spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED, client->GetPlayer()))
+							spawn->PauseMovement(rule_manager.GetGlobalRule(R_Spawn, HailMovementPause)->GetInt32());
+						else if(spawn->IsNPC())
+							((NPC*)spawn)->ClearRunback();
+					}
 				}
 			}
 			else {
@@ -3817,6 +3824,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				details3 += "Pitch:	" + to_string(spawn->GetPitch()) + "\n";
 				details3 += "Roll:	" + to_string(spawn->GetRoll()) + "\n";
 				details3 += "Hide Hood:	" + to_string(spawn->appearance.hide_hood) + "\n";
+				details3 += "Speed:	" + to_string(spawn->GetSpeed()) + "\n";
+				details3 += "BaseSpeed:	" + to_string(spawn->GetBaseSpeed()) + "\n";
 
 				string details4;
 				if (spawn->IsEntity()) {

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

@@ -81,6 +81,12 @@ Entity::Entity(){
 	MCommandMutex.SetName("Entity::MCommandMutex");
 	hasSeeInvisSpell = false;
 	hasSeeHideSpell = false;
+
+	owner = 0;
+	m_petType = 0;
+	m_petSpellID = 0;
+	m_petSpellTier = 0;
+	m_petDismissing = false;
 }
 
 Entity::~Entity(){
@@ -239,6 +245,9 @@ void Entity::MapInfoStruct()
 
 	get_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::get_power_regen_override, &info_struct);
 	get_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::get_hp_regen_override, &info_struct);
+	
+	get_int8_funcs["water_type"] = l::bind(&InfoStruct::get_water_type, &info_struct);
+	get_int8_funcs["flying_type"] = l::bind(&InfoStruct::get_flying_type, &info_struct);
 
 
 /** SETS **/
@@ -379,6 +388,9 @@ void Entity::MapInfoStruct()
 
 	set_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::set_power_regen_override, &info_struct, l::_1);
 	set_int8_funcs["hp_region_override"] = l::bind(&InfoStruct::set_hp_regen_override, &info_struct, l::_1);
+	
+	set_int8_funcs["water_type"] = l::bind(&InfoStruct::set_water_type, &info_struct, l::_1);
+	set_int8_funcs["flying_type"] = l::bind(&InfoStruct::set_flying_type, &info_struct, l::_1);
 
 }
 
@@ -1562,18 +1574,29 @@ void Entity::HideCosmeticPet(bool val) {
 		cosmeticPet->MakeSpawnPublic();
 }
 
-void Entity::DismissPet(NPC* pet, bool from_death, bool spawnListLocked) {
+void Entity::DismissAllPets(bool from_death, bool spawnListLocked)
+{
+	DismissPet(GetPet(), from_death, spawnListLocked);
+	DismissPet(GetCharmedPet(), from_death, spawnListLocked);
+	DismissPet(GetDeityPet(), from_death, spawnListLocked);
+	DismissPet(GetCosmeticPet(), from_death, spawnListLocked);
+}
+
+void Entity::DismissPet(Entity* pet, bool from_death, bool spawnListLocked) {
 	if (!pet)
 		return;
 
 	Entity* PetOwner = pet->GetOwner();
 
-	pet->SetDismissing(true);
+	if(pet->IsNPC())
+	{
+		((NPC*)pet)->SetDismissing(true);
 
-	// Remove the spell maintained spell
-	Spell* spell = master_spell_list.GetSpell(pet->GetPetSpellID(), pet->GetPetSpellTier());
-	if (spell)
-		GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell, from_death == true ? (string)"pet_death" : (string)"canceled");
+		// Remove the spell maintained spell
+		Spell* spell = master_spell_list.GetSpell(pet->GetPetSpellID(), pet->GetPetSpellTier());
+		if (spell)
+			GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell, from_death == true ? (string)"pet_death" : (string)"canceled");
+	}
 
 	if (pet->GetPetType() == PET_TYPE_CHARMED) {
 		PetOwner->SetCharmedPet(0);
@@ -1582,7 +1605,9 @@ void Entity::DismissPet(NPC* pet, bool from_death, bool spawnListLocked) {
 			// set the pet flag to false, owner to 0, and give the mob its old brain back
 			pet->SetPet(false);
 			pet->SetOwner(0);
-			pet->SetBrain(new Brain(pet));
+			if(pet->IsNPC())
+				((NPC*)pet)->SetBrain(new Brain((NPC*)pet));
+
 			pet->SetDismissing(false);
 		}
 	}
@@ -1822,6 +1847,7 @@ float Entity::GetSpeed() {
 	MStats.unlock();
 	
 	ret *= speed_multiplier;
+	
 	return ret;
 }
 
@@ -3262,4 +3288,14 @@ bool Entity::SetInfoStructFloat(std::string field, float value)
 			return true;
 		}
 	return false;
+}
+
+Entity*	Entity::GetOwner() {
+	Entity* ent = nullptr;
+
+	Spawn* spawn = GetZone()->GetSpawnByID(owner);
+	if ( spawn && spawn->IsEntity() )
+		ent = (Entity*)spawn;
+
+	return ent;
 }

+ 34 - 1
EQ2/source/WorldServer/Entity.h

@@ -251,6 +251,9 @@ struct InfoStruct{
 
 		power_regen_override_ = 0;
 		hp_regen_override_ = 0;
+
+		water_type_ = 0;
+		flying_type_ = 0;
 	}
 
 
@@ -397,6 +400,9 @@ struct InfoStruct{
 
 		power_regen_override_ = oldStruct->get_power_regen_override();
 		hp_regen_override_ = oldStruct->get_hp_regen_override();
+
+		water_type_ = oldStruct->get_water_type();
+		flying_type_ = oldStruct->get_flying_type();
 	}
 
 	//mutable std::shared_mutex mutex_;
@@ -554,6 +560,9 @@ struct InfoStruct{
 	int8	 get_power_regen_override() { std::lock_guard<std::mutex> lk(classMutex); return power_regen_override_; }
 	int8	 get_hp_regen_override() { std::lock_guard<std::mutex> lk(classMutex); return hp_regen_override_; }
 
+	int8	 get_water_type() { std::lock_guard<std::mutex> lk(classMutex); return water_type_; }
+	int8	 get_flying_type() { std::lock_guard<std::mutex> lk(classMutex); return flying_type_; }
+
 	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; }
@@ -793,6 +802,9 @@ struct InfoStruct{
 	void	set_power_regen_override(int8 value) { std::lock_guard<std::mutex> lk(classMutex); power_regen_override_ = value; }
 	void	set_hp_regen_override(int8 value) { std::lock_guard<std::mutex> lk(classMutex); hp_regen_override_ = value; }
 
+	void	set_water_type(int8 value) { std::lock_guard<std::mutex> lk(classMutex); water_type_ = value; }
+	void	set_flying_type(int8 value) { std::lock_guard<std::mutex> lk(classMutex); flying_type_ = value; }
+
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -955,6 +967,9 @@ private:
 
 	int8			power_regen_override_;
 	int8			hp_regen_override_;
+
+	int8			water_type_;
+	int8			flying_type_;
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };
@@ -1400,7 +1415,19 @@ public:
 
 	void HideDeityPet(bool val);
 	void HideCosmeticPet(bool val);
-	void DismissPet(NPC* pet, bool from_death = false, bool spawnListLocked = false);
+	void DismissPet(Entity* pet, bool from_death = false, bool spawnListLocked = false);
+	void DismissAllPets(bool from_death = false, bool spawnListLocked = false);
+
+	void	SetOwner(Entity* owner) { if (owner) { this->owner = owner->GetID(); } else { owner = 0; } }
+	Entity*	GetOwner();
+	int8	GetPetType() { return m_petType; }
+	void	SetPetType(int8 val) { m_petType = val; }
+	void	SetPetSpellID(int32 val) { m_petSpellID = val; }
+	int32	GetPetSpellID() { return m_petSpellID; }
+	void	SetPetSpellTier(int8 val) { m_petSpellTier = val; }
+	int8	GetPetSpellTier() { return m_petSpellTier; }
+	bool IsDismissing() { return m_petDismissing; }
+	void SetDismissing(bool val) { m_petDismissing = val; }
 
 	/// <summary>Creates a loot chest to drop in the world</summary>
 	/// <returns>Pointer to the chest</returns>
@@ -1580,6 +1607,12 @@ public:
 	std::mutex		MStats;
 protected:
 	bool	in_combat;
+	int8	m_petType;
+	int32	owner;
+	// m_petSpellID holds the spell id used to create/control this pet
+	int32	m_petSpellID;
+	int8	m_petSpellTier;
+	bool	m_petDismissing;
 
 private:
 	MutexList<BonusValues*> bonus_list;

+ 20 - 1
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1025,10 +1025,16 @@ int EQ2Emu_lua_FaceTarget(lua_State* state) {
 		return 0;
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	Spawn* target = lua_interface->GetSpawn(state, 2);
+	
+	int8 num_args = (int8)lua_interface->GetNumberOfArgs(state);
+	bool reset_action_state = true;
+	if(num_args > 2)
+		reset_action_state = lua_interface->GetBooleanValue(state, 3);
+	
 	if (spawn && target) {
 		if (spawn->IsEntity())
 			// ((Entity*)spawn)->FaceTarget(target);
-			static_cast<Entity*>(spawn)->FaceTarget(target);
+			static_cast<Entity*>(spawn)->FaceTarget(target, reset_action_state);
 	}
 	lua_interface->ResetFunctionStack(state);
 	return 0;
@@ -1996,6 +2002,7 @@ int EQ2Emu_lua_SetSpeed(lua_State* state) {
 	float value = lua_interface->GetFloatValue(state, 2);
 	lua_interface->ResetFunctionStack(state);
 	if (spawn) {
+				printf("Speed set lua: %f\n",value);
 		spawn->SetSpeed(value);
 		((Entity*)spawn)->SetSpeed(value);
 		if (spawn->IsPlayer()) {
@@ -12043,3 +12050,15 @@ int EQ2Emu_lua_DeleteDBShardID(lua_State* state) {
 		lua_interface->SetBooleanValue(state, database.DeleteSpiritShard(shardid));
 	return 1;
 }
+
+int EQ2Emu_lua_PauseMovement(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int32 delay_in_ms = lua_interface->GetInt32Value(state, 2);
+	if (spawn) {
+		spawn->PauseMovement(delay_in_ms);
+	}
+	lua_interface->ResetFunctionStack(state);
+	return 0;
+}

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

@@ -572,4 +572,6 @@ int EQ2Emu_lua_GetShardID(lua_State* state);
 int EQ2Emu_lua_GetShardCharID(lua_State* state);
 int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state);
 int EQ2Emu_lua_DeleteDBShardID(lua_State* state);
+
+int EQ2Emu_lua_PauseMovement(lua_State* state);
 #endif

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

@@ -1286,6 +1286,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "GetShardCharID", EQ2Emu_lua_GetShardCharID);
 	lua_register(state, "GetShardCreatedTimestamp", EQ2Emu_lua_GetShardCreatedTimestamp);
 	lua_register(state, "DeleteDBShardID", EQ2Emu_lua_DeleteDBShardID);
+	
+	lua_register(state, "PauseMovement", EQ2Emu_lua_PauseMovement);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 43 - 10
EQ2/source/WorldServer/NPC.cpp

@@ -135,7 +135,6 @@ void NPC::Initialize(){
 	skills = 0;
 	spells = 0;
 	runback = 0;
-	owner = 0;
 	m_brain = new ::Brain(this);
 	MBrain.SetName("NPC::m_brain");
 	m_runningBack = false;
@@ -191,11 +190,16 @@ float NPC::GetRunbackDistance(){
 	return GetDistance(runback->x, runback->y, runback->z);
 }
 
-void NPC::Runback(float distance){
+void NPC::Runback(float distance, bool stopFollowing){
+	if(!runback)
+		return;
+	
 	if ( distance == 0.0f )
 		distance = GetRunbackDistance(); // gotta make sure its true, lua doesn't send the distance
 	
-	following = false;
+	if(stopFollowing)
+		following = false;
+	
 	if (!m_runningBack)
 	{
 		ClearRunningLocations();
@@ -229,6 +233,41 @@ void NPC::ClearRunback(){
 	NeedsToResumeMovement(false);
 }
 
+void NPC::StartRunback()
+{
+	if(GetRunbackLocation())
+		return;
+
+	SetRunbackLocation(GetX(), GetY(), GetZ(), GetLocation());
+	m_runbackHeadingDir1 = appearance.pos.Dir1;
+	m_runbackHeadingDir2 = appearance.pos.Dir2;
+}
+
+bool NPC::PauseMovement(int32 period_of_time_ms)
+{
+	if(period_of_time_ms < 1)
+		period_of_time_ms = 1;
+
+	if(HasMovementLoop() || HasMovementLocations())
+		StartRunback();
+	
+	RunToLocation(GetX(),GetY(),GetZ());
+	pause_timer.Start(period_of_time_ms, true);
+
+	return true;
+}
+
+bool NPC::IsPauseMovementTimerActive()
+{
+	if(pause_timer.Check())
+	{
+		pause_timer.Disable();
+		Runback();
+	}
+	
+	return pause_timer.Enabled();
+}
+
 void NPC::InCombat(bool val){
 	if (in_combat == val)
 		return;
@@ -241,9 +280,7 @@ void NPC::InCombat(bool val){
 	if(!in_combat && val){
 		// if not a pet and no current run back location set then set one to the current location
 		if(!IsPet() && !GetRunbackLocation()) {
-			SetRunbackLocation(GetX(), GetY(), GetZ(), GetLocation());
-			m_runbackHeadingDir1 = appearance.pos.Dir1;
-			m_runbackHeadingDir2 = appearance.pos.Dir2;
+			StartRunback();
 		}
 	}
 
@@ -881,10 +918,6 @@ void NPC::SetBrain(::Brain* brain) {
 	safe_delete(old_brain);
 }
 
-Entity*	NPC::GetOwner() {
-	return (Entity*)GetZone()->GetSpawnByID(owner);
-}
-
 void NPC::SetZone(ZoneServer* in_zone, int32 version) {
 	Spawn::SetZone(in_zone, version);
 	if (in_zone){

+ 6 - 19
EQ2/source/WorldServer/NPC.h

@@ -80,6 +80,7 @@ public:
 	void	SetAppearanceID(int32 id){ appearance_id = id; }
 	int32	GetAppearanceID(){ return appearance_id; }
 	bool	IsNPC(){ return true; }
+	void	StartRunback();
 	void	InCombat(bool val);
 	bool	HandleUse(Client* client, string type);
 	void	SetRandomize(int32 value) {appearance.randomize = value;}
@@ -114,20 +115,16 @@ public:
 	void	SetRunbackLocation(float x, float y, float z, int32 gridid);
 	MovementLocation* GetRunbackLocation();
 	float	GetRunbackDistance();
-	void	Runback(float distance=0.0f);
+	void	Runback(float distance=0.0f, bool stopFollowing = true);
 	void	ClearRunback();
+	
+	virtual bool PauseMovement(int32 period_of_time_ms);
+	virtual bool IsPauseMovementTimerActive();
+	
 	void	AddSkillBonus(int32 spell_id, int32 skill_id, float value);
 	virtual void RemoveSkillBonus(int32 spell_id);
 	virtual void SetZone(ZoneServer* zone, int32 version=0);
 
-	void	SetOwner(Entity* owner) { if (owner) { this->owner = owner->GetID(); } else { owner = 0; } }
-	Entity*	GetOwner();
-	int8	GetPetType() { return m_petType; }
-	void	SetPetType(int8 val) { m_petType = val; }
-	void	SetPetSpellID(int32 val) { m_petSpellID = val; }
-	int32	GetPetSpellID() { return m_petSpellID; }
-	void	SetPetSpellTier(int8 val) { m_petSpellTier = val; }
-	int8	GetPetSpellTier() { return m_petSpellTier; }
 	void	SetMaxPetLevel(int8 val) { m_petMaxLevel = val; }
 	int8	GetMaxPetLevel() { return m_petMaxLevel; }
 
@@ -143,9 +140,6 @@ public:
 	sint16 m_runbackHeadingDir1;
 	sint16 m_runbackHeadingDir2;
 
-	bool IsDismissing() { return m_petDismissing; }
-	void SetDismissing(bool val) { m_petDismissing = val; }
-
 	int32 GetShardID() { return m_ShardID; }
 	void SetShardID(int32 shardid) { m_ShardID = shardid; }
 
@@ -172,15 +166,8 @@ private:
 	int32	appearance_id;
 	int32	npc_id;
 	MutexMap<int32, SkillBonus*> skill_bonus_list;
-	int8	m_petType;
-	// m_petSpellID holds the spell id used to create this pet
-	int32	m_petSpellID;
-	int8	m_petSpellTier;
-	int32	owner;
 	int8	m_petMaxLevel;
 
-	bool	m_petDismissing;
-
 	// Because I named the get function Brain() as well we need to use '::' to specify we are refering to
 	// the brain class and not the function defined above
 	::Brain*	m_brain;

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

@@ -83,7 +83,7 @@ void Brain::Think() {
 			m_body->FaceTarget(target);
 
 			bool breakWaterPursuit = false;
-			if (m_body->IsWaterCreature() && !target->InWater())
+			if (m_body->IsWaterCreature() && !m_body->IsFlyingCreature() && !target->InWater())
 				breakWaterPursuit = true;
 			// Check to see if the NPC has exceeded the max chase distance
 			if (run_back_distance > MAX_CHASE_DISTANCE || breakWaterPursuit) {
@@ -131,7 +131,7 @@ void Brain::Think() {
 			CheckBuffs();
 
 			// If run back distance is greater then 0 then run back
-			if(!m_body->EngagedInCombat())
+			if(!m_body->EngagedInCombat() && !m_body->IsPauseMovementTimerActive())
 			{
 				if (run_back_distance > 1) {
 					m_body->Runback(run_back_distance);
@@ -330,7 +330,7 @@ vector<Entity*>* Brain::GetHateList() {
 
 void Brain::MoveCloser(Spawn* target) {
 	if (target && m_body->GetFollowTarget() != target)
-		m_body->SetFollowTarget(target);
+		m_body->SetFollowTarget(target, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat());
 
 	if (m_body->GetFollowTarget() && !m_body->following) {
 		m_body->CalculateRunningLocation(true);

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

@@ -6301,7 +6301,7 @@ NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float
 		npc->SetShardID(dbid);
 		npc->SetShardCharID(GetCharacterID());
 		npc->SetShardCreatedTimestamp(Timer::GetCurrentTime2());
-		
+
 		if(script)
 			npc->SetSpawnScript(script);
 		

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

@@ -959,6 +959,7 @@ public:
 
 	NPC* InstantiateSpiritShard(float origX, float origY, float origZ, float origHeading, int32 origGridID, ZoneServer* origZone);
 
+	void DismissAllPets();
 
 
 	AppearanceData SavedApp;

+ 4 - 0
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -232,6 +232,10 @@ void RuleManager::Init()
 	/* SPAWN */
 	RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...?
 	RULE_INIT(R_Spawn, ClassicRegen, "0");
+	RULE_INIT(R_Spawn, HailMovementPause, "5000"); // time in milliseconds the spawn is paused on hail
+	RULE_INIT(R_Spawn, HailDistance, "5"); // max distance to hail a spawn/npc
+	RULE_INIT(R_Spawn, UseHardCodeWaterModelType, "1"); // uses alternate method of setting water type by model type (hardcoded) versus relying on just DB
+	RULE_INIT(R_Spawn, UseHardCodeFlyingModelType, "1"); // uses alternate method of setting flying type by model type (hardcoded) versus relying on just DB
 
 	/* TIMER */
 

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

@@ -92,6 +92,10 @@ enum RuleType {
 	/* SPAWN */
 	SpeedMultiplier,
 	ClassicRegen,
+	HailMovementPause,
+	HailDistance,
+	UseHardCodeWaterModelType,
+	UseHardCodeFlyingModelType,
 	//SpeedRatio,
 
 	/* UI */

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

@@ -119,6 +119,7 @@ Spawn::Spawn(){
 	region_map = nullptr;
 	current_map = nullptr;
 	RegionMutex.SetName("Spawn::RegionMutex");
+	pause_timer.Disable();
 }
 
 Spawn::~Spawn(){
@@ -2750,7 +2751,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 	Spawn* followTarget = GetZone()->GetSpawnByID(m_followTarget, isSpawnListLocked);
 	if (!followTarget && m_followTarget > 0)
 		m_followTarget = 0;
-	if (following && followTarget && !((Entity*)this)->IsFeared()) {
+	if (following && !IsPauseMovementTimerActive() && followTarget && !((Entity*)this)->IsFeared()) {
 
 		// Need to clear m_followTarget before the zoneserver deletes it
 		if (followTarget->GetHP() <= 0) {
@@ -2767,7 +2768,10 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 				float speed = 4.0f;
 				if (IsEntity())
 					speed = ((Entity*)this)->GetMaxSpeed();
-				SetSpeed(speed);
+				if (IsEntity())
+					((Entity*)this)->SetSpeed(speed);
+				
+					SetSpeed(speed);
 			}
 			MovementLocation* loc = GetCurrentRunningLocation();
 			float dist = GetDistance(followTarget, true);
@@ -2792,7 +2796,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 	}
 
 	// Movement loop is only for scripted paths
-	else if(!EngagedInCombat() && !NeedsToResumeMovement() && movement_loop.size() > 0 && movement_index < movement_loop.size() && (!IsNPC() || !((NPC*)this)->m_runningBack)){
+	else if(!EngagedInCombat() && !IsPauseMovementTimerActive() && !NeedsToResumeMovement() && movement_loop.size() > 0 && movement_index < movement_loop.size() && (!IsNPC() || !((NPC*)this)->m_runningBack)){
 		// Get the target location
 		MovementData* data = movement_loop[movement_index];
 		// need to resume our movement
@@ -2806,6 +2810,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 			}
 
 			data = movement_loop[movement_index];
+			
 			((Entity*)this)->SetSpeed(data->speed);
 			SetSpeed(data->speed);
 			if(!IsWidget())
@@ -2907,7 +2912,7 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 			AddRunningLocation(data->x, data->y, data->z, data->speed);
 		}
 	}
-	else if (IsRunning()) {
+	else if (IsRunning() && !IsPauseMovementTimerActive()) {
 		CalculateRunningLocation();
 	}
 	/*else if (IsNPC() && !IsRunning() && !EngagedInCombat() && ((NPC*)this)->GetRunbackLocation()) {
@@ -2958,6 +2963,9 @@ bool Spawn::IsRunning(){
 }
 
 void Spawn::RunToLocation(float x, float y, float z, float following_x, float following_y, float following_z){
+	if(IsPauseMovementTimerActive())
+		return;
+	
 	if(!IsWidget())
 		FaceTarget(x, z);
 	SetPos(&appearance.pos.X2, x, false);
@@ -3041,7 +3049,7 @@ void Spawn::AddRunningLocation(float x, float y, float z, float speed, float dis
 		SetSpawnOrigHeading(GetHeading());
 	}
 	movement_locations->push_back(data);	
-	if(finished_adding_locations){
+	if(!IsPauseMovementTimerActive() && finished_adding_locations){
 		current_location = movement_locations->front();
 		SetSpeed(current_location->speed);
 		if(movement_locations->size() > 1){		
@@ -3151,14 +3159,15 @@ bool Spawn::CalculateChange(){
 
 void Spawn::CalculateRunningLocation(bool stop){
 
-	if (!stop && (last_location_update + 100) > Timer::GetCurrentTime2())
-		return;
-	else if (!stop)
-	last_location_update = Timer::GetCurrentTime2();
+	bool pauseTimerEnabled = IsPauseMovementTimerActive();
 
+	if (!pauseTimerEnabled && !stop && (last_location_update + 100) > Timer::GetCurrentTime2())
+		return;
+	else if (!pauseTimerEnabled && !stop)
+		last_location_update = Timer::GetCurrentTime2();
 
 	bool removed = CalculateChange();
-	if (stop) {
+	if (stop || pauseTimerEnabled) {
 		//following = false;
 		SetPos(&appearance.pos.X2, GetX(), false);
 		SetPos(&appearance.pos.Y2, GetY(), false);
@@ -3255,13 +3264,14 @@ void Spawn::FaceTarget(float x, float z){
 	SetHeading(angle);
 }
 
-void Spawn::FaceTarget(Spawn* target){
+void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
 	if(!target)
 		return;
 	FaceTarget(target->GetX(), target->GetZ());
 	if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){
 		GetZone()->AddHeadingTimer(this);
-		SetTempActionState(0);
+		if(disable_action_state)
+			SetTempActionState(0);
 	}
 }
 
@@ -3992,4 +4002,85 @@ float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
 	angle = angle * 180.0f / 3.1415f;
 
 	return angle;
+}
+
+bool Spawn::PauseMovement(int32 period_of_time_ms)
+{
+	if(period_of_time_ms < 1)
+		period_of_time_ms = 1;
+	
+	RunToLocation(GetX(),GetY(),GetZ());
+	pause_timer.Start(period_of_time_ms, true);
+
+	return true;
+}
+
+bool Spawn::IsPauseMovementTimerActive()
+{
+	if(pause_timer.Check())
+		pause_timer.Disable();
+	
+	return pause_timer.Enabled();
+}
+
+bool Spawn::IsFlyingCreature()
+{
+	if(!IsEntity())
+		return false;
+
+	return ((Entity*)this)->GetInfoStruct()->get_flying_type();
+}
+
+bool Spawn::IsWaterCreature()
+{
+	if(!IsEntity())
+		return false;
+
+	return ((Entity*)this)->GetInfoStruct()->get_water_type();
+}
+
+
+void Spawn::SetFlyingCreature() {
+	if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeFlyingModelType)->GetInt8())
+		return;
+
+	if(((Entity*)this)->GetInfoStruct()->get_flying_type() > 0) // DB spawn npc flag already set
+		return;
+
+	switch (GetModelType())
+	{
+	case 260:
+	case 295:
+		((Entity*)this)->GetInfoStruct()->set_flying_type(1);
+		is_flying_creature = true;
+		break;
+	default:
+		((Entity*)this)->GetInfoStruct()->set_flying_type(0);
+		break;
+	}
+}
+	
+void Spawn::SetWaterCreature() {
+	if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeWaterModelType)->GetInt8())
+		return;
+
+	if(((Entity*)this)->GetInfoStruct()->get_water_type() > 0) // DB spawn npc flag already set
+		return;
+
+	switch (GetModelType())
+	{
+	case 194:
+	case 204:
+	case 210:
+	case 241:
+	case 242:
+	case 254:
+	case 10668:
+	case 20828:
+		((Entity*)this)->GetInfoStruct()->set_water_type(1);
+		break;
+	default:
+		((Entity*)this)->GetInfoStruct()->set_water_type(0);
+		break;
+	}
 }

+ 10 - 30
EQ2/source/WorldServer/Spawn.h

@@ -781,39 +781,14 @@ public:
 		return appearance.model_type;
 	}
 	
-	bool IsFlyingCreature() { return is_flying_creature; }
-	bool IsWaterCreature() { return is_water_creature; }
+	bool IsFlyingCreature();
+	bool IsWaterCreature();
 	bool InWater();
 	bool InLava();
 
-	void SetFlyingCreature() {
-		is_flying_creature = false;
-		switch (GetModelType())
-		{
-		case 260:
-		case 295:
-			is_flying_creature = true;
-			break;
-		}
-	}
+	void SetFlyingCreature();
+	void SetWaterCreature();
 	
-	void SetWaterCreature() {
-		is_water_creature = false;
-
-		switch (GetModelType())
-		{
-		case 194:
-		case 204:
-		case 210:
-		case 241:
-		case 242:
-		case 254:
-		case 10668:
-		case 20828:
-			is_water_creature = true;
-			break;
-		}
-	}
 	void SetPrimaryCommand(const char* name, const char* command, float distance = 10);
 	void SetPrimaryCommands(vector<EntityCommand*>* commands);
 	void SetSecondaryCommands(vector<EntityCommand*>* commands);
@@ -1035,10 +1010,11 @@ public:
 	bool	NeedsToResumeMovement(){ return attack_resume_needed; }
 	void	NeedsToResumeMovement(bool val) { attack_resume_needed = val; }
 	bool	HasMovementLoop(){ return movement_loop.size() > 0; }
+	bool	HasMovementLocations() { return movement_locations ? movement_locations->size() > 0 : false; }
 	Timer*	GetRunningTimer();
 	float	GetFaceTarget(float x, float z);
 	void	FaceTarget(float x, float z);
-	void	FaceTarget(Spawn* target);
+	void	FaceTarget(Spawn* target, bool disable_action_state = true);
 	void	SetInvulnerable(bool val);
 	bool	GetInvulnerable();
 	bool				changed;
@@ -1205,6 +1181,9 @@ public:
 
 	std::map<std::map<Region_Node*, ZBSP_Node*>, Region_Status> Regions;
 	Mutex RegionMutex;
+
+	virtual bool PauseMovement(int32 period_of_time_ms);
+	virtual bool IsPauseMovementTimerActive();
 protected:
 
 	bool	has_quests_required;
@@ -1243,6 +1222,7 @@ protected:
 	MutexList<SpawnProximity*> spawn_proximities;
 
 	void CheckProximities();
+	Timer pause_timer;
 private:		
 	vector<Item*>	loot_items;
 	int32			loot_coins;

+ 4 - 1
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -900,7 +900,7 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 	NPC* npc = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type\n"
 													"FROM spawn s\n"
 													"INNER JOIN spawn_npcs npc\n"
 													"ON s.id = npc.spawn_id\n"
@@ -1052,6 +1052,9 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 		
 		npc->SetAAXPRewards(atoul(row[80]));
 
+		info->set_water_type(atoul(row[81]));
+		info->set_flying_type(atoul(row[82]));
+		
 		zone->AddNPC(id, npc);
 		total++;
 		LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id);

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

@@ -3846,11 +3846,7 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords) {
 	//GetCurrentZone()->GetCombat()->RemoveHate(player);
 
 	// Remove players pet from zone if there is one
-	player->DismissPet((NPC*)player->GetPet());
-	player->DismissPet((NPC*)player->GetCharmedPet());
-	player->DismissPet((NPC*)player->GetDeityPet());
-	player->DismissPet((NPC*)player->GetCosmeticPet());
-
+	((Entity*)player)->DismissAllPets();
 
 	LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__);
 	GetCurrentZone()->RemoveSpawn(player, false);

+ 22 - 11
EQ2/source/WorldServer/zoneserver.cpp

@@ -721,10 +721,24 @@ void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) {
 		for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 			spawn = itr->second;
 			if(spawn && !spawn->IsPlayer()){
+				bool dispatched = false;
 				if(spawn->IsBot())
-				((Bot*)spawn)->Camp(true);
-
-				SendRemoveSpawn(client, spawn, packet);
+				{
+					((Bot*)spawn)->Camp(true);
+					dispatched = true;
+				}
+				else if(spawn->IsPet())
+				{
+					Entity* owner = ((Entity*)spawn)->GetOwner();
+					if(owner)
+					{
+						owner->DismissPet((Entity*)spawn);
+						dispatched = true;
+					}
+				}
+				
+				if(!dispatched)
+					SendRemoveSpawn(client, spawn, packet);
 			}
 		}
 		MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
@@ -2655,7 +2669,7 @@ bool ZoneServer::CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn, const char
 				break;
 									   }
 			case SPAWN_SCRIPT_HAILED:{
-				lua_interface->RunSpawnScript(script, "hailed", npc, spawn);
+				result = lua_interface->RunSpawnScript(script, "hailed", npc, spawn);
 				break;
 									 }
 			case SPAWN_SCRIPT_HAILED_BUSY:{
@@ -2978,10 +2992,7 @@ void ZoneServer::RemoveClient(Client* client)
 				LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to Camp/Quit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID());
 			}
 				
-				client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetPet());
-				client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetCharmedPet());
-				client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetDeityPet());
-				client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetCosmeticPet());
+				((Entity*)client->GetPlayer())->DismissAllPets();
 			//}
 		}
 		else
@@ -2992,7 +3003,7 @@ void ZoneServer::RemoveClient(Client* client)
 		map<int32, int32>::iterator itr;
 		for (itr = client->GetPlayer()->SpawnedBots.begin(); itr != client->GetPlayer()->SpawnedBots.end(); itr++) {
 			Spawn* spawn = GetSpawnByID(itr->second);
-			if (spawn && spawn->IsBot())
+			if (spawn)
 				((Bot*)spawn)->Camp();
 		}
 
@@ -6659,8 +6670,8 @@ void ZoneServer::DismissAllPets() {
 	MSpawnList.readlock(__FUNCTION__, __LINE__);
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
 		spawn = itr->second;
-		if (spawn && spawn->IsPet() && ((NPC*)spawn)->GetOwner())
-			((NPC*)spawn)->GetOwner()->DismissPet((NPC*)spawn);
+		if (spawn && spawn->IsEntity())
+			((Entity*)spawn)->DismissAllPets();
 	}
 	MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
 }