diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 5180c32107..4321808ccc 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -123,7 +123,8 @@ public override void Init() _prototypeManager.RegisterIgnore("alertLevels"); _prototypeManager.RegisterIgnore("nukeopsRole"); _prototypeManager.RegisterIgnore("secretPool"); - _prototypeManager.RegisterIgnore("missionMapGrid"); + _prototypeManager.RegisterIgnore("missionGrid"); + _prototypeManager.RegisterIgnore("missionMap"); _prototypeManager.RegisterIgnore("stationGoal"); // Corvax-StationGoal _prototypeManager.RegisterIgnore("loadout"); // Corvax-Loadout diff --git a/Content.Server/GameTicking/Rules/TS/MasterRORuleSystem.cs b/Content.Server/GameTicking/Rules/TS/MasterRORuleSystem.cs index b891f35e70..0922b1ed0a 100644 --- a/Content.Server/GameTicking/Rules/TS/MasterRORuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TS/MasterRORuleSystem.cs @@ -1,6 +1,16 @@ +using System.Linq; +using System.Numerics; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Rules.Components; +using Content.Server.TS; +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.GameTicking.Rules; @@ -8,31 +18,230 @@ namespace Content.Server.GameTicking.Rules; public sealed class MasterRORuleSystem : GameRuleSystem { [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly MapSystem _mapSystem = default!; + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly ITimerManager _timerManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ILocalizationManager _localizationManager = default!; - protected override void Added(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + private static MapId _currentMissionMapId; + private static Vector2 _currentCenterMissionMap; + protected override void Started(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - _chat.DispatchGlobalAnnouncement("Added gamerule", "SpecialForces", true, null, Color.BetterViolet); + var smallestValue = 1; + for (; smallestValue < 25; ++smallestValue) // 25 is magic number + { + if (_mapManager.MapExists(new MapId(smallestValue))) + continue; + break; + } + + if (smallestValue == 25) + { + _logManager.GetSawmill("RORule").Error("Event cant spawn map, because maps already more than 24"); + return; + } + _currentMissionMapId = new MapId(smallestValue); + + var allFoundMaps = _prototypeManager.EnumeratePrototypes().ToList(); + + if (!allFoundMaps.Any()) + { + _logManager.GetSawmill("RORule").Error("Event cant spawn map, because cant find any mission map prototype"); + return; + } + + var allFoundGrids = _prototypeManager.EnumeratePrototypes().ToList(); + var indexMap = _random.Next(allFoundMaps.Count() - 1); + + if (!_mapLoader.TryLoad(_currentMissionMapId, allFoundMaps[indexMap].MapPath.ToString(), out var entityList)) + { + return; + } + + var grids = _mapManager.GetAllMapGrids(_currentMissionMapId); + var mainGrid = grids.First(); + + if (!grids.Any()) + { + _logManager.GetSawmill("RORule").Error("Event cant found main grid!"); + return; + } + var entityMap = _mapManager.GetMapEntityId(_currentMissionMapId); + + _currentCenterMissionMap = _mapSystem.LocalToWorld(entityMap, mainGrid, Vector2.Zero); + + int objectCount = _random.Next(5, 9); + _logManager.GetSawmill("RORule").Info("Side grid count = {0}", objectCount); + + float angleDelta = 360 / objectCount; + var xDelta = _random.NextFloat(0.5f, 1); + var startVector = new Vector2(xDelta, 1 - (xDelta * xDelta)); // hard math of point on normalized circle, Y found by Pifagor's theorem + for (var i = 0; i < objectCount; ++i) + { + var indexGrid = _random.Next(allFoundGrids.Count() - 1); + float currentAngle = angleDelta * i; + var tempOptions = new MapLoadOptions(); + tempOptions.Offset = new Vector2( + (startVector.X * Single.Cos(currentAngle) - startVector.Y * Single.Sin(currentAngle)), + (startVector.X * Single.Sin(currentAngle) + startVector.Y * Single.Cos(currentAngle)) + ) * _random.NextFloat(140f, 220f) + _currentCenterMissionMap; + tempOptions.Rotation = _random.NextAngle(0, 360); + + _mapLoader.TryLoad(_currentMissionMapId, allFoundGrids[indexGrid].GridPath.ToString(), out _, tempOptions); + } + _mapManager.DoMapInitialize(_currentMissionMapId); + + var allSecretPools = _prototypeManager.EnumeratePrototypes().ToList(); + + if (!allSecretPools.Any()) + { + _logManager.GetSawmill("RORule").Error("No one secretPoolPrototype found!"); + return; + } + + var missionItemSpawners = new List(); + var tempEntities = _entMan.GetEntities(); + + foreach (var tempEnt in tempEntities) // there is no other way to get entity by ID in current map + { + if (_entMan.TryGetComponent(tempEnt, out var tempTransform)) + { + if (tempTransform.MapID != _currentMissionMapId) continue; + } + + if (_entMan.TryGetComponent(tempEnt, out var tempMeta)) + { + if (tempMeta.EntityPrototype == null) + continue; + + if (tempMeta.EntityPrototype.ID == "MissionItemSpawn") + { + missionItemSpawners.Add(tempEnt); + } + } + } + + if (!missionItemSpawners.Any()) + { + _logManager.GetSawmill("RORule").Error("No one MissionItemPrototype found!"); + return; + } + else + _logManager.GetSawmill("RORule").Info("Found {0} mission item's spawners on RO map", missionItemSpawners.Count()); + + // @todo antag player system balance + var guaranteedPointsCount = 3; + var sidePointsCount = 2; + int debugCountItems = 0; + + var tempPoolIndex = _random.Next(allSecretPools.Count() - 1); + + var tempTupleSpawnerList = new List>(); + foreach (var tempMissionItem in missionItemSpawners) + { + tempTupleSpawnerList.Add(new Tuple(tempMissionItem, false)); + } + + if (missionItemSpawners.Count() <= guaranteedPointsCount) + { + foreach (var tempMissionItem in missionItemSpawners) + { + if (spawnMissionItem(allSecretPools[tempPoolIndex], tempMissionItem, entityMap, mainGrid)) + ++debugCountItems; + } + } + else + { + for (var i = 0; i < guaranteedPointsCount; ++i) + { + var tempRandMissionIndex = _random.Next(missionItemSpawners.Count()); + + if (spawnMissionItem(allSecretPools[tempPoolIndex], missionItemSpawners[tempRandMissionIndex], + entityMap, mainGrid)) + { + missionItemSpawners.Remove(missionItemSpawners[tempRandMissionIndex]); + ++debugCountItems; + } + } + + if (missionItemSpawners.Any()) + { + if (missionItemSpawners.Count() <= sidePointsCount) + { + foreach (var tempMissionItem in missionItemSpawners) + { + if (_random.Next(100) > 60) + continue; // 40% chance + + if (spawnMissionItem(allSecretPools[tempPoolIndex], tempMissionItem, entityMap, mainGrid)) + ++debugCountItems; + } + } + else + { + for (var i = 0; i < sidePointsCount; ++i) + { + var tempRandMissionIndex = _random.Next(missionItemSpawners.Count()); + if (spawnMissionItem(allSecretPools[tempPoolIndex], missionItemSpawners[tempRandMissionIndex], + entityMap, mainGrid)) + { + missionItemSpawners.Remove(missionItemSpawners[tempRandMissionIndex]); + ++debugCountItems; + } + } + } + } + } + + _logManager.GetSawmill("RORule").Info("Spawned {0} mission items on RO map", debugCountItems); + + var randDelay = 60000; // 1 min //_random.Next(400000, 600000); // 5-10 min + _timerManager.AddTimer(new Timer(randDelay, false, startEvent)); } - protected override void Started(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + protected override void ActiveTick(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, float frameTime) { - _chat.DispatchGlobalAnnouncement("Started gamerule", "SpecialForces", true, null, Color.BetterViolet); + //_chat.DispatchGlobalAnnouncement("Added gamerule", "SpecialForces", true, null, Color.BetterViolet); } - /// - /// Called when the gamerule ends - /// - protected override void Ended(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) + + private void startEvent() { - _chat.DispatchGlobalAnnouncement("Ended gamerule", "SpecialForces", true, null, Color.BetterViolet); + var ftlPoint = _entMan.SpawnEntity("FTLPointUnknown", new MapCoordinates(_currentCenterMissionMap, _currentMissionMapId)); + + var senderLocale = _localizationManager.GetString("research-mission-sender"); + var messageLocale = _localizationManager.GetString("research-mission-message"); + + _chat.DispatchGlobalAnnouncement(messageLocale, senderLocale, true, null, Color.GreenYellow); } - /// - /// Called on an active gamerule entity in the Update function - /// - protected override void ActiveTick(EntityUid uid, MasterRORuleComponent component, GameRuleComponent gameRule, float frameTime) + + private bool spawnMissionItem(SecretPoolPrototype secretPool, EntityUid missionItemSpawner, EntityUid entityMap, MapGridComponent mainGrid) { - //_chat.DispatchGlobalAnnouncement("Added gamerule", "SpecialForces", true, null, Color.BetterViolet); + if (!TryComp(missionItemSpawner, out TransformComponent? xComp)) + { + _logManager.GetSawmill("RORule") + .Error( + "Item {0} has no transform component, cant spawn random guaranteed mission item!", missionItemSpawner.Id); + return false; + } + + var tempItemPoolIndex = _random.Next(secretPool.PoolItems.Count() - 1); + _entMan.Spawn( + secretPool.PoolItems[tempItemPoolIndex], + new MapCoordinates(_mapSystem.LocalToWorld(entityMap, mainGrid, xComp.LocalPosition), + _currentMissionMapId) + ); + return true; } + + + + } diff --git a/Content.Server/TS/MissionGridPrototype.cs b/Content.Server/TS/MissionGridPrototype.cs new file mode 100644 index 0000000000..33f23bf021 --- /dev/null +++ b/Content.Server/TS/MissionGridPrototype.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Utility; + +namespace Content.Server.TS; + +/// +/// Stores data for generic queries. +/// Each query is run in turn to get the final available results. +/// These results are then run through the considerations. +/// +[Prototype] +public sealed partial class MissionGridPrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField(required: true)] + public ResPath GridPath; +} diff --git a/Content.Server/TS/MissionMapGridPrototype.cs b/Content.Server/TS/MissionMapPrototype.cs similarity index 88% rename from Content.Server/TS/MissionMapGridPrototype.cs rename to Content.Server/TS/MissionMapPrototype.cs index 9014a0762c..acdec485dd 100644 --- a/Content.Server/TS/MissionMapGridPrototype.cs +++ b/Content.Server/TS/MissionMapPrototype.cs @@ -10,7 +10,7 @@ namespace Content.Server.TS; /// These results are then run through the considerations. /// [Prototype] -public sealed partial class MissionMapGridPrototype : IPrototype +public sealed partial class MissionMapPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Resources/Locale/ru-RU/TS/research-mission.ftl b/Resources/Locale/ru-RU/TS/research-mission.ftl new file mode 100644 index 0000000000..5ee3eaa4de --- /dev/null +++ b/Resources/Locale/ru-RU/TS/research-mission.ftl @@ -0,0 +1,2 @@ +research-mission-sender = Центральный военный штаб +research-mission-message = Внимание! Одна из наших станций послала сигнал SOS, по последним данным станционного ИИ, на борту живых членов экипажа нет. Приказываем собрать группу, разведать ситуацию, эвакуировать важные документы, устранить имеющиеся угрозы. diff --git a/Resources/Maps/Mission/broken-aspid.yml b/Resources/Maps/Mission/broken-aspid.yml index e69edb295e..73e1e74d35 100644 --- a/Resources/Maps/Mission/broken-aspid.yml +++ b/Resources/Maps/Mission/broken-aspid.yml @@ -57,7 +57,6 @@ entities: - type: MetaData - type: Transform pos: 0.13793182,0.57805127 - parent: invalid - type: MapGrid chunks: -1,-1: @@ -8397,13 +8396,6 @@ entities: rot: -1.5707963267948966 rad pos: -2.5,48.5 parent: 1 - - type: DeviceLinkSink - links: - - 99 - - type: DeviceLinkSource - linkedPorts: - invalid: - - DoorStatus: Close - uid: 99 components: - type: Transform @@ -40219,80 +40211,6 @@ entities: - type: Transform pos: -28.5,58.5 parent: 1 -- proto: DeployableBarrier - entities: - - uid: 3438 - components: - - type: Transform - anchored: True - pos: 7.5,13.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - - uid: 6158 - components: - - type: Transform - anchored: True - pos: 7.5,15.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - - uid: 6161 - components: - - type: Transform - anchored: True - pos: 10.5,15.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - - uid: 8387 - components: - - type: Transform - anchored: True - pos: -28.5,37.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - - uid: 9502 - components: - - type: Transform - anchored: True - pos: 10.5,14.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - - uid: 9533 - components: - - type: Transform - anchored: True - pos: -28.5,38.5 - parent: 1 - - type: Physics - bodyType: Static - - type: Lock - locked: True - - type: PointLight - enabled: True - proto: DeskBell entities: - uid: 5855 @@ -59050,10 +58968,6 @@ entities: rot: -1.5707963267948966 rad pos: -4.5,-37.5 parent: 1 - - type: DeviceLinkSource - linkedPorts: - invalid: - - Pressed: Toggle - uid: 8753 components: - type: Transform diff --git a/Resources/Prototypes/Maps/mission_grids.yml b/Resources/Prototypes/Maps/mission_grids.yml index 9fe9def54f..f8e8df2296 100644 --- a/Resources/Prototypes/Maps/mission_grids.yml +++ b/Resources/Prototypes/Maps/mission_grids.yml @@ -1,27 +1,27 @@ -- type: missionMapGrid +- type: missionMap id: MissionAspidBroken mapPath: /Maps/Mission/broken-aspid.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaMediumVault1 - mapPath: /Maps/Mission/medium-vault-1.yml + gridPath: /Maps/Mission/medium-vault-1.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaRuinCargoSalvage - mapPath: /Maps/Mission/ruin-cargo-salvage.yml + gridPath: /Maps/Mission/ruin-cargo-salvage.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaSmallChapel - mapPath: /Maps/Mission/small-chapel.yml + gridPath: /Maps/Mission/small-chapel.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaSmallShip1 - mapPath: /Maps/Mission/small-ship-1.yml + gridPath: /Maps/Mission/small-ship-1.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaSmallSyndicate - mapPath: /Maps/Mission/small-syndicate.yml + gridPath: /Maps/Mission/small-syndicate.yml -- type: missionMapGrid +- type: missionGrid id: MissionVanillaTickColony - mapPath: /Maps/Mission/tick-colony.yml + gridPath: /Maps/Mission/tick-colony.yml diff --git a/Resources/Prototypes/TS/Entities/Markers.yml b/Resources/Prototypes/TS/Entities/Markers.yml new file mode 100644 index 0000000000..1acd27a3f0 --- /dev/null +++ b/Resources/Prototypes/TS/Entities/Markers.yml @@ -0,0 +1,4 @@ +- type: entity + id: FTLPointUnknown + parent: FTLPoint + name: Unknown point