using System; using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; public abstract partial class SharedMapSystem { protected int LastMapId; private void InitializeMap() { SubscribeLocalEvent(OnComponentAdd); SubscribeLocalEvent(OnCompInit); SubscribeLocalEvent(OnCompStartup); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMapRemoved); SubscribeLocalEvent(OnMapHandleState); SubscribeLocalEvent(OnMapGetState); } public bool MapExists([NotNullWhen(true)] MapId? mapId) { return mapId != null && Maps.ContainsKey(mapId.Value); } public EntityUid GetMap(MapId mapId) { return Maps[mapId]; } public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid) { if (mapId == null || !Maps.TryGetValue(mapId.Value, out var map)) { uid = null; return false; } uid = map; return true; } private void OnMapHandleState(EntityUid uid, MapComponent component, ref ComponentHandleState args) { if (args.Current is not MapComponentState state) return; if (component.MapId == MapId.Nullspace) { if (state.MapId == MapId.Nullspace) throw new Exception($"Received invalid map state? {ToPrettyString(uid)}"); component.MapId = state.MapId; Maps.Add(component.MapId, uid); RecursiveMapIdUpdate(uid, uid, component.MapId); } DebugTools.AssertEqual(component.MapId, state.MapId); component.LightingEnabled = state.LightingEnabled; component.MapInitialized = state.Initialized; if (LifeStage(uid) >= EntityLifeStage.Initialized) SetPaused(uid, state.MapPaused); else component.MapPaused = state.MapPaused; } private void RecursiveMapIdUpdate(EntityUid uid, EntityUid mapUid, MapId mapId) { // This is required only in the event where an entity becomes a map AFTER children have already been attached to it. // AFAIK, this currently only happens when the client applies entity states out of order (i.e., ignoring transform hierarchy), // which itself only happens if PVS is disabled. // TODO MAPS remove this var xform = Transform(uid); xform.MapUid = mapUid; xform.MapID = mapId; xform._mapIdInitialized = true; foreach (var child in xform._children) { RecursiveMapIdUpdate(child, mapUid, mapId); } } private void OnMapGetState(EntityUid uid, MapComponent component, ref ComponentGetState args) { args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused, component.MapInitialized); } protected abstract MapId GetNextMapId(); private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args) { // ordered startups when EnsureComp(uid); EnsureComp(uid); EnsureComp(uid); } private void OnCompInit(EntityUid uid, MapComponent component, ComponentInit args) { if (component.MapId == MapId.Nullspace) component.MapId = GetNextMapId(); DebugTools.AssertEqual(component.MapId.IsClientSide, IsClientSide(uid)); if (!Maps.TryAdd(component.MapId, uid)) { if (Maps[component.MapId] != uid) throw new Exception($"Attempted to initialize a map {ToPrettyString(uid)} with a duplicate map id {component.MapId}"); } var msg = new MapChangedEvent(uid, component.MapId, true); RaiseLocalEvent(uid, msg, true); } private void OnCompStartup(EntityUid uid, MapComponent component, ComponentStartup args) { if (component.MapPaused) RecursiveSetPaused(uid, true); } private void OnMapRemoved(EntityUid uid, MapComponent component, ComponentShutdown args) { DebugTools.Assert(component.MapId != MapId.Nullspace); Maps.Remove(component.MapId); var msg = new MapChangedEvent(uid, component.MapId, false); RaiseLocalEvent(uid, msg, true); } /// /// Creates a new map, automatically assigning a map id. /// public EntityUid CreateMap(out MapId mapId, bool runMapInit = true) { mapId = GetNextMapId(); var uid = CreateMap(mapId, runMapInit); return uid; } /// public EntityUid CreateMap(bool runMapInit = true) => CreateMap(out _, runMapInit); /// /// Creates a new map with the specified map id. /// /// Throws if an invalid or already existing map id is provided. public EntityUid CreateMap(MapId mapId, bool runMapInit = true) { if (Maps.ContainsKey(mapId)) throw new ArgumentException($"Map with id {mapId} already exists"); if (mapId == MapId.Nullspace) throw new ArgumentException($"Cannot create a null-space map"); if (_netManager.IsServer && mapId.IsClientSide) throw new ArgumentException($"Attempted to create a client-side map on the server?"); if (_netManager.IsClient && _netManager.IsConnected && !mapId.IsClientSide) throw new ArgumentException($"Attempted to create a client-side map entity with a non client-side map ID?"); var uid = EntityManager.CreateEntityUninitialized(null); var map = _factory.GetComponent(); map.MapId = mapId; AddComp(uid, map); // Give the entity a name, mainly for debugging. Content can always override this with a localized name. var meta = MetaData(uid); _meta.SetEntityName(uid, $"Map Entity", meta); // Initialize components. this should add the map id to the collections. EntityManager.InitializeComponents(uid, meta); EntityManager.StartComponents(uid); DebugTools.Assert(Maps[mapId] == uid); if (runMapInit) InitializeMap((uid, map)); else SetPaused((uid, map), true); return uid; } }