using System; using System.Collections.Generic; using System.Linq; using Robust.Client.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Utility; namespace Robust.Client.GameObjects { /// /// Manager for entities -- controls things like template loading and instantiation /// public sealed class ClientEntityManager : EntityManager, IClientEntityManager, IDisposable { #pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager; #pragma warning restore 649 private int _nextClientEntityUid = EntityUid.ClientUid + 1; public override void Startup() { base.Startup(); if (Started) { throw new InvalidOperationException("Startup() called multiple times"); } EntitySystemManager.Initialize(); Started = true; } public void ApplyEntityStates(List curEntStates, IEnumerable deletions, List nextEntStates) { var toApply = new Dictionary(); var toInitialize = new List(); deletions ??= new EntityUid[0]; if (curEntStates != null && curEntStates.Count != 0) { foreach (var es in curEntStates) { //Known entities if (Entities.TryGetValue(es.Uid, out var entity)) { toApply.Add(entity, (es, null)); } else //Unknown entities { var metaState = (MetaDataComponentState)es.ComponentStates.First(c => c.NetID == NetIDs.META_DATA); var newEntity = CreateEntity(metaState.PrototypeId, es.Uid); toApply.Add(newEntity, (es, null)); toInitialize.Add(newEntity); } } } if (nextEntStates != null && nextEntStates.Count != 0) { foreach (var es in nextEntStates) { if (Entities.TryGetValue(es.Uid, out var entity)) { if (toApply.TryGetValue(entity, out var state)) { toApply[entity] = (state.Item1, es); } else { toApply[entity] = (null, es); } } } } // Make sure this is done after all entities have been instantiated. foreach (var kvStates in toApply) { var ent = kvStates.Key; var entity = (Entity)ent; HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1, kvStates.Value.Item2); } foreach (var kvp in toApply) { UpdateEntityTree(kvp.Key); } foreach (var id in deletions) { DeleteEntity(id); } foreach (var entity in toInitialize) { InitializeEntity(entity); } foreach (var entity in toInitialize) { StartEntity(entity); } foreach (var entity in toInitialize) { UpdateEntityTree(entity); } } public void Dispose() { Shutdown(); } /// public override IEntity CreateEntityUninitialized(string prototypeName) { return CreateEntity(prototypeName); } /// public override IEntity CreateEntityUninitialized(string prototypeName, GridCoordinates coordinates) { var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); if (coordinates.GridID != GridId.Invalid) { var gridEntityId = _mapManager.GetGrid(coordinates.GridID).GridEntityId; newEntity.Transform.AttachParent(GetEntity(gridEntityId)); newEntity.Transform.LocalPosition = coordinates.Position; } return newEntity; } /// public override IEntity CreateEntityUninitialized(string prototypeName, MapCoordinates coordinates) { var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId)); newEntity.Transform.WorldPosition = coordinates.Position; return newEntity; } /// public override IEntity SpawnEntity(string protoName, GridCoordinates coordinates) { var newEnt = CreateEntityUninitialized(protoName, coordinates); InitializeAndStartEntity((Entity)newEnt); UpdateEntityTree(newEnt); return newEnt; } /// public override IEntity SpawnEntity(string protoName, MapCoordinates coordinates) { var entity = CreateEntityUninitialized(protoName, coordinates); InitializeAndStartEntity((Entity)entity); UpdateEntityTree(entity); return entity; } /// public override IEntity SpawnEntityNoMapInit(string protoName, GridCoordinates coordinates) { return SpawnEntity(protoName, coordinates); } protected override EntityUid GenerateEntityUid() { return new EntityUid(_nextClientEntityUid++); } private static void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState curState, EntityState nextState) { var compStateWork = new Dictionary(); var entityUid = entity.Uid; if(curState?.ComponentChanges != null) { foreach (var compChange in curState.ComponentChanges) { if (compChange.Deleted) { if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp)) { compMan.RemoveComponent(entityUid, comp); } } else { if (compMan.HasComponent(entityUid, compChange.NetID)) continue; var newComp = (Component) IoCManager.Resolve().GetComponent(compChange.ComponentName); newComp.Owner = entity; compMan.AddComponent(entity, newComp, true); } } } if(curState?.ComponentStates != null) { foreach (var compState in curState.ComponentStates) { compStateWork[compState.NetID] = (compState, null); } } if(nextState?.ComponentStates != null) { foreach (var compState in nextState.ComponentStates) { if (compStateWork.TryGetValue(compState.NetID, out var state)) { compStateWork[compState.NetID] = (state.curState, compState); } else { compStateWork[compState.NetID] = (null, compState); } } } foreach (var kvStates in compStateWork) { if (!compMan.TryGetComponent(entityUid, kvStates.Key, out var component)) { DebugTools.Assert("Component does not exist for state."); continue; } try { component.HandleComponentState(kvStates.Value.curState, kvStates.Value.nextState); } catch (Exception e) { Logger.ErrorS("entity", $"Failed to apply comp state: entity={component.Owner}, comp={component.Name}\n {e}"); DebugTools.Assert(e.Message); } } } } }