mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-12 20:50:44 -05:00
LighthouseProgressionClass Improvements (!159)
* Bug fix for getting negative Lightkeeper rep and disabling your transmitter only when killing Zryachiy, not his followers * Revised the class to support multiple players having access to the island, not only the main player * Added support for having multiple Lightkeeper-friendly PMC's on the island (currently requires mod support). Killing one of them while on the island will also result in getting reduced Lightkeeper rep and disabling your transmitter. * Separated location changes into discrete methods and made them public so mods can call them individually * Added static property that determines if the main player's transmitter status should open the island for everyone on the map. Also added property indicating if the island has been opened for everyone. This allows mods to override SPT's behavior and not have the island opened to everyone if the main player has access. * Rewrote the class so that the cyclic `Update()` method is no longer needed Co-authored-by: chomp <chomp@noreply.dev.sp-tarkov.com> Reviewed-on: SPT/Modules#159 Co-authored-by: dwesterwick <dwesterwick@yahoo.com> Co-committed-by: dwesterwick <dwesterwick@yahoo.com>
This commit is contained in:
parent
f8eb2374cb
commit
f235ee21b7
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Flag to disable mines and AI data that instructs Zryachiy to attack you if the main player is authorized to enter Lightkeeper Island
|
||||
/// </summary>
|
||||
public static bool MainPlayerControlsIslandAccessForEveryone { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating if the Lightkeeper-Island bridge mines and AI data that instructs Zryachiy to attack you have been disabled for everyone
|
||||
/// </summary>
|
||||
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<MineDirectional> _bridgeMines;
|
||||
private RecodableItemClass _transmitter;
|
||||
private readonly List<IPlayer> _zryachiyAndFollowers = new List<IPlayer>();
|
||||
private bool _aggressor;
|
||||
private bool _isDoorDisabled;
|
||||
private readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
|
||||
private readonly string _lightKeeperTid = "638f541a29ffd1183d187f57";
|
||||
private ManualLogSource _logger;
|
||||
private List<IPlayer> lightkeeperFriendlyPlayers = new List<IPlayer>();
|
||||
private List<Player> playersOnIsland = new List<Player>();
|
||||
|
||||
/// <summary>
|
||||
/// PMC's that have been reported to be Lightkeeper-friendly
|
||||
/// </summary>
|
||||
public IReadOnlyList<IPlayer> LightkeeperFriendlyPlayers => lightkeeperFriendlyPlayers.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// PMC's that have been reported to be on Lightkeeper Island
|
||||
/// </summary>
|
||||
public IReadOnlyList<Player> LightkeeperFriendlyPlayersOnIsland => playersOnIsland.AsReadOnly();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_gameWorld = Singleton<GameWorld>.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<IBotGame>.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<IBotGame>.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()
|
||||
/// <summary>
|
||||
/// Check if the player has been added to the Lightkeeper-friendly PMC list
|
||||
/// </summary>
|
||||
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)
|
||||
/// <summary>
|
||||
/// Check if the player has been added to the list of Lightkeeper-friendly PMC's on Lightkeeper Island
|
||||
/// </summary>
|
||||
public bool IsLightkeeperFriendlyPlayerOnIsland(IPlayer player)
|
||||
{
|
||||
return player != null && playersOnIsland.Contains(player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has an active transmitter in its inventory, and if so add it to the Lightkeeper-friendly PMC list
|
||||
/// </summary>
|
||||
/// <returns>True if the player was added to the Lightkeeper-friendly PMC list</returns>
|
||||
public bool CheckAndAddLightkeeperFriendlyPlayer(IPlayer player)
|
||||
{
|
||||
if (PlayerHasActiveTransmitterInInventory(player))
|
||||
{
|
||||
return AddLightkeeperFriendlyPlayer(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the player to the Lightkeeper-friendly PMC list
|
||||
/// </summary>
|
||||
/// <returns>True if the player was added to the Lightkeeper-friendly PMC list</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the player from the Lightkeeper-friendly PMC list
|
||||
/// </summary>
|
||||
/// <returns>True if the player was removed from the Lightkeeper-friendly PMC list</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the player to the list of PMC's that are on Lightkeeper Island
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the player from the list of PMC's that are on Lightkeeper Island
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables brige mines, disables AI data to instruct Zryachiy to attack you, and watch for Zryachiy and his followers to spawn
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the "Attack" and "CloseZone" AIPlaceInfo objects that instruct Zryachiy and his followers to attack you
|
||||
/// </summary>
|
||||
public void DisableAIPlaceInfoForZryachiy()
|
||||
{
|
||||
var places = Singleton<IBotGame>.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets transmitter from players inventory
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for transmitter status and exists in players inventory
|
||||
/// </summary>
|
||||
private bool PlayerHasActiveTransmitterInInventory()
|
||||
public bool PlayerHasActiveTransmitterInInventory(IPlayer player)
|
||||
{
|
||||
return _transmitter != null &&
|
||||
_transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
|
||||
RecodableItemClass transmitter = GetTransmitterFromInventory(player);
|
||||
return IsTransmitterActive(transmitter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update _time to diff from last run of update()
|
||||
/// Check if the transmitter allows access to the island
|
||||
/// </summary>
|
||||
private void IncrementLastUpdateTimer()
|
||||
public bool IsTransmitterActive(RecodableItemClass transmitter)
|
||||
{
|
||||
_timer += Time.deltaTime;
|
||||
return transmitter != null && transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set all brdige mines to desire state
|
||||
/// </summary>
|
||||
/// <param name="desiredMineState">What state should bridge mines be set to</param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
private void SetupZryachiyAndFollowerHostility()
|
||||
/// <returns>True if the mine is on the Lightkeeper Island bridge</returns>
|
||||
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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set aggression + standing loss when Zryachiy/follower or a Lightkeeper-friendly PMC is killed by the main player
|
||||
/// </summary>
|
||||
/// <param name="player">The player that was killed</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set aggression + standing loss when Zryachiy/follower is killed by player
|
||||
/// </summary>
|
||||
/// <param name="player">The player who killed Zryachiy/follower.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable door + set transmitter to 'red'
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user