using System.Linq; using Content.Server._Miracle.Components; using Content.Server.Administration.Managers; using Content.Server.Administration.Systems; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.Chat.Managers; using Content.Server.Database; using Content.Server.GameTicking; using Content.Server.GameTicking.Events; using Content.Server.KillTracking; using Content.Server.Materials; using Content.Server.Mind; using Content.Server.Parallax; using Content.Server.Preferences.Managers; using Content.Server.Spawners.Components; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Server.Storage.EntitySystems; using Content.Shared._Miracle.Cvars; using Content.Shared._Miracle.GulagSystem; using Content.Shared.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Materials; using Content.Shared.Mind; using Content.Shared.Parallax.Biomes; using Content.Shared.Popups; using Content.Shared.Preferences; using Content.Shared.Throwing; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server._Miracle.GulagSystem; public sealed partial class GulagSystem : SharedGulagSystem { //1 second = 0.01 points [Dependency] private readonly AdminSystem _adminSystem = default!; [Dependency] private readonly IBanManager _banManager = default!; [Dependency] private readonly BiomeSystem _biome = default!; [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly MapLoaderSystem _mapLoader = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StationSpawningSystem _spawningSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly CargoSystem _cargoSystem = default!; [Dependency] private readonly MaterialStorageSystem _materialStorageSystem = default!; [Dependency] private readonly EntityStorageSystem _entityStorageSystem = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!; private readonly List> _gulagBiomes = new() { "GulagBiome" }; private readonly List _gulagMaps = new() { "/Maps/Gulags/gulag.yml" }; private double _timeToPointsRatio; private MapId? _activeMap; private EntityUid? _mapEntity; private readonly TimeSpan _safeguardUpdateRate = TimeSpan.FromSeconds(10); private DateTime _nextSafeguardUpdate = DateTime.MinValue; private readonly TimeSpan _shuttleFillUpdateRate = TimeSpan.FromMinutes(10); private DateTime _nextShuttleFillUpdate = DateTime.MinValue; private List _spawnCoords = new(); private readonly Dictionary _pointsPerPlayer = new(); private readonly Dictionary, int> _gulagMaterialStorage = new(); public override void Initialize() { base.Initialize(); _cfg.OnValueChanged(MiracleCvars.GulagPointsToTimeRatio, newValue => _timeToPointsRatio = newValue, true); SubscribeLocalEvent(OnRoundStarting); SubscribeLocalEvent(OnRoundRestart); SubscribeLocalEvent(BeforeSpawn); SubscribeLocalEvent(OnOreInserted); SubscribeLocalEvent(OnInteract, before: new[] {typeof(MaterialStorageSystem)}); SubscribeLocalEvent(OnGulagContainerSpawned); SubscribeLocalEvent(OnKillReported); SubscribeLocalEvent(OnJoinedLobby); //safeguard SubscribeLocalEvent(OnPlayerAttached); } private void OnJoinedLobby(PlayerJoinedLobbyEvent ev) { if(IsUserGulagged(ev.PlayerSession.UserId, out _)) { _chatManager.DispatchServerMessage(ev.PlayerSession, Loc.GetString("gulag-chat-join-message")); } } private void OnKillReported(ref KillReportedEvent ev) { if (!HasComp(ev.Entity)) { return; } if (ev.Primary is not KillPlayerSource source) { return; } var player = source.PlayerId; if (!IsUserGulagged(player, out var ban)) { return; } var banDef = ban.First(); var newExpirationTime = banDef.ExpirationTime!.Value + TimeSpan.FromDays(1); EditServerBan(banDef, newExpirationTime); } public override void Update(float frameTime) { base.Update(frameTime); Safeguard(); TryFillCargoShuttle(); } private void OnGulagContainerSpawned(EntityUid uid, GulagFillContainerComponent component, MapInitEvent args) { var coords = Transform(uid).Coordinates; foreach (var (materialId, value) in _gulagMaterialStorage) { var materialEntities = _materialStorageSystem.SpawnMultipleFromMaterial(value, materialId, coords); foreach (var material in materialEntities) { _entityStorageSystem.Insert(material, uid); } } _gulagMaterialStorage.Clear(); } private void TryFillCargoShuttle() { if (_nextShuttleFillUpdate > DateTime.Now) { return; } if (_gulagMaterialStorage.Count == 0) { return; } var station = GetMainStation(); if (!station.HasValue) { return; } if (!TryComp(station.Value, out var comp) || !TryComp(station.Value, out StationDataComponent? dataComp)) { return; } _cargoSystem.AddAndApproveOrder(station.Value, "CrateGulag", "Автоматическая система доставки", 0, 1, Loc.GetString("gulag-sender"), Loc.GetString("gulag-order-description"), Loc.GetString("gulag-order-destination"), comp, (station.Value, dataComp)); _nextShuttleFillUpdate = DateTime.Now + _shuttleFillUpdateRate; } // Just check if we need to bring back somehow escaped players private void Safeguard() { if (_nextSafeguardUpdate > DateTime.Now) { return; } var querry = EntityQueryEnumerator(); while (querry.MoveNext(out var uid, out var gulagbound, out var xform)) { if (xform.MapID != _activeMap) { SendToGulag(uid); } } _nextSafeguardUpdate = DateTime.Now + _safeguardUpdateRate; } public void SendToGulag(ICommonSession session) { var playerEntity = session.AttachedEntity; if (_mapEntity == null) { _adminSystem.Erase(session); return; } if (playerEntity.HasValue) { SendToGulag(playerEntity.Value); } else { SpawnPlayer(session, (HumanoidCharacterProfile)_preferencesManager.GetPreferences(session.UserId).SelectedCharacter); } var banDef = _banManager.GetServerBans(session.UserId).First(); var message = Loc.GetString("gulag-greetings-message", ("BanTime", $"{(banDef.ExpirationTime! - DateTime.Now).Value.TotalHours}")); _chatManager.DispatchServerMessage(session, message); } private void OnInteract(EntityUid uid, GulagOreProcessorComponent component, InteractUsingEvent args) { //It wasn't player who interacted with the entity if (!_playerManager.TryGetSessionByEntity(args.User, out var session)) { return; } component.LastInteractedUser = session.UserId; Log.Info("OnInteract raised"); } private void OnOreInserted(EntityUid uid, GulagOreProcessorComponent component, MaterialEntityInsertedEvent args) { var storageComponent = Comp(uid); var userId = component.LastInteractedUser!.Value; foreach (var (materialId, currentVolume ) in storageComponent.Storage) { var materialPrototype = _prototypeManager.Index(materialId.Id); var stackVolume = _materialStorageSystem.GetSheetVolume(materialPrototype); var actualOreCount = currentVolume / stackVolume; var points = materialPrototype.Price * actualOreCount; _pointsPerPlayer[userId] = points + _pointsPerPlayer.GetValueOrDefault(userId); _gulagMaterialStorage[materialId] = currentVolume + _gulagMaterialStorage.GetValueOrDefault(materialId); _materialStorageSystem.TrySetMaterialAmount(uid, materialId, 0); } var time = ConvertPointsToTime(_pointsPerPlayer[userId]); _popupSystem.PopupEntity(Loc.GetString("gulag-ban-time-changed", ("Time", $"{time.TotalSeconds}")), uid, PopupType.Medium); } public bool IsUserGulagged(NetUserId playerId, out HashSet bans) { bans = _banManager.GetServerBans(playerId); return bans.Count != 0; } public bool IsMindGulagged(EntityUid mindId) { if (!TryComp(mindId, out MindComponent? mind) || mind.UserId == null) return false; return IsUserGulagged(mind.UserId.Value, out _); } private void SendToGulag(EntityUid playerEntity) { if (_inventorySystem.TryGetContainerSlotEnumerator(playerEntity, out var enumerator)) { while (enumerator.NextItem(out var item, out var slot)) { if (_inventorySystem.TryUnequip(playerEntity, playerEntity, slot.Name, true, true)) _physicsSystem.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse); } } if (TryComp(playerEntity, out HandsComponent? hands)) { foreach (var hand in _handsSystem.EnumerateHands(playerEntity, hands)) { _handsSystem.TryDrop(playerEntity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands); } } var newPosition = GetSpawnPosition(); _transformSystem.SetCoordinates(playerEntity, newPosition); _transformSystem.AttachToGridOrMap(playerEntity); EnsureComp(playerEntity); EnsureComp(playerEntity); } private void SpawnPlayer(ICommonSession session, HumanoidCharacterProfile profile) { var newMind = _mind.CreateMind(session.UserId, profile.Name); _mind.SetUserId(newMind, session.UserId); var coords = GetSpawnPosition(); var mob = _spawningSystem.SpawnPlayerMob(coords, null, profile, null); _mind.TransferTo(newMind, mob); EnsureComp(mob); EnsureComp(mob); } private void OnPlayerAttached(PlayerAttachedEvent ev) { var bans = _banManager.GetServerBans(ev.Player.UserId); if (bans.Count == 0) { return; } var xform = Transform(ev.Entity); if (xform.MapID != _activeMap) { SendToGulag(ev.Player); } } private void BeforeSpawn(PlayerBeforeSpawnEvent ev) { var bans = _banManager.GetServerBans(ev.Player.UserId); if (bans.Count == 0) { return; } ev.Handled = true; SendToGulag(ev.Player); } private void OnRoundRestart(RoundRestartCleanupEvent ev) { foreach (var (player, points) in _pointsPerPlayer) { var banDef = _banManager.GetServerBans(player).FirstOrDefault(); if (banDef == null) { continue; } var newExpirationTime = banDef.ExpirationTime!.Value - ConvertPointsToTime(points); EditServerBan(banDef, newExpirationTime); } _pointsPerPlayer.Clear(); _activeMap = null!; _mapEntity = null!; } private void EditServerBan(ServerBanDef banDef, DateTimeOffset newExpirationTime) { _db.EditServerBan(banDef.Id!.Value, banDef.Reason, banDef.Severity, newExpirationTime.UtcDateTime, banDef.UserId!.Value, DateTime.UtcNow); _banManager.RemoveCachedServerBan(banDef.UserId.Value, banDef.Id.Value); _banManager.AddCachedServerBan(new ServerBanDef( banDef.Id, banDef.UserId, banDef.Address, banDef.HWId, banDef.BanTime, newExpirationTime, banDef.RoundId, banDef.PlaytimeAtNote, banDef.Reason, banDef.Severity, banDef.BanningAdmin, banDef.Unban, banDef.ServerName)); } private void OnRoundStarting(RoundStartingEvent ev) { //Spawn Gulag var mapId = _mapManager.CreateMap(); var mapUid = _mapManager.GetMapEntityId(mapId); _metaData.SetEntityName(mapUid, "Gulag Map"); _mapManager.AddUninitializedMap(mapId); var pickedMap = _random.Pick(_gulagMaps); if (!_mapLoader.TryLoad(mapId, pickedMap, out var uids)) { _mapManager.DeleteMap(mapId); Log.Error("Can't spawn map with path {0}", pickedMap); return; } foreach (var uid in uids) { _metaData.SetEntityName(uid, $"Gulag grid {uid}"); } var pickerBiome = _random.Pick(_gulagBiomes); _biome.EnsurePlanet(mapUid, _prototypeManager.Index(pickerBiome)); _mapManager.DoMapInitialize(mapId); _activeMap = mapId; _mapEntity = mapUid; //Item2 = TransformComponent _spawnCoords = EntityQuery() .Where(x => x.Item2.MapID == mapId) .Select(x => x.Item2.Coordinates) .ToList(); } private TimeSpan ConvertPointsToTime(double points) { return TimeSpan.FromSeconds(points / _timeToPointsRatio); } private EntityCoordinates GetSpawnPosition() { return _spawnCoords.Count != 0 ? _random.Pick(_spawnCoords) : Transform(_mapEntity!.Value).Coordinates; } private EntityUid? GetMainStation() { var stations = _stationSystem.GetStations(); foreach (var station in stations) { var stationData = Comp(station); if (!HasComp(station)) { continue; } foreach (var grid in stationData.Grids) { if (Transform(grid).MapID == _gameTicker.DefaultMap) { return station; } } } return null!; } }