Browse Source

Fix #318 - added spawn set scared_strong_players [0|1]. NPCs will be afraid if gray con and in aggro range, needs faction to check aggro list

Emagi 1 year ago
parent
commit
2ea1982355

+ 1 - 0
DB/updates/spawnnpc_update_feb12_2023.sql

@@ -0,0 +1 @@
+alter table spawn_npcs add column scared_by_strong_players tinyint(3) unsigned not null default 0;

+ 26 - 0
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -201,6 +201,7 @@ Commands::Commands(){
 	spawn_set_values["race_type"] = SPAWN_SET_RACE_TYPE;
 	spawn_set_values["loot_tier"] = SPAWN_SET_LOOT_TIER;
 	spawn_set_values["loot_drop_type"] = SPAWN_SET_LOOT_DROP_TYPE;
+	spawn_set_values["scared_strong_players"] = SPAWN_SET_SCARED_STRONG_PLAYERS;
 
 	zone_set_values["expansion_id"] = ZONE_SET_VALUE_EXPANSION_ID;
 	zone_set_values["name"] = ZONE_SET_VALUE_NAME;
@@ -898,6 +899,14 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				}
 				break;
 			}
+			case SPAWN_SET_SCARED_STRONG_PLAYERS:{
+				if(target->IsNPC()){
+					sprintf(tmp, "%u", target->IsScaredByStrongPlayers());
+					int32 new_value = atoul(value);
+					target->SetScaredByStrongPlayers(new_value);
+				}
+				break;
+			}
 
 			if(temp_value)
 				*temp_value = string(tmp);
@@ -1604,6 +1613,22 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				}
 				break;
 			}
+			case SPAWN_SET_SCARED_STRONG_PLAYERS:{
+				int32 new_value = atoul(value);
+				target->SetScaredByStrongPlayers(new_value);
+				
+				if (target->GetDatabaseID() > 0)
+				{
+					char query[256];
+					snprintf(query, 256, "update spawn_npcs set scared_by_strong_players=%u where id=%u", atoul(value), target->GetDatabaseID());
+					if (database.RunQuery(query, strnlen(query, 256)))
+					{
+						if(client)
+							client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query);
+					}
+				}
+				break;
+			}
 		}
 	}
 	return true;
@@ -4959,6 +4984,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 								case SPAWN_SET_RACE_TYPE:
 								case SPAWN_SET_LOOT_TIER:
 								case SPAWN_SET_LOOT_DROP_TYPE:
+								case SPAWN_SET_SCARED_STRONG_PLAYERS:
 								{
 									// not applicable already ran db command
 									break;

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

@@ -571,6 +571,7 @@ private:
 #define SPAWN_SET_RACE_TYPE								103
 #define SPAWN_SET_LOOT_TIER								104
 #define SPAWN_SET_LOOT_DROP_TYPE						105
+#define SPAWN_SET_SCARED_STRONG_PLAYERS					106
 
 #define ZONE_SET_VALUE_EXPANSION_ID			0
 #define ZONE_SET_VALUE_NAME					1

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

@@ -104,6 +104,7 @@ NPC::NPC(NPC* old_npc){
 		SetLootTier(old_npc->GetLootTier());
 		SetLootDropType(old_npc->GetLootDropType());
 		has_spells = old_npc->HasSpells();
+		SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers());
 	}
 }
 

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

@@ -6940,3 +6940,39 @@ void Player::GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count
 		}
 	}
 }
+
+
+bool Player::IsSpawnInRangeList(int32 spawn_id) {
+    std::shared_lock lock(spawn_aggro_range_mutex);
+	map<int32, bool>::iterator spawn_itr = player_aggro_range_spawns.find(spawn_id);
+	if(spawn_itr != player_aggro_range_spawns.end()) {
+		return spawn_itr->second;
+	}
+	return false;
+}
+
+void Player::SetSpawnInRangeList(int32 spawn_id, bool in_range) {
+    std::unique_lock lock(spawn_aggro_range_mutex);
+	player_aggro_range_spawns[spawn_id] = in_range;
+}
+
+void Player::ProcessSpawnRangeUpdates() {
+    std::unique_lock lock(spawn_aggro_range_mutex);
+	if(GetClient()->GetCurrentZone() == nullptr) {
+		return;
+	}
+	
+	map<int32, bool>::iterator spawn_itr;
+	for(spawn_itr = player_aggro_range_spawns.begin(); spawn_itr != player_aggro_range_spawns.end();) {
+		if(spawn_itr->second) {
+			Spawn* spawn = GetClient()->GetCurrentZone()->GetSpawnByID(spawn_itr->first);
+			if(spawn && spawn->IsNPC() && (GetDistance(spawn)) > ((NPC*)spawn)->GetAggroRadius()) {
+				GetClient()->GetCurrentZone()->SendSpawnChanges((NPC*)spawn, GetClient(), true, true);
+				spawn_itr->second = false;
+				spawn_itr = player_aggro_range_spawns.erase(spawn_itr);
+				continue;
+			}
+		}
+		spawn_itr++;
+	}
+}

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

@@ -973,6 +973,7 @@ public:
 	Mutex vis_mutex;
 	Mutex index_mutex;
 	Mutex spawn_mutex;
+	mutable std::shared_mutex spawn_aggro_range_mutex;
 
 	void SetTempMount(int32 id) { tmp_mount_model = id; }
 	int32 GetTempMount() { return tmp_mount_model; }
@@ -1052,6 +1053,10 @@ public:
 	void	SetActiveReward(bool val) { active_reward = val; }
 	bool	IsActiveReward() { return active_reward; }
 	
+	
+	bool	IsSpawnInRangeList(int32 spawn_id);
+	void	SetSpawnInRangeList(int32 spawn_id, bool in_range);
+	void	ProcessSpawnRangeUpdates();
 	Mutex MPlayerQuests;
 	float   pos_packet_speed;
 private:
@@ -1179,6 +1184,7 @@ private:
 
 	map<int32, Spawn*>	player_spawn_id_map;
 	map<Spawn*, int32>	player_spawn_reverse_id_map;
+	map<int32, bool>	player_aggro_range_spawns;
 
 	bool all_spells_locked;
 	Timer lift_cooldown;

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

@@ -137,6 +137,7 @@ Spawn::Spawn(){
 	deleted_spawn = false;
 	is_collector = false;
 	trigger_widget_id = 0;
+	scared_by_strong_players = false;
 }
 
 Spawn::~Spawn(){
@@ -2354,14 +2355,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	packet->setDataByName("difficulty", appearance.encounter_level); //6);
 	packet->setDataByName("unknown6", 1);
 	packet->setDataByName("heroic_flag", appearance.heroic_flag);
-	if (IsNPC() && !IsPet())
-	{
-		if(((Entity*)this)->GetInfoStruct()->get_interaction_flag())
-			packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12)
-		else
-			packet->setDataByName("interaction_flag", 12); //turn head since no other value is set
-	}
-
 	packet->setDataByName("class", appearance.adventure_class);
 
 	int16 model_type = appearance.model_type;
@@ -2393,12 +2386,28 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	else
 		packet->setDataByName("action_state", appearance.action_state);
 	
+	bool scaredOfPlayer = false;
+	
 	if(IsCollector() && spawn->GetCollectionList()->HasCollectionsToHandIn())
-		packet->setDataByName("visual_state", 6674);
+		packet->setDataByName("visual_state", VISUAL_STATE_COLLECTION_TURN_IN);
+	else if(!IsRunning() && IsNPC() && IsScaredByStrongPlayers() && spawn->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY &&
+	(GetDistance(spawn)) <= ((NPC*)this)->GetAggroRadius() && CheckLoS(spawn)) {
+		packet->setDataByName("visual_state", VISUAL_STATE_IDLE_AFRAID);
+		scaredOfPlayer = true;
+	}
 	else if (GetTempVisualState() >= 0)
 		packet->setDataByName("visual_state", GetTempVisualState());
 	else
 		packet->setDataByName("visual_state", appearance.visual_state);
+	
+	if (IsNPC() && !IsPet() && !scaredOfPlayer)
+	{
+		if(((Entity*)this)->GetInfoStruct()->get_interaction_flag())
+			packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12)
+		else
+			packet->setDataByName("interaction_flag", 12); //turn head since no other value is set
+	}
+	
 	packet->setDataByName("emote_state", appearance.emote_state);
 	packet->setDataByName("mood_state", appearance.mood_state);
 	packet->setDataByName("gender", appearance.gender);

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

@@ -165,6 +165,9 @@
 #define ENCOUNTER_STATE_OVERMATCHED			4
 #define ENCOUNTER_STATE_NO_REWARD			5
 
+#define VISUAL_STATE_COLLECTION_TURN_IN		6674
+#define VISUAL_STATE_IDLE_AFRAID			17953
+
 using namespace std;
 class Spell;
 class ZoneServer;
@@ -1084,10 +1087,12 @@ public:
 	void	FaceTarget(Spawn* target, bool disable_action_state = true);
 	void	SetInvulnerable(bool val);
 	bool	GetInvulnerable();
-	bool				changed;
-	bool				position_changed;
-	bool				info_changed;
-	bool				vis_changed;
+	void	SetScaredByStrongPlayers(bool val) { scared_by_strong_players = val; }
+	bool	IsScaredByStrongPlayers() { return scared_by_strong_players; }
+	std::atomic<bool>	changed;
+	std::atomic<bool>	position_changed;
+	std::atomic<bool> 	info_changed;
+	std::atomic<bool>	vis_changed;
 	int16				size;
 	int32				faction_id;
 	int8				oversized_packet; //0xff
@@ -1397,6 +1402,7 @@ private:
 	std::atomic<bool> deleted_spawn;
 	Mutex m_GridMutex;
 	bool is_collector;
+	bool scared_by_strong_players;
 };
 
 #endif

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

@@ -932,7 +932,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, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type\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, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players\n"
 													"FROM spawn s\n"
 													"INNER JOIN spawn_npcs npc\n"
 													"ON s.id = npc.spawn_id\n"
@@ -1092,6 +1092,8 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 		
 		npc->SetLootDropType(atoul(row[84]));
 		
+		npc->SetScaredByStrongPlayers(atoul(row[85]));
+		
 		zone->AddNPC(id, npc);
 		total++;
 		LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id);
@@ -6695,7 +6697,7 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 										
-	database_new.Select(&result, "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.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n"
+	database_new.Select(&result, "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.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_npcs npc\n"
 								 "ON npc.spawn_id = s.id\n"
@@ -6831,6 +6833,8 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 
 		npc->SetLootDropType(result.GetInt32(80));
 		
+		npc->SetScaredByStrongPlayers(result.GetInt32(81));
+		
 		zone->AddNPC(id, npc);
 
 		//skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load

+ 10 - 2
EQ2/source/WorldServer/zoneserver.cpp

@@ -926,9 +926,17 @@ bool ZoneServer::CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client){
 		return true;
 
 	if (client) {
+		int8 arrow = 0;
 		if (client->IsReadyForUpdates() && npc->CanSeeInvis(client->GetPlayer()) && client->GetPlayer()->GetFactions()->ShouldAttack(npc->GetFactionID()) && npc->AttackAllowed((Entity*)victim, false)) {
-			if (!npc->EngagedInCombat() && client->GetPlayer()->GetArrowColor(npc->GetLevel()) != ARROW_COLOR_GRAY) {
-				AggroVictim(npc, victim, client);
+			if (!npc->EngagedInCombat()) {
+				if(client->GetPlayer()->GetArrowColor(npc->GetLevel()) != ARROW_COLOR_GRAY) {
+					AggroVictim(npc, victim, client);
+				}
+				else if(npc->IsScaredByStrongPlayers() &&
+						!client->GetPlayer()->IsSpawnInRangeList(npc->GetID())) {
+					SendSpawnChanges(npc, client, true, true);
+					client->GetPlayer()->SetSpawnInRangeList(npc->GetID(), true);
+				}
 			}
 		}
 	}