using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using GlitchyEngine.Core; using GlitchyEngine.Extensions; using GlitchyEngine.Physics; using System.Diagnostics.CodeAnalysis; using GlitchyEngine.Editor; namespace GlitchyEngine; /// /// The base class for all entities and scripts in the current world. /// public partial class Entity : EngineObject { /// /// Gets or sets the name of the . /// public string Name { get => "TODO!";//ScriptGlue.Entity_GetName(_uuid); set => ScriptGlue.Entity_SetName(_uuid, value); } /// /// Gets or sets the of the , which specify how the is displayed and interacted with in the editor. /// public EditorFlags EditorFlags { get { ScriptGlue.Entity_GetEditorFlags(_uuid, out EditorFlags flags); return flags; } set => ScriptGlue.Entity_SetEditorFlags(_uuid, value); } /// /// Only to be called by the engine. Don't call this constructor yourself, it will not result in a valid . /// If you want to create a new use or /// protected Entity() { // We don't do anything here. // This constructor will be called by the Engine to initialize the scripts fields. // Especially don't call Create here! Because Create would try and create a new entity. } /// /// Creates a new Entity. /// /// The name of the new . public Entity(string? name = null) { Create(name, null); } /// /// Creates a new with the specified components attached to it. /// /// The name of the new . /// The components that the will be created with. public Entity(string? name, params Type[] components) { Create(name, components); } private void Create(string? name, Type[]? components) { ScriptGlue.Entity_Create(name, out _uuid); if (_uuid == UUID.Zero) { throw new InvalidOperationException("Failed to create the Entity. Received UUID.Zero from the engine."); } if (components != null) { AddComponents(components); } } /// /// Creates an instance that represents the with the given id. /// /// The ID of the that belongs to this instance. internal Entity(UUID uuid) : base(uuid) { } #region Components /// /// Returns if a component of the given type is attached to this . /// /// The type of the component. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent() => HasComponent(typeof(T)); /// /// Returns if a component of the given type is attached to this . /// /// The type of the component. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent(Type type) { return ScriptGlue.Entity_HasComponent(_uuid, type); } /// /// Gets the component with the specified type. /// /// The type of the component to get. /// The component or null, if the doesn't have a component of the specified type. public T? GetComponent() where T : Component, new() { if (HasComponent()) { return new T { _uuid = _uuid }; } return null; } /// /// Gets the component with the specified type. /// /// The type of the component to get. /// If the entity has a of type then contains a reference to this component; otherwise it will contain after the method returned. /// , if a component was retrieved; otherwise . public bool TryGetComponent([MaybeNullWhen(false)] out T component) where T : Component, new() { component = GetComponent(); return component != null; } /// /// Gets the component with the given type. /// /// The type of the component to get. /// The component; or , if the doesn't have a component of the specified type or if doesn't inherit from . /// For performance reasons it is recommended to use if possible as it might be slightly faster. public Component? GetComponent(Type componentType) { if (!componentType.IsSubclassOf(typeof(Component))) { Log.Error($"{nameof(GetComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\"."); return null; } if (HasComponent(componentType)) { return ActivatorExtension.CreateComponent(componentType, _uuid); } return null; } /// /// Attaches a component of the specified type to the . /// /// The type of the component to attach. /// The component. public T AddComponent() where T : Component, new() { ScriptGlue.Entity_AddComponent(_uuid, typeof(T)); return new T { _uuid = _uuid }; } /// /// Attaches a component of the specified type to the . /// /// The type of the component to attach. /// The component that was attached to the ; or if doesn't inherit from . /// For performance reasons it is recommended to use if possible as it might be slightly faster. public Component? AddComponent(Type componentType) { if (!componentType.IsSubclassOf(typeof(Component))) { Log.Error($"{nameof(AddComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\"."); return null; } ScriptGlue.Entity_AddComponent(_uuid, componentType); return ActivatorExtension.CreateComponent(componentType, _uuid); } /// /// Attaches components with the specified types to the . /// /// The types of the components to attach. /// An array containing the components that were attached. A component is , if it's type doesn't inherit from . public Component?[] AddComponents(params Type[] componentTypes) { // Note: The elements in componentTypes are non-nullable externally, but internally we rely on nullability. foreach ((Type componentType, int index) in componentTypes.WithIndex()) { if (!componentType.IsSubclassOf(typeof(Component))) { Log.Error($"{nameof(AddComponents)}: Invalid component type \"{componentType}\" at index {index}. Must be a subclass of \"{nameof(Component)}\"."); // Setting null here is fine, the engine can handle it! componentTypes[index] = null!; } } Component?[] components = new Component[componentTypes.Length]; foreach ((Type componentType, int index) in componentTypes.WithIndex()) { // componentType is null, if it's type is invalid! if (componentType == null!) continue; ScriptGlue.Entity_AddComponent(_uuid, componentType); components[index] = ActivatorExtension.CreateComponent(componentType, _uuid); } return components; } /// /// Attaches the specified components to the . /// /// A tuple containing the added components. public (T1, T2) AddComponents() where T1 : Component, new() where T2 : Component, new() { ScriptGlue.Entity_AddComponent(_uuid, typeof(T1)); ScriptGlue.Entity_AddComponent(_uuid, typeof(T2)); return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid }); } /// /// Attaches the specified components to the . /// /// A tuple containing the added components. public (T1, T2, T3) AddComponents() where T1 : Component, new() where T2 : Component, new() where T3 : Component, new() { ScriptGlue.Entity_AddComponent(_uuid, typeof(T1)); ScriptGlue.Entity_AddComponent(_uuid, typeof(T2)); ScriptGlue.Entity_AddComponent(_uuid, typeof(T3)); return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid }, new T3 { _uuid = _uuid }); } /// /// Removes the component with the specified component type from the . /// /// The type of the component to remove. public void RemoveComponent() where T : Component, new() { ScriptGlue.Entity_RemoveComponent(_uuid, typeof(T)); } /// /// Removes the component with the given component type from the . /// /// The type of the component to remove. public void RemoveComponent(Type componentType) { if (!componentType.IsSubclassOf(typeof(Component))) { Log.Error($"{nameof(RemoveComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\"."); return; } ScriptGlue.Entity_RemoveComponent(_uuid, componentType); } #endregion Components /// /// Sets the script of the to the specified type. /// If necessary removes the existing script. /// /// The type of the script. /// A reference to the new script or , if the operation failed. public T? SetScript() where T : Entity { Entity? scriptInstance = null; if (ScriptGlue.Entity_SetScript(_uuid, typeof(T).FullName)) { ScriptGlue.Entity_GetScriptInstance(_uuid, out scriptInstance); } return scriptInstance as T; } /// /// Removes the script from the . /// public void RemoveScript() { ScriptGlue.Entity_RemoveScript(_uuid); } /// /// Gets the of this . /// public Transform Transform => GetComponent()!; // Transform always exists! /// /// Returns the first with the given name. /// /// The name of the . /// The first with the given name, or if no such entity exists. public static Entity? FindEntityWithName(string name) { ScriptGlue.Entity_FindEntityWithName(name, out UUID entityId); if (entityId == UUID.Zero) return null; return new Entity(entityId); } /// /// Returns whether or not the has a script of the specified type. /// /// The type of the script. /// if the has a script component of the given type; or if the either has no script or the script is not of the specified type. public bool Is() where T : Entity { ScriptGlue.Entity_GetScriptInstance(_uuid, out Entity? scriptInstance); return scriptInstance is T; } /// /// Returns whether or not the has a script of the specified type. /// /// The type of the script. /// if the has a script component of the given type; or if the either has no script or the script is not of the specified type. public bool Is(Type type) { if (!type.IsSubclassOf(typeof(Entity))) { Log.Warning($"{nameof(Is)}: Invalid script type \"{type}\". Must be a subclass of \"{nameof(Entity)}\"."); return false; } ScriptGlue.Entity_GetScriptInstance(_uuid, out Entity? scriptInstance); return type.IsInstanceOfType(scriptInstance); } /// /// Returns the script instance of the given type that is attached to the . /// /// The type of the script. /// The script instance of the given type; or if the has no script of the given type. public T? As() where T : Entity { ScriptGlue.Entity_GetScriptInstance(_uuid, out Entity? scriptInstance); return scriptInstance as T; } /// /// Returns the script instance of the given type that is attached to the . /// /// The type of the script. /// The script instance of the given type; or if the has no script of the given type. public object? As(Type type) { if (!type.IsSubclassOf(typeof(Entity))) { Log.Error($"{nameof(As)}: Invalid script type \"{type}\". Must be a subclass of \"{nameof(Entity)}\"."); return null; } ScriptGlue.Entity_GetScriptInstance(_uuid, out Entity? scriptInstance); return scriptInstance; } /// /// Returns the script instance of the given type that is attached to the with the given . /// /// The id of the whose script instance shall be returned. /// The type of the script. /// The script instance of the given type; or if the with the given has no script of the given type. internal static Entity? GetScriptReference(UUID id, Type type) { Debug.Assert(typeof(Entity).IsAssignableFrom(type)); ScriptGlue.Entity_GetScriptInstance(id, out Entity? scriptInstance); return scriptInstance as Entity; } /// /// Destroys the and all it's children. /// /// /// The destruction will not take place immediately. It will happen at the end of the current frame. /// public void Destroy() { ScriptGlue.Entity_Destroy(_uuid); } /// /// Instantiates a copy of the given and all of it's children. /// /// The to copy. /// The new . public static Entity CreateInstance(Entity entity) { if (entity == null) { throw new ArgumentException("The provided instance must not be null!", nameof(entity)); } ScriptGlue.Entity_CreateInstance(entity.UUID, out UUID newEntityId); return new Entity(newEntityId); } /// /// Will be called once after the entity has be created. /// protected internal virtual void OnCreate() { } /// /// Is called once every frame. /// protected internal virtual void OnUpdate(float deltaTime) { } /// /// Will be called once when the entity is being destroyed. /// protected internal virtual void OnDestroy() { } /// /// Will be called /// /// protected internal virtual void OnCollisionEnter2D(Collision2D collision) { } }