Browse Source

PVP Invisibility Support

Fixes #42

- Invisibility support for PVP
- Rule R_PVP InvisPlayerDiscoveryRange added.  Default 20.  Setting to 0 means invis players are always seen.  -1 means they are never seen (unless their opponent has see invis for example)
- Crash fix back in ProcessSpawnConditional, spawn_list is getting a null spawn somehow, we can't process it!
Image 4 years ago
parent
commit
4e940f6b7c

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

@@ -1627,6 +1627,10 @@ bool Entity::IsStealthed(){
 	return  (!stealth_list || stealth_list->size(true) == 0) == false;
 }
 
+bool Entity::CanSeeInvis(Entity* target) {
+	return true;
+}
+
 bool Entity::IsInvis(){
 	MutexList<LuaSpell*>* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS];
 	return  (!invis_list || invis_list->size(true) == 0) == false;

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

@@ -764,6 +764,8 @@ public:
 	vector<DetrimentalEffects>* GetDetrimentalSpellEffects();
 	void RemoveEffectsFromLuaSpell(LuaSpell* spell);
 	virtual void RemoveSkillBonus(int32 spell_id);
+
+	virtual bool CanSeeInvis(Entity* target);
 	void CancelAllStealth();
 	bool IsStealthed();
 	bool IsInvis();

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

@@ -994,6 +994,19 @@ bool Item::CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level)
 void Item::AddStat(ItemStat* in_stat){
 	item_stats.push_back(in_stat);
 }
+
+bool Item::HasStat(uint32 statID)
+{
+	vector<ItemStat*>::iterator itr;
+	for (itr = item_stats.begin(); itr != item_stats.end(); itr++) {
+		if ((*itr)->stat_type_combined == statID) {
+			return true;
+			break;
+		}
+	}
+
+	return false;
+}
 void Item::AddSet(ItemSet* in_set){
 	item_sets.push_back(in_set);
 }

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

@@ -849,6 +849,7 @@ public:
 	void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue);
 	void SetAppearance(ItemAppearance* appearance);
 	void AddStat(ItemStat* in_stat);
+	bool HasStat(uint32 statID);
 	void AddSet(ItemSet* in_set);
 	void AddStatString(ItemStatString* in_stat);
 	void AddStat(int8 type, int16 subtype, float value, char* name = 0);

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

@@ -5434,3 +5434,84 @@ LUAHistory* Player::GetLUAHistory(int32 event_id) {
 	return ret;
 }
 
+bool Player::CanSeeInvis(Entity* target)
+{
+	if (!target->IsStealthed() && !target->IsInvis())
+		return true;
+
+	sint32 radius = rule_manager.GetGlobalRule(R_PVP, InvisPlayerDiscoveryRange)->GetSInt32();
+
+	if (radius == 0) // radius of 0 is always seen
+		return true;
+	// radius of -1 is never seen except through items/spells, radius > -1 means we will show the player if they get into the inner radius
+	else if (radius > -1 && this->GetDistance((Spawn*)target) < (float)radius)
+		return true;
+
+	// TODO: Implement See Invis Spells! http://cutpon.com:3000/devn00b/EQ2EMu/issues/43
+
+	Item* item = 0;
+	vector<Item*>* equipped_list = GetEquippedItemList();
+	bool seeInvis = false;
+	bool seeStealth = false;
+	for (int32 i = 0; i < equipped_list->size(); i++)
+	{
+		item = equipped_list->at(i);
+		seeInvis = item->HasStat(ITEM_STAT_SEEINVIS);
+		seeStealth = item->HasStat(ITEM_STAT_SEESTEALTH);
+		if (target->IsStealthed() && seeStealth)
+			return true;
+		else if (target->IsInvis() && seeInvis)
+			return true;
+	}
+
+	return false;
+}
+
+// returns true if we need to update target info due to see invis status change
+bool Player::CheckChangeInvisHistory(Entity* target)
+{
+	std::map<int32, bool>::iterator it;
+
+	it = target_invis_history.find(target->GetID());
+	if (it != target_invis_history.end())
+	{
+		//canSeeStatus
+		if (it->second)
+		{
+			if (!this->CanSeeInvis(target))
+			{
+				UpdateTargetInvisHistory(target->GetID(), false);
+				return true;
+			}
+			else
+				return false;
+		}
+		else
+		{
+			if (this->CanSeeInvis(target))
+			{
+				UpdateTargetInvisHistory(target->GetID(), true);
+				return true;
+			}
+			else
+				return false;
+		}
+	}
+
+	if (!this->CanSeeInvis(target))
+		UpdateTargetInvisHistory(target->GetID(), false);
+	else
+		UpdateTargetInvisHistory(target->GetID(), true);
+
+	return true;
+}
+
+void Player::UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus)
+{
+	target_invis_history[targetID] = canSeeStatus;
+}
+
+void Player::RemoveTargetInvisHistory(int32 targetID)
+{
+	target_invis_history.erase(targetID);
+}

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

@@ -667,6 +667,12 @@ public:
 	void				AddSkillBonus(int32 spell_id, int32 skill_id, float value);
 	SkillBonus*			GetSkillBonus(int32 spell_id);
 	virtual void		RemoveSkillBonus(int32 spell_id);
+
+	virtual bool		CanSeeInvis(Entity* target);
+	bool				CheckChangeInvisHistory(Entity* target);
+	void				UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus);
+	void				RemoveTargetInvisHistory(int32 targetID);
+
 	bool				HasFreeBankSlot();
 	int8				FindFreeBankSlot();
 	PlayerCollectionList * GetCollectionList() { return &collection_list; }
@@ -867,6 +873,8 @@ private:
 	float               pos_packet_speed;
 	PlayerControlFlags  control_flags;
 
+	map<int32, bool>	target_invis_history;
+
 	// JA: POI Discoveries
 	map<int32, vector<int32> >	players_poi_list;
 

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

@@ -205,6 +205,7 @@ RuleManager::RuleManager() {
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");
 	RULE_INIT(R_PVP, LevelRange, "4");
+	RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen
 
 	/* 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...?

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

@@ -70,6 +70,7 @@ enum RuleType {
 	/* PVP */
 	AllowPVP,
 	LevelRange,
+	InvisPlayerDiscoveryRange,
 
 	/* SPAWN */
 	SpeedMultiplier,

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

@@ -1439,7 +1439,13 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 
 void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet){
 	int16 version = packet->GetVersion();
-	if(appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1){
+
+	bool hiddenInPVP = false;
+	bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool();
+	if (pvp_allowed && (Spawn*)spawn != this && this->IsPlayer() && !spawn->CanSeeInvis((Entity*)this))
+			hiddenInPVP = true;
+
+	if(!hiddenInPVP && (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1)){
 		appearance.locked_no_loot = 1; //for now
 		if(!IsObject() && !IsGroundSpawn() && !IsWidget() && !IsSign()){
 			int8 percent = 0;
@@ -1482,11 +1488,18 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet){
 			model_type = GetIllusionModel();
 	}
 
+	int16 sogaModelType = appearance.soga_model_type;
+	if (hiddenInPVP)
+	{
+		model_type = 0;
+		sogaModelType = 0;
+	}
+
 	packet->setDataByName("model_type", model_type);
 	if(appearance.soga_model_type == 0)
 		packet->setDataByName("soga_model_type", model_type);
 	else
-		packet->setDataByName("soga_model_type", appearance.soga_model_type);
+		packet->setDataByName("soga_model_type", sogaModelType);
 
 	if(GetTempActionState() >= 0)
 		packet->setDataByName("action_state", GetTempActionState());

+ 38 - 2
EQ2/source/WorldServer/client.cpp

@@ -2527,6 +2527,7 @@ bool Client::Process(bool zone_process) {
 	}
 	if(pos_update.Check() && player_pos_changed){
 		//GetPlayer()->CalculateLocation();
+		client_list.CheckPlayersInvisStatus(this);
 		GetCurrentZone()->SendPlayerPositionChanges(GetPlayer());
 		player_pos_changed = false;
 		GetCurrentZone()->CheckTransporters(this);
@@ -2629,15 +2630,50 @@ void ClientList::ReloadQuests() {
 	list<Client*>::iterator client_iter;
 	Client* client = 0;
 	MClients.readlock(__FUNCTION__, __LINE__);
-	for(client_iter=client_list.begin(); client_iter!=client_list.end(); client_iter++){
+	for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) {
 		client = *client_iter;
-		if(client)
+		if (client)
 			client->ReloadQuests();
 	}
 	MClients.releasereadlock(__FUNCTION__, __LINE__);
 
 }
 
+void ClientList::CheckPlayersInvisStatus(Client* owner) {
+	if ( !owner->GetPlayer() || (!owner->GetPlayer()->IsInvis() && !owner->GetPlayer()->IsStealthed()) )
+		return;
+
+	list<Client*>::iterator client_iter;
+	Client* client = 0;
+	MClients.readlock(__FUNCTION__, __LINE__);
+	for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) {
+		client = *client_iter;
+		if (client == owner || client->GetPlayer() == NULL)
+			continue;
+
+		if (client->GetPlayer()->CheckChangeInvisHistory((Entity*)owner->GetPlayer()))
+			client->GetPlayer()->GetZone()->SendSpawnChanges(owner->GetPlayer(), client, true, true);
+	}
+	MClients.releasereadlock(__FUNCTION__, __LINE__);
+
+}
+
+void ClientList::RemovePlayerFromInvisHistory(int32 spawnID) {
+	list<Client*>::iterator client_iter;
+	Client* client = 0;
+	MClients.readlock(__FUNCTION__, __LINE__);
+	for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) {
+		client = *client_iter;
+		if (!client->GetPlayer())
+			continue;
+
+		client->GetPlayer()->RemoveTargetInvisHistory(spawnID);
+	}
+	MClients.releasereadlock(__FUNCTION__, __LINE__);
+}
+
+
+
 int32 ClientList::Count(){
 	return client_list.size();
 }

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

@@ -502,6 +502,8 @@ public:
 	void	Process();
 	int32	Count();
 	void	ReloadQuests();
+	void	CheckPlayersInvisStatus(Client* owner);
+	void	RemovePlayerFromInvisHistory(int32 spawnID);
 private:
 	Mutex	MClients;
 	list<Client*> client_list;

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

@@ -2823,6 +2823,9 @@ void ZoneServer::RemoveClient(Client* client)
 
 	if(client)
 	{
+		if (client->GetPlayer()) 
+			client_list.RemovePlayerFromInvisHistory(client->GetPlayer()->GetID());
+
 		LogWrite(ZONE__DEBUG, 0, "Zone", "Sending login equipment appearance updates...");
 		loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID());
 
@@ -7054,10 +7057,13 @@ void ZoneServer::ProcessSpawnConditional(int8 condition) {
 	MSpawnList.readlock(__FUNCTION__, __LINE__);
 	map<int32, Spawn*>::iterator itr;
 	for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) {
-		SpawnLocation* loc = spawn_location_list[itr->second->GetSpawnLocationID()];
-		if (loc && loc->conditional > 0) {
-			if ((loc->conditional & condition) != condition) {
-				Despawn(itr->second, 0);
+		if (itr->second != NULL) // null itr->second still coming into ProcessSpawnConditional
+		{
+			SpawnLocation* loc = spawn_location_list[itr->second->GetSpawnLocationID()];
+			if (loc && loc->conditional > 0) {
+				if ((loc->conditional & condition) != condition) {
+					Despawn(itr->second, 0);
+				}
 			}
 		}
 	}