using Barotrauma; using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using MoreLevelContent.Shared; using MoreLevelContent.Shared.Data; using MoreLevelContent.Shared.Generation; using MoreLevelContent.Shared.Utils; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Resources; using System.Xml.Linq; namespace MoreLevelContent.Missions { // Shared internal partial class CablePuzzleMission : Mission { /// /// This sucks ass but we should /NEVER/ have more than one relay station in one level /// public static SubmarineFile SubmarineFile { get; set; } const float INTERVAL = 2.5f; const int RANGE_MIN = 50; const int RANGE_MAX = 200; const int REQUIRED_CYCLES = 2; const float REQUIRED_CYCLE_TIME = 1; const double CYCLE_GRACE_PERIOD = Timing.Step * 2; private readonly LocalizedString defaultSonarLabel; private readonly string terminalTag; private readonly XElement _SubmarineConfig; private Submarine _Station; private LevelData _LevelData; private float _Timer = INTERVAL; public CablePuzzleMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { _SubmarineConfig = prefab.ConfigElement.GetChildElement("Submarine"); defaultSonarLabel = TextManager.Get("relaysonarlabel"); terminalTag = _SubmarineConfig.GetAttributeString("welcomemsg", "relayrepair.terminal"); SubmarineFile = null; LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData; if (levelData != null) { SetLevel(levelData); } } public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels { get { if (_Station == null) yield break; yield return (Prefab.SonarLabel.IsNullOrEmpty() ? defaultSonarLabel : Prefab.SonarLabel, _Station.WorldPosition); } } public override void SetLevel(LevelData level) { if (_LevelData != null) { //level already set return; } _LevelData = level; SetSub(_SubmarineConfig, Prefab); } public static void SetSub(XElement config, MissionPrefab prefab) { ContentPath subPath = config.GetAttributeContentPath("path", prefab.ContentPackage); if (subPath.IsNullOrEmpty()) { Log.Error($"No path used for submarine for the relay station mission \"{prefab.Identifier}\"!"); return; } SubmarineFile file = ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles()).Where(f => f.Path.Value == subPath).FirstOrDefault(); if (file == null) { Log.Error($"Failed to find submarine at path {subPath}"); return; } SubmarineFile = file; Log.Debug("Set relay station sub file"); } private MemoryComponent _WpInput; private MemoryComponent _WpTarget; private LightComponent _WpLight; private Terminal _WpHint; private readonly List _Operations = new(); private readonly SignalOperation[] _Sequence = new SignalOperation[4]; private enum OperationType { Add, Sub, Mul, Div } private struct SignalOperation { public SignalOperation(OperationType type, int value) { _Value = value; _Operation = type; } private readonly OperationType _Operation; private readonly int _Value; public override string ToString() => $"{_Operation} {_Value}"; public int Run(int input) { switch (_Operation) { case OperationType.Add: return input += _Value; case OperationType.Sub: return input -= _Value; case OperationType.Mul: return input *= _Value; case OperationType.Div: return input /= _Value; } throw new NotImplementedException(); } } public override void StartMissionSpecific(Level level) { _Station = level.MLC().RelayStation; if (_Station == null) { Log.Error("Failed to spawn relay station!!"); return; } if (IsClient) return; var items = _Station.GetItems(false); var rand = MLCUtils.GetRandomFromString(_LevelData.Seed); var memoryComps = new List(); foreach (var item in items) { if (item.HasTag("wp_input")) _WpInput = item.GetComponent(); if (item.HasTag("wp_target")) _WpTarget = item.GetComponent(); if (item.HasTag("wp_light")) _WpLight = item.GetComponent(); if (item.HasTag("wp_hint")) _WpHint = item.GetComponent(); if (item.HasTag("wp_add")) AddOperation(OperationType.Add); if (item.HasTag("wp_sub")) AddOperation(OperationType.Sub); if (item.HasTag("wp_mul")) AddOperation(OperationType.Mul); void AddOperation(OperationType type, int low = 5, int high = 25) { var comp = item.GetComponent(); if (comp == null) { Log.Error("Failed to find a memory component on tagged item"); return; } int val = rand.Next(low, high); comp.Value = val.ToString(); memoryComps.Add(comp); _Operations.Add(new SignalOperation(type, val)); } } if (_Operations.Count == 0) { Log.Error("Failed to collect any operations!"); return; } // Build random sequence _Operations.Shuffle(rand); _Sequence[0] = _Operations[0]; _Sequence[1] = _Operations[1]; _Sequence[2] = _Operations[2]; _Sequence[3] = _Operations[3]; int[] steps0 = GetStepsForInput(Rand.Range(RANGE_MIN, RANGE_MAX, Rand.RandSync.Unsynced)); int[] steps1 = GetStepsForInput(Rand.Range(RANGE_MIN, RANGE_MAX, Rand.RandSync.Unsynced)); int[] steps2 = GetStepsForInput(Rand.Range(RANGE_MIN, RANGE_MAX, Rand.RandSync.Unsynced)); _WpHint.ShowMessage = TextManager.GetWithVariables("relayrepair.terminal", ("[version]", $"${Main.Version}"), ("[station]", $"{rand.Next(100, 999)}"), ("[input0]", $"{steps0[0].ToString().PadLeft(3, '0')}"), ("[input1]", $"{steps1[0].ToString().PadLeft(3, '0')}"), ("[input2]", $"{steps2[0].ToString().PadLeft(3, '0')}"), ("[step10]", $"{steps0[1].ToString().PadLeft(3, '0')}"), ("[step20]", $"{steps0[2].ToString().PadLeft(3, '0')}"), ("[step30]", $"{steps0[3].ToString().PadLeft(3, '0')}"), ("[step11]", $"{steps1[1].ToString().PadLeft(3, '0')}"), ("[step21]", $"{steps1[2].ToString().PadLeft(3, '0')}"), ("[step31]", $"{steps1[3].ToString().PadLeft(3, '0')}"), ("[step12]", $"{steps2[1].ToString().PadLeft(3, '0')}"), ("[step22]", $"{steps2[2].ToString().PadLeft(3, '0')}"), ("[step32]", $"{steps2[3].ToString().PadLeft(3, '0')}"), ("[output0]", $"{steps0[4]}"), ("[output1]", $"{steps1[4]}"), ("[output2]", $"{steps2[4]}") ).Value; Log.Debug("Sub created"); #if SERVER // Have the server send these changes to the client _WpHint.SyncHistory(); foreach (var comp in memoryComps) { comp.Item.CreateServerEvent(comp); } #endif } private int GetStepForInput(int input, int stepCount) { if (stepCount > _Sequence.Length) { Log.Error($"Requested sequence step ({stepCount}) is bigger then the sequence length ({_Sequence.Length})"); return 0; } for (int i = 0; i < stepCount; i++) { var operation = _Sequence[i]; input = operation.Run(input); } return input; } private int[] GetStepsForInput(int input) { int[] output = new int[_Sequence.Length + 1]; output[0] = input; for (int i = 0; i < _Sequence.Length; i++) { input = _Sequence[i].Run(input); output[i + 1] = input; } return output; } double successTimer = 0; public override void UpdateMissionSpecific(float deltaTime) { if (IsClient) return; if (_Station == null) return; if (State == 0) { if (CrewInSub()) { State = 1; } } // Crew has entered the relay and is fixing it if (State >= 1) { // We have the correct value if (_WpLight.IsOn) { successTimer = 2f; } else if (successTimer > 0) { successTimer -= deltaTime; } State = successTimer > 0 ? 2 : 1; } // Return if timer is counting if (_Timer > 0) { _Timer -= deltaTime; return; } var input = Rand.Range(RANGE_MIN, RANGE_MAX, Rand.RandSync.Unsynced); _WpInput.Value = input.ToString(); _WpTarget.Value = GetStepForInput(input, 4).ToString(); #if SERVER // Have the server send the info to the client _WpInput.Item.CreateServerEvent(_WpInput); _WpTarget.Item.CreateServerEvent(_WpTarget); #endif _Timer = INTERVAL; bool CrewInSub() { foreach (var crewMember in GameSession.GetSessionCrewCharacters(CharacterType.Player)) { if (crewMember.Submarine == _Station) { return true; } } return false; } } public override void EndMissionSpecific(bool completed) { if (completed && level.LevelData != null) { level.LevelData.MLC().RelayStationStatus = RelayStationStatus.Active; } } public override bool DetermineCompleted(CampaignMode.TransitionType transitionType) => State == 2; } }