| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- using System.Linq;
- using System.Numerics;
- using Content.Server.Administration;
- using Content.Server.Chat.Managers;
- using Content.Server.DeviceNetwork.Components;
- using Content.Server.DeviceNetwork.Systems;
- using Content.Server.GameTicking;
- using Content.Server.GameTicking.Events;
- using Content.Server.Parallax;
- using Content.Server.Screens.Components;
- using Content.Server.Shuttles.Components;
- using Content.Server.Shuttles.Events;
- using Content.Server.Spawners.Components;
- using Content.Server.Spawners.EntitySystems;
- using Content.Server.Station.Components;
- using Content.Server.Station.Events;
- using Content.Server.Station.Systems;
- using Content.Shared.Administration;
- using Content.Shared.CCVar;
- using Content.Shared.Damage.Components;
- using Content.Shared.DeviceNetwork;
- using Content.Shared.GameTicking;
- using Content.Shared.Mobs.Components;
- using Content.Shared.Movement.Components;
- using Content.Shared.Parallax.Biomes;
- using Content.Shared.Salvage;
- using Content.Shared.Shuttles.Components;
- using Content.Shared.Tiles;
- using Robust.Server.GameObjects;
- using Robust.Shared.Collections;
- using Robust.Shared.Configuration;
- using Robust.Shared.Console;
- using Robust.Shared.EntitySerialization;
- using Robust.Shared.EntitySerialization.Systems;
- using Robust.Shared.Map;
- using Robust.Shared.Map.Components;
- using Robust.Shared.Player;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Random;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
- namespace Content.Server.Shuttles.Systems;
- /// <summary>
- /// If enabled spawns players on a separate arrivals station before they can transfer to the main station.
- /// </summary>
- public sealed class ArrivalsSystem : EntitySystem
- {
- [Dependency] private readonly IChatManager _chat = default!;
- [Dependency] private readonly IConfigurationManager _cfgManager = default!;
- [Dependency] private readonly IConsoleHost _console = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IPrototypeManager _protoManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ActorSystem _actor = default!;
- [Dependency] private readonly BiomeSystem _biomes = default!;
- [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
- [Dependency] private readonly GameTicker _ticker = default!;
- [Dependency] private readonly MapLoaderSystem _loader = default!;
- [Dependency] private readonly MetaDataSystem _metaData = default!;
- [Dependency] private readonly SharedMapSystem _mapSystem = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly ShuttleSystem _shuttles = default!;
- [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
- [Dependency] private readonly StationSystem _station = default!;
- private EntityQuery<PendingClockInComponent> _pendingQuery;
- private EntityQuery<ArrivalsBlacklistComponent> _blacklistQuery;
- private EntityQuery<MobStateComponent> _mobQuery;
- /// <summary>
- /// If enabled then spawns players on an alternate map so they can take a shuttle to the station.
- /// </summary>
- public bool Enabled { get; private set; }
- /// <summary>
- /// Flags if all players spawning at the departure terminal have godmode until they leave the terminal.
- /// </summary>
- public bool ArrivalsGodmode { get; private set; }
- /// <summary>
- /// The first arrival is a little early, to save everyone 10s
- /// </summary>
- private const float RoundStartFTLDuration = 10f;
- private readonly List<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
- {
- "Grasslands",
- "LowDesert",
- "Snow",
- };
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<PlayerSpawningEvent>(HandlePlayerSpawning, before: new []{ typeof(SpawnPointSystem)}, after: new [] { typeof(ContainerSpawnPointSystem)});
- SubscribeLocalEvent<StationArrivalsComponent, StationPostInitEvent>(OnStationPostInit);
- SubscribeLocalEvent<ArrivalsShuttleComponent, ComponentStartup>(OnShuttleStartup);
- SubscribeLocalEvent<ArrivalsShuttleComponent, FTLTagEvent>(OnShuttleTag);
- SubscribeLocalEvent<RoundStartingEvent>(OnRoundStarting);
- SubscribeLocalEvent<ArrivalsShuttleComponent, FTLStartedEvent>(OnArrivalsFTL);
- SubscribeLocalEvent<ArrivalsShuttleComponent, FTLCompletedEvent>(OnArrivalsDocked);
- SubscribeLocalEvent<PlayerSpawnCompleteEvent>(SendDirections);
- _pendingQuery = GetEntityQuery<PendingClockInComponent>();
- _blacklistQuery = GetEntityQuery<ArrivalsBlacklistComponent>();
- _mobQuery = GetEntityQuery<MobStateComponent>();
- // Don't invoke immediately as it will get set in the natural course of things.
- Enabled = _cfgManager.GetCVar(CCVars.ArrivalsShuttles);
- ArrivalsGodmode = _cfgManager.GetCVar(CCVars.GodmodeArrivals);
- _cfgManager.OnValueChanged(CCVars.ArrivalsShuttles, SetArrivals);
- _cfgManager.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b);
- // Command so admins can set these for funsies
- _console.RegisterCommand("arrivals", ArrivalsCommand, ArrivalsCompletion);
- }
- private void OnShuttleTag(EntityUid uid, ArrivalsShuttleComponent component, ref FTLTagEvent args)
- {
- if (args.Handled)
- return;
- // Just saves mappers forgetting. (v2 boogaloo)
- args.Handled = true;
- args.Tag = "DockArrivals";
- }
- private CompletionResult ArrivalsCompletion(IConsoleShell shell, string[] args)
- {
- if (args.Length != 1)
- return CompletionResult.Empty;
- return new CompletionResult(new CompletionOption[]
- {
- // Enables and disable are separate comms in case you don't want to accidentally toggle it, compared to
- // returns which doesn't have an immediate effect
- new("enable", Loc.GetString("cmd-arrivals-enable-hint")),
- new("disable", Loc.GetString("cmd-arrivals-disable-hint")),
- new("returns", Loc.GetString("cmd-arrivals-returns-hint")),
- new ("force", Loc.GetString("cmd-arrivals-force-hint"))
- }, "Option");
- }
- [AdminCommand(AdminFlags.Fun)]
- private void ArrivalsCommand(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length != 1)
- {
- shell.WriteError(Loc.GetString("cmd-arrivals-invalid"));
- return;
- }
- switch (args[0])
- {
- case "enable":
- _cfgManager.SetCVar(CCVars.ArrivalsShuttles, true);
- break;
- case "disable":
- _cfgManager.SetCVar(CCVars.ArrivalsShuttles, false);
- break;
- case "returns":
- var existing = _cfgManager.GetCVar(CCVars.ArrivalsReturns);
- _cfgManager.SetCVar(CCVars.ArrivalsReturns, !existing);
- shell.WriteLine(Loc.GetString("cmd-arrivals-returns", ("value", !existing)));
- break;
- case "force":
- var query = AllEntityQuery<PendingClockInComponent, TransformComponent>();
- var spawnPoints = EntityQuery<SpawnPointComponent, TransformComponent>().ToList();
- TryGetArrivals(out var arrivalsUid);
- while (query.MoveNext(out var uid, out _, out var pendingXform))
- {
- _random.Shuffle(spawnPoints);
- foreach (var (point, xform) in spawnPoints)
- {
- if (point.SpawnType != SpawnPointType.LateJoin || xform.GridUid == arrivalsUid)
- continue;
- _transform.SetCoordinates(uid, pendingXform, xform.Coordinates);
- break;
- }
- RemCompDeferred<AutoOrientComponent>(uid);
- RemCompDeferred<PendingClockInComponent>(uid);
- shell.WriteLine(Loc.GetString("cmd-arrivals-forced", ("uid", ToPrettyString(uid))));
- }
- break;
- default:
- shell.WriteError(Loc.GetString($"cmd-arrivals-invalid"));
- break;
- }
- }
- /// <summary>
- /// First sends shuttle timer data, then kicks people off the shuttle if it isn't leaving the arrivals terminal
- /// </summary>
- private void OnArrivalsFTL(EntityUid shuttleUid, ArrivalsShuttleComponent component, ref FTLStartedEvent args)
- {
- if (!TryGetArrivals(out EntityUid arrivals))
- return;
- if (TryComp<DeviceNetworkComponent>(shuttleUid, out var netComp))
- {
- TryComp<FTLComponent>(shuttleUid, out var ftlComp);
- var ftlTime = TimeSpan.FromSeconds(ftlComp?.TravelTime ?? _shuttles.DefaultTravelTime);
- var payload = new NetworkPayload
- {
- [ShuttleTimerMasks.ShuttleMap] = shuttleUid,
- [ShuttleTimerMasks.ShuttleTime] = ftlTime
- };
- // unfortunate levels of spaghetti due to roundstart arrivals ftl behavior
- EntityUid? sourceMap;
- var arrivalsDelay = _cfgManager.GetCVar(CCVars.ArrivalsCooldown);
- if (component.FirstRun)
- {
- var station = _station.GetLargestGrid(Comp<StationDataComponent>(component.Station));
- sourceMap = station == null ? null : Transform(station.Value)?.MapUid;
- arrivalsDelay += RoundStartFTLDuration;
- component.FirstRun = false;
- payload.Add(ShuttleTimerMasks.DestMap, Transform(args.TargetCoordinates.EntityId).MapUid);
- payload.Add(ShuttleTimerMasks.DestTime, ftlTime);
- }
- else
- sourceMap = args.FromMapUid;
- payload.Add(ShuttleTimerMasks.SourceMap, sourceMap);
- payload.Add(ShuttleTimerMasks.SourceTime, ftlTime + TimeSpan.FromSeconds(arrivalsDelay));
- _deviceNetworkSystem.QueuePacket(shuttleUid, null, payload, netComp.TransmitFrequency);
- }
- // Don't do anything here when leaving arrivals.
- var arrivalsMapUid = Transform(arrivals).MapUid;
- if (args.FromMapUid == arrivalsMapUid)
- return;
- // Any mob then yeet them off the shuttle.
- if (!_cfgManager.GetCVar(CCVars.ArrivalsReturns) && args.FromMapUid != null)
- DumpChildren(shuttleUid, ref args);
- var pendingQuery = AllEntityQuery<PendingClockInComponent, TransformComponent>();
- // We're heading from the station back to arrivals (if leaving arrivals, would have returned above).
- // Process everyone who holds a PendingClockInComponent
- // Note, due to way DumpChildren works, anyone who doesn't have a PendingClockInComponent gets left in space
- // and will not warp. This is intended behavior.
- while (pendingQuery.MoveNext(out var pUid, out _, out var xform))
- {
- if (xform.GridUid == shuttleUid)
- {
- // Warp all players who are still on this shuttle to a spawn point. This doesn't let them return to
- // arrivals. It also ensures noobs, slow players or AFK players safely leave the shuttle.
- TryTeleportToMapSpawn(pUid, component.Station, xform);
- }
- // Players who have remained at arrivals keep their warp coupon (PendingClockInComponent) for now.
- if (xform.MapUid == arrivalsMapUid)
- continue;
- // The player has successfully left arrivals and is also not on the shuttle. Remove their warp coupon.
- RemCompDeferred<PendingClockInComponent>(pUid);
- RemCompDeferred<AutoOrientComponent>(pUid);
- if (ArrivalsGodmode)
- RemCompDeferred<GodmodeComponent>(pUid);
- }
- }
- private void OnArrivalsDocked(EntityUid uid, ArrivalsShuttleComponent component, ref FTLCompletedEvent args)
- {
- var dockTime = component.NextTransfer - _timing.CurTime + TimeSpan.FromSeconds(_shuttles.DefaultStartupTime);
- if (TryComp<DeviceNetworkComponent>(uid, out var netComp))
- {
- var payload = new NetworkPayload
- {
- [ShuttleTimerMasks.ShuttleMap] = uid,
- [ShuttleTimerMasks.ShuttleTime] = dockTime,
- [ShuttleTimerMasks.SourceMap] = args.MapUid,
- [ShuttleTimerMasks.SourceTime] = dockTime,
- [ShuttleTimerMasks.Docked] = true
- };
- _deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
- }
- }
- private void DumpChildren(EntityUid uid, ref FTLStartedEvent args)
- {
- var toDump = new List<Entity<TransformComponent>>();
- FindDumpChildren(uid, toDump);
- foreach (var (ent, xform) in toDump)
- {
- var rotation = xform.LocalRotation;
- _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, Vector2.Transform(xform.LocalPosition, args.FTLFrom)));
- _transform.SetWorldRotation(ent, args.FromRotation + rotation);
- if (_actor.TryGetSession(ent, out var session))
- {
- _chat.DispatchServerMessage(session!, Loc.GetString("latejoin-arrivals-dumped-from-shuttle"));
- }
- }
- }
- private void FindDumpChildren(EntityUid uid, List<Entity<TransformComponent>> toDump)
- {
- if (_pendingQuery.HasComponent(uid))
- return;
- var xform = Transform(uid);
- if (_mobQuery.HasComponent(uid) || _blacklistQuery.HasComponent(uid))
- {
- toDump.Add((uid, xform));
- return;
- }
- var children = xform.ChildEnumerator;
- while (children.MoveNext(out var child))
- {
- FindDumpChildren(child, toDump);
- }
- }
- public void HandlePlayerSpawning(PlayerSpawningEvent ev)
- {
- if (ev.SpawnResult != null)
- return;
- // We use arrivals as the default spawn so don't check for job prio.
- // Only works on latejoin even if enabled.
- if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound)
- return;
- if (!HasComp<StationArrivalsComponent>(ev.Station))
- return;
- TryGetArrivals(out var arrivals);
- if (!TryComp(arrivals, out TransformComponent? arrivalsXform))
- return;
- var mapId = arrivalsXform.MapID;
- var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
- var possiblePositions = new List<EntityCoordinates>();
- while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
- {
- if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
- continue;
- possiblePositions.Add(xform.Coordinates);
- }
- if (possiblePositions.Count <= 0)
- return;
- var spawnLoc = _random.Pick(possiblePositions);
- ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
- spawnLoc,
- ev.Job,
- ev.HumanoidCharacterProfile,
- ev.Station);
- EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
- EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
- // If you're forced to spawn, you're invincible until you leave wherever you were forced to spawn.
- if (ArrivalsGodmode)
- EnsureComp<GodmodeComponent>(ev.SpawnResult.Value);
- }
- private void SendDirections(PlayerSpawnCompleteEvent ev)
- {
- if (!Enabled || !ev.LateJoin || ev.Silent || !_pendingQuery.HasComp(ev.Mob))
- return;
- var arrival = NextShuttleArrival();
- var message = arrival is not null
- ? Loc.GetString("latejoin-arrivals-direction-time", ("time", $"{arrival:mm\\:ss}"))
- : Loc.GetString("latejoin-arrivals-direction");
- _chat.DispatchServerMessage(ev.Player, message);
- }
- private bool TryTeleportToMapSpawn(EntityUid player, EntityUid stationId, TransformComponent? transform = null)
- {
- if (!Resolve(player, ref transform))
- return false;
- var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
- var possiblePositions = new ValueList<EntityCoordinates>(32);
- // Find a spawnpoint on the same map as the player is already docked with now.
- while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
- {
- if (spawnPoint.SpawnType == SpawnPointType.LateJoin &&
- _station.GetOwningStation(uid, xform) == stationId)
- {
- // Add to list of possible spawn locations
- possiblePositions.Add(xform.Coordinates);
- }
- }
- if (possiblePositions.Count > 0)
- {
- // Move the player to a random late-join spawnpoint.
- _transform.SetCoordinates(player, transform, _random.Pick(possiblePositions));
- if (_actor.TryGetSession(player, out var session))
- {
- _chat.DispatchServerMessage(session!, Loc.GetString("latejoin-arrivals-teleport-to-spawn"));
- }
- return true;
- }
- return false;
- }
- private void OnShuttleStartup(EntityUid uid, ArrivalsShuttleComponent component, ComponentStartup args)
- {
- EnsureComp<PreventPilotComponent>(uid);
- }
- private bool TryGetArrivals(out EntityUid uid)
- {
- var arrivalsQuery = EntityQueryEnumerator<ArrivalsSourceComponent>();
- while (arrivalsQuery.MoveNext(out uid, out _))
- {
- return true;
- }
- return false;
- }
- public TimeSpan? NextShuttleArrival()
- {
- var query = EntityQueryEnumerator<ArrivalsShuttleComponent>();
- var time = TimeSpan.MaxValue;
- while (query.MoveNext(out var uid, out var comp))
- {
- if (comp.NextArrivalsTime < time)
- time = comp.NextArrivalsTime;
- }
- var duration = _timing.CurTime;
- return (time < duration) ? null : time - duration;
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var query = EntityQueryEnumerator<ArrivalsShuttleComponent, ShuttleComponent, TransformComponent>();
- var curTime = _timing.CurTime;
- TryGetArrivals(out var arrivals);
- if (TryComp(arrivals, out TransformComponent? arrivalsXform))
- {
- while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform))
- {
- if (comp.NextTransfer > curTime || !TryComp<StationDataComponent>(comp.Station, out var data))
- continue;
- var tripTime = _shuttles.DefaultTravelTime + _shuttles.DefaultStartupTime;
- // Go back to arrivals source
- if (xform.MapUid != arrivalsXform.MapUid)
- {
- if (arrivals.IsValid())
- _shuttles.FTLToDock(uid, shuttle, arrivals);
- comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(tripTime);
- }
- // Go to station
- else
- {
- var targetGrid = _station.GetLargestGrid(data);
- if (targetGrid != null)
- _shuttles.FTLToDock(uid, shuttle, targetGrid.Value);
- // The ArrivalsCooldown includes the trip there, so we only need to add the time taken for
- // the trip back.
- comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(
- _cfgManager.GetCVar(CCVars.ArrivalsCooldown) + tripTime);
- }
- comp.NextTransfer += TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
- }
- }
- }
- private void OnRoundStarting(RoundStartingEvent ev)
- {
- // Setup arrivals station
- if (!Enabled)
- return;
- SetupArrivalsStation();
- }
- private void SetupArrivalsStation()
- {
- var path = new ResPath(_cfgManager.GetCVar(CCVars.ArrivalsMap));
- _mapSystem.CreateMap(out var mapId, runMapInit: false);
- var mapUid = _mapSystem.GetMap(mapId);
- if (!_loader.TryLoadGrid(mapId, path, out var grid))
- return;
- _metaData.SetEntityName(mapUid, Loc.GetString("map-name-terminal"));
- EnsureComp<ArrivalsSourceComponent>(grid.Value);
- EnsureComp<ProtectedGridComponent>(grid.Value);
- EnsureComp<PreventPilotComponent>(grid.Value);
- // Setup planet arrivals if relevant
- if (_cfgManager.GetCVar(CCVars.ArrivalsPlanet))
- {
- var template = _random.Pick(_arrivalsBiomeOptions);
- _biomes.EnsurePlanet(mapUid, _protoManager.Index(template));
- var restricted = new RestrictedRangeComponent
- {
- Range = 32f
- };
- AddComp(mapUid, restricted);
- }
- _mapSystem.InitializeMap(mapId);
- // Handle roundstart stations.
- var query = AllEntityQuery<StationArrivalsComponent>();
- while (query.MoveNext(out var uid, out var comp))
- {
- SetupShuttle(uid, comp);
- }
- }
- private void SetArrivals(bool obj)
- {
- Enabled = obj;
- if (Enabled)
- {
- SetupArrivalsStation();
- var query = AllEntityQuery<StationArrivalsComponent>();
- while (query.MoveNext(out var sUid, out var comp))
- {
- SetupShuttle(sUid, comp);
- }
- }
- else
- {
- var sourceQuery = AllEntityQuery<ArrivalsSourceComponent>();
- while (sourceQuery.MoveNext(out var uid, out _))
- {
- QueueDel(uid);
- }
- var shuttleQuery = AllEntityQuery<ArrivalsShuttleComponent>();
- while (shuttleQuery.MoveNext(out var uid, out _))
- {
- QueueDel(uid);
- }
- }
- }
- private void OnStationPostInit(EntityUid uid, StationArrivalsComponent component, ref StationPostInitEvent args)
- {
- if (!Enabled)
- return;
- // If it's a latespawn station then this will fail but that's okey
- SetupShuttle(uid, component);
- }
- private void SetupShuttle(EntityUid uid, StationArrivalsComponent component)
- {
- if (!Deleted(component.Shuttle))
- return;
- // Spawn arrivals on a dummy map then dock it to the source.
- var dummpMapEntity = _mapSystem.CreateMap(out var dummyMapId);
- if (TryGetArrivals(out var arrivals) &&
- _loader.TryLoadGrid(dummyMapId, component.ShuttlePath, out var shuttle))
- {
- component.Shuttle = shuttle.Value;
- var shuttleComp = Comp<ShuttleComponent>(component.Shuttle);
- var arrivalsComp = EnsureComp<ArrivalsShuttleComponent>(component.Shuttle);
- arrivalsComp.Station = uid;
- EnsureComp<ProtectedGridComponent>(uid);
- _shuttles.FTLToDock(component.Shuttle, shuttleComp, arrivals, hyperspaceTime: RoundStartFTLDuration);
- arrivalsComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
- }
- // Don't start the arrivals shuttle immediately docked so power has a time to stabilise?
- var timer = AddComp<TimedDespawnComponent>(dummpMapEntity);
- timer.Lifetime = 15f;
- }
- }
|