diff --git a/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs b/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs
index 6021931..d0a84b8 100644
--- a/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs
+++ b/project/SPT.SinglePlayer/Models/Progression/LighthouseProgressionClass.cs
@@ -1,5 +1,8 @@
-using Comfort.Common;
+using BepInEx.Logging;
+using Comfort.Common;
using EFT;
+using SPT.Reflection.Patching;
+using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@@ -8,123 +11,236 @@ namespace SPT.SinglePlayer.Models.Progression
{
public class LighthouseProgressionClass : MonoBehaviour
{
+ ///
+ /// Flag to disable mines and AI data that instructs Zryachiy to attack you if the main player is authorized to enter Lightkeeper Island
+ ///
+ public static bool MainPlayerControlsIslandAccessForEveryone { get; set; } = true;
+
+ ///
+ /// Flag indicating if the Lightkeeper-Island bridge mines and AI data that instructs Zryachiy to attack you have been disabled for everyone
+ ///
+ public bool IsIslandOpenForEveryone { get; private set; } = false;
+
+ private static readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
+ private static readonly string _lightKeeperTid = "638f541a29ffd1183d187f57";
+
private GameWorld _gameWorld;
- private Player _player;
- private float _timer;
- private List _bridgeMines;
- private RecodableItemClass _transmitter;
- private readonly List _zryachiyAndFollowers = new List();
- private bool _aggressor;
- private bool _isDoorDisabled;
- private readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
- private readonly string _lightKeeperTid = "638f541a29ffd1183d187f57";
+ private ManualLogSource _logger;
+ private List lightkeeperFriendlyPlayers = new List();
+ private List playersOnIsland = new List();
+
+ ///
+ /// PMC's that have been reported to be Lightkeeper-friendly
+ ///
+ public IReadOnlyList LightkeeperFriendlyPlayers => lightkeeperFriendlyPlayers.AsReadOnly();
+
+ ///
+ /// PMC's that have been reported to be on Lightkeeper Island
+ ///
+ public IReadOnlyList LightkeeperFriendlyPlayersOnIsland => playersOnIsland.AsReadOnly();
public void Start()
{
_gameWorld = Singleton.Instance;
- _player = _gameWorld?.MainPlayer;
- if (_gameWorld == null || _player == null)
+ if (_gameWorld == null || _gameWorld.MainPlayer == null)
{
- Destroy(this);
-
return;
}
+ _logger = BepInEx.Logging.Logger.CreateLogSource(nameof(ModulePatch));
- // Get transmitter from players inventory
- _transmitter = GetTransmitterFromInventory();
+ // Watch for Zryachiy and his followers to spawn
+ Singleton.Instance.BotsController.BotSpawner.OnBotCreated += botCreated;
// Exit if transmitter does not exist and isnt green
- if (!PlayerHasActiveTransmitterInInventory())
+ if (CheckAndAddLightkeeperFriendlyPlayer(_gameWorld.MainPlayer) && MainPlayerControlsIslandAccessForEveryone)
{
- Destroy(this);
-
- return;
+ AllowEveryoneAccessToLightkeeperIsland();
}
-
- var places = Singleton.Instance.BotsController.CoversData.AIPlaceInfoHolder.Places;
-
- places.First(x => x.name == "Attack").gameObject.SetActive(false);
-
- // Zone was added in a newer version and the gameObject actually has a \
- places.First(y => y.name == "CloseZone\\").gameObject.SetActive(false);
-
- // Give access to Lightkeepers door
- _gameWorld.BufferZoneController.SetPlayerAccessStatus(_player.ProfileId, true);
-
- _bridgeMines = _gameWorld.MineManager.Mines;
-
- // Set mines to be non-active
- SetBridgeMinesStatus(false);
}
- public void Update()
+ ///
+ /// Check if the player has been added to the Lightkeeper-friendly PMC list
+ ///
+ public bool IsALightkeeperFriendlyPlayer(IPlayer player)
{
- IncrementLastUpdateTimer();
+ return player != null && lightkeeperFriendlyPlayers.Contains(player);
+ }
- // Exit early if last update() run time was < 10 secs ago
- if (_timer < 10f)
+ ///
+ /// Check if the player has been added to the list of Lightkeeper-friendly PMC's on Lightkeeper Island
+ ///
+ public bool IsLightkeeperFriendlyPlayerOnIsland(IPlayer player)
+ {
+ return player != null && playersOnIsland.Contains(player);
+ }
+
+ ///
+ /// Checks if the player has an active transmitter in its inventory, and if so add it to the Lightkeeper-friendly PMC list
+ ///
+ /// True if the player was added to the Lightkeeper-friendly PMC list
+ public bool CheckAndAddLightkeeperFriendlyPlayer(IPlayer player)
+ {
+ if (PlayerHasActiveTransmitterInInventory(player))
+ {
+ return AddLightkeeperFriendlyPlayer(player);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Add the player to the Lightkeeper-friendly PMC list
+ ///
+ /// True if the player was added to the Lightkeeper-friendly PMC list
+ public bool AddLightkeeperFriendlyPlayer(IPlayer player)
+ {
+ if (player == null)
+ {
+ return false;
+ }
+
+ if (lightkeeperFriendlyPlayers.Contains(player))
+ {
+ _logger.LogWarning($"{player.Profile.Nickname} is already a registered Lightkeeper-friendly player");
+ return false;
+ }
+
+ lightkeeperFriendlyPlayers.Add(player);
+
+ // Give access to Lightkeepers door
+ _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, true);
+
+ return true;
+ }
+
+ ///
+ /// Remove the player from the Lightkeeper-friendly PMC list
+ ///
+ /// True if the player was removed from the Lightkeeper-friendly PMC list
+ public bool RemoveLightkeeperFriendlyPlayer(IPlayer player)
+ {
+ if (player == null)
+ {
+ return false;
+ }
+
+ if (!lightkeeperFriendlyPlayers.Contains(player))
+ {
+ _logger.LogWarning($"{player.Profile.Nickname} is not a registered Lightkeeper-friendly player");
+ return false;
+ }
+
+ lightkeeperFriendlyPlayers.Remove(player);
+
+ // Revoke access to Lightkeepers door
+ _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, false);
+
+ return true;
+ }
+
+ ///
+ /// Add the player to the list of PMC's that are on Lightkeeper Island
+ ///
+ public void LightkeeperFriendlyPlayerEnteredIsland(Player player)
+ {
+ if (playersOnIsland.Contains(player))
+ {
+ _logger.LogWarning($"{player.name} is already a registered player on Lightkeeper Island");
+ return;
+ }
+
+ playersOnIsland.Add(player);
+ player.OnPlayerDead += OnLightkeeperFriendlyPlayerDead;
+ }
+
+ ///
+ /// Remove the player from the list of PMC's that are on Lightkeeper Island
+ ///
+ public void LightkeeperFriendlyPlayerLeftIsland(Player player)
+ {
+ if (!playersOnIsland.Contains(player))
+ {
+ _logger.LogWarning($"{player.name} is not a registered player on Lightkeeper Island");
+ return;
+ }
+
+ playersOnIsland.Remove(player);
+ player.OnPlayerDead -= OnLightkeeperFriendlyPlayerDead;
+ }
+
+ ///
+ /// Disables brige mines, disables AI data to instruct Zryachiy to attack you, and watch for Zryachiy and his followers to spawn
+ ///
+ public void AllowEveryoneAccessToLightkeeperIsland()
+ {
+ if (IsIslandOpenForEveryone)
{
return;
}
- // Skip if:
- // GameWorld missing
- // Player not an enemy to Zryachiy
- // Lk door not accessible
- // Player has no transmitter on thier person
- if (_gameWorld == null || _isDoorDisabled || _transmitter == null)
- {
- return;
- }
+ DisableAIPlaceInfoForZryachiy();
- // Find Zryachiy and prep him
- if (_zryachiyAndFollowers.Count == 0)
- {
- SetupZryachiyAndFollowerHostility();
- }
+ // Set mines to be non-active
+ SetBridgeMinesStatus(false);
- // If player becomes aggressor, block access to LK
- if (_aggressor)
- {
- DisableAccessToLightKeeper();
- }
+ IsIslandOpenForEveryone = true;
+ }
+
+ ///
+ /// Disable the "Attack" and "CloseZone" AIPlaceInfo objects that instruct Zryachiy and his followers to attack you
+ ///
+ public void DisableAIPlaceInfoForZryachiy()
+ {
+ var places = Singleton.Instance.BotsController.CoversData.AIPlaceInfoHolder.Places;
+
+ places.First(x => x.name == "Attack").gameObject.SetActive(false);
+
+ // Zone was added in a newer version and the gameObject actually has a \
+ places.First(y => y.name == "CloseZone\\").gameObject.SetActive(false);
}
///
/// Gets transmitter from players inventory
///
- private RecodableItemClass GetTransmitterFromInventory()
+ public RecodableItemClass GetTransmitterFromInventory(IPlayer player)
{
- return (RecodableItemClass) _player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
+ if (player == null)
+ {
+ return null;
+ }
+
+ return (RecodableItemClass)player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
}
///
/// Checks for transmitter status and exists in players inventory
///
- private bool PlayerHasActiveTransmitterInInventory()
+ public bool PlayerHasActiveTransmitterInInventory(IPlayer player)
{
- return _transmitter != null &&
- _transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
+ RecodableItemClass transmitter = GetTransmitterFromInventory(player);
+ return IsTransmitterActive(transmitter);
}
///
- /// Update _time to diff from last run of update()
+ /// Check if the transmitter allows access to the island
///
- private void IncrementLastUpdateTimer()
+ public bool IsTransmitterActive(RecodableItemClass transmitter)
{
- _timer += Time.deltaTime;
+ return transmitter != null && transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
}
///
/// Set all brdige mines to desire state
///
/// What state should bridge mines be set to
- private void SetBridgeMinesStatus(bool desiredMineState)
+ public void SetBridgeMinesStatus(bool desiredMineState)
{
- // Find mines with opposite state of what we want
- var mines = _bridgeMines.Where(mine => mine.gameObject.activeSelf == !desiredMineState && mine.transform.parent.gameObject.name == "Directional_mines_LHZONE");
+ // Find mines with opposite state of what we want
+ var mines = _gameWorld.MineManager.Mines
+ .Where(mine => IsLighthouseBridgeMine(mine) && mine.gameObject.activeSelf == !desiredMineState);
+
foreach (var mine in mines)
{
mine.gameObject.SetActive(desiredMineState);
@@ -132,61 +248,98 @@ namespace SPT.SinglePlayer.Models.Progression
}
///
- /// Put Zryachiy and followers into a list and sub to their death event
- /// Make player agressor if player kills them.
+ /// Check if the mine is on the Lightkeeper Island bridge
///
- private void SetupZryachiyAndFollowerHostility()
+ /// True if the mine is on the Lightkeeper Island bridge
+ public static bool IsLighthouseBridgeMine(MineDirectional mine)
{
- // Only process non-players (ai)
- foreach (var aiBot in _gameWorld.AllAlivePlayersList.Where(x => !x.IsYourPlayer))
+ if (mine == null)
{
- // Bots that die on mounted guns get stuck in AllAlivePlayersList, need to check health
- if (!aiBot.HealthController.IsAlive)
+ return false;
+ }
+
+ return mine.transform.parent.gameObject.name == "Directional_mines_LHZONE";
+ }
+
+ ///
+ /// Set aggression + standing loss when Zryachiy/follower or a Lightkeeper-friendly PMC is killed by the main player
+ ///
+ /// The player that was killed
+ public void OnLightkeeperFriendlyPlayerDead(Player player, IPlayer lastAggressor, DamageInfo damageInfo, EBodyPart part)
+ {
+ foreach (Player lightkeeperFriendlyPlayer in lightkeeperFriendlyPlayers)
+ {
+ // Check if a Lightkeeper-friendly player was the killer
+ if ((lightkeeperFriendlyPlayer == null) || (lightkeeperFriendlyPlayer.ProfileId != player?.KillerId))
{
continue;
}
- // Edge case of bossZryachiy not being hostile to player
- if (aiBot.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || aiBot.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy))
+ // A Lightkeeper-friendly player killed Zryachiy or one of his followers
+ if (isZryachiyOrFollower(player))
{
- // Subscribe to bots OnDeath event
- aiBot.OnPlayerDeadOrUnspawn += OnZryachiyOrFollowerDeath;
+ playerKilledLightkeeperFriendlyPlayer(lastAggressor);
+ break;
+ }
- // Save bot to list for later access
- if (!_zryachiyAndFollowers.Contains(aiBot))
- {
- _zryachiyAndFollowers.Add(aiBot);
- }
+ // A Lightkeeper-friendly player killed another Lightkeeper-friendly player when they were both on the island
+ if (playersOnIsland.Any(x => x?.Id == player?.Id) && playersOnIsland.Any(x => x?.Id == lastAggressor?.Id))
+ {
+ playerKilledLightkeeperFriendlyPlayer(lastAggressor);
+ break;
}
}
}
- ///
- /// Set aggression + standing loss when Zryachiy/follower is killed by player
- ///
- /// The player who killed Zryachiy/follower.
- private void OnZryachiyOrFollowerDeath(Player player)
+ private void playerKilledLightkeeperFriendlyPlayer(IPlayer player)
{
- // Check if zryachiy/follower was killed by player
- if (player?.KillerId == _player?.ProfileId)
+ if (player == null)
{
- // If player kills zryachiy or follower, force aggressor state
- // Also set players Lk standing to negative (allows access to quest chain (Making Amends))
- _aggressor = true;
- _player?.Profile.TradersInfo[_lightKeeperTid].SetStanding(-0.01);
+ return;
+ }
+
+ // Set players Lk standing to negative (allows access to quest chain (Making Amends))
+ player.Profile.TradersInfo[_lightKeeperTid].SetStanding(-0.01);
+
+ // Disable access to Lightkeepers door for the player
+ _gameWorld.BufferZoneController.SetPlayerAccessStatus(player.ProfileId, false);
+
+ RecodableItemClass transmitter = GetTransmitterFromInventory(player);
+ if ((transmitter != null) && IsTransmitterActive(transmitter))
+ {
+ transmitter.RecodableComponent.SetStatus(RadioTransmitterStatus.Yellow);
+ transmitter.RecodableComponent.SetEncoded(false);
+ }
+
+ RemoveLightkeeperFriendlyPlayer(player);
+
+ _logger.LogInfo($"Removed Lightkeeper access for {player.Profile.Nickname}");
+ }
+
+ private void botCreated(BotOwner bot)
+ {
+ // Make sure the bot is Zryachiy or one of his followers
+ if (bot.Side != EPlayerSide.Savage)
+ {
+ return;
+ }
+
+ // Check if the bot is Zryachiy or one of his followers
+ if (isZryachiyOrFollower(bot))
+ {
+ // Subscribe to bots OnDeath event
+ bot.GetPlayer.OnPlayerDead += OnLightkeeperFriendlyPlayerDead;
}
}
- ///
- /// Disable door + set transmitter to 'red'
- ///
- private void DisableAccessToLightKeeper()
+ private static bool isZryachiyOrFollower(IPlayer player)
{
- // Disable access to Lightkeepers door for the player
- _gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, false);
- _transmitter?.RecodableComponent?.SetStatus(RadioTransmitterStatus.Yellow);
- _transmitter?.RecodableComponent?.SetEncoded(false);
- _isDoorDisabled = true;
+ if (player == null || !player.IsAI)
+ {
+ return false;
+ }
+
+ return player.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || player.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy);
}
}
}
diff --git a/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs b/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs
index 4db3ebc..85b6b82 100644
--- a/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs
+++ b/project/SPT.SinglePlayer/Patches/Progression/LighthouseBridgePatch.cs
@@ -24,7 +24,7 @@ namespace SPT.SinglePlayer.Patches.Progression
return;
}
- if (gameWorld.MainPlayer.Location.ToLower() != "lighthouse" || gameWorld.MainPlayer.Side == EPlayerSide.Savage)
+ if (gameWorld.MainPlayer.Location.ToLower() != "lighthouse")
{
return;
}