Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Entity Collider Components to Entities #858

Open
wants to merge 44 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b442c72
Added AddEntityToTracker and AddComponentToTracker methods
TheDavSmasher Nov 26, 2024
fec4721
Simplified Lambda, accounted for multiple Components in the same entity
TheDavSmasher Nov 26, 2024
036e3d3
Simplified new statement
TheDavSmasher Nov 26, 2024
a6d3b62
Added EntityCollider and EntityColliderByComponent
TheDavSmasher Nov 26, 2024
48db15a
Simplified AddComponentToTracker by using AddRange instead of foreach…
TheDavSmasher Nov 30, 2024
5c0e16a
Made method instance instead of static so it can access its own dicti…
TheDavSmasher Nov 30, 2024
68d4be0
Changed Added for EntityAdded to access scene for Tracker
TheDavSmasher Nov 30, 2024
04ead2d
Added default null collider
TheDavSmasher Nov 30, 2024
795db3c
Added Summary documentation to Entity Collider Components
TheDavSmasher Nov 30, 2024
c20217c
Fixed boolean logic of EntityAdded
TheDavSmasher Nov 30, 2024
aa7cd7b
Changed to general AddTypeToTracker
TheDavSmasher Nov 30, 2024
3cc657a
Added static RefreshTracker which will make sure the tracker is up to…
TheDavSmasher Nov 30, 2024
780d16a
Changed Entity and Component methods to AddTypeToTracker in the same …
TheDavSmasher Nov 30, 2024
6bc5a2e
Added RefreshTracker and RefreshTrackerLists to update the tracker ma…
TheDavSmasher Dec 1, 2024
6a2f3cf
TrackedTypes Dictionaries access changed, added RefreshTracker call t…
TheDavSmasher Dec 1, 2024
d6c68cf
Added TrackedAs parameter to AddTypeToTracker
TheDavSmasher Dec 1, 2024
4dd4556
Changed type field to be Type instead of string
TheDavSmasher Dec 1, 2024
e6a0da1
Made PascalCase and changed to property
TheDavSmasher Dec 1, 2024
d4cdbf7
Changed exception message
TheDavSmasher Dec 1, 2024
5b6b2e0
Changed Initialize to use AddTypeToTracker to avoid duplicate code
TheDavSmasher Dec 1, 2024
8d65602
Removed parameterized RefreshTracker
TheDavSmasher Dec 1, 2024
610ffb3
Made refresh instance method
TheDavSmasher Dec 1, 2024
a35664d
Made non-static and added check if already inside tracker to prevent …
TheDavSmasher Dec 1, 2024
024a821
Added instance field Unrefreshed to prevent Refresh being executed ag…
TheDavSmasher Dec 1, 2024
64d8f9c
Added note to summary for Refresh
TheDavSmasher Dec 1, 2024
56b0549
Overrode Added and EntityAwake methods to Refresh the Tracker: Added …
TheDavSmasher Dec 1, 2024
e9312ac
Do Dictionary check before List indexing
TheDavSmasher Dec 2, 2024
3391348
Fixed summary error
TheDavSmasher Dec 2, 2024
4b1e9ac
Added AddTypeToTracker to Added before refresh, both only if Entity i…
TheDavSmasher Dec 2, 2024
df2f875
Removed unneeded using statement
TheDavSmasher Dec 2, 2024
1bd3b0b
Attempt to reduce duplicate code between adding an Entity Type and Co…
TheDavSmasher Dec 2, 2024
df175d3
Changed Exception Message to account for Tracked vs TrackedAs additions
TheDavSmasher Dec 2, 2024
7d6ad9f
Small refactors
TheDavSmasher Dec 6, 2024
c1a7050
Made untracked and removed Type property
TheDavSmasher Dec 6, 2024
42fac47
use version to determine if tracker is outdated
wuke32767 Dec 3, 2024
4edfb38
Merge pull request #2 from wuke32767-s-clone/addColliderComponents
TheDavSmasher Dec 6, 2024
5c4d285
Simplified assignment and boolean expressions
TheDavSmasher Dec 6, 2024
2170b46
Added Summary and set Tracker Version to default after initialization…
TheDavSmasher Dec 6, 2024
4da146f
Made TrackedTypeVersion private and removed Summary
TheDavSmasher Dec 6, 2024
d21dcfd
Made static and requires a scene parameter, which defaults to Engine.…
TheDavSmasher Dec 9, 2024
79ad2ff
Changed parameter name and null check
TheDavSmasher Dec 9, 2024
797ca53
Added summary note of default value of scene parameter
TheDavSmasher Dec 9, 2024
5385f3e
Changed method call to static call
TheDavSmasher Dec 9, 2024
9ef4ab5
Called Refresh with the scene of the Entity
TheDavSmasher Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions Celeste.Mod.mm/Mod/Entities/EntityCollider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Monocle;
using System;
using Microsoft.Xna.Framework;

namespace Celeste.Mod.Entities {
/// <summary>
/// Allows for Collision with any type of entity in the game, similar to a PlayerCollider or PufferCollider.
/// Performs the Action provided on collision.
/// </summary>
/// <typeparam name="T">The specific type of Entity this component should try to collide with</typeparam>
public class EntityCollider<T> : Component where T : Entity {
JaThePlayer marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// The Action invoked on Collision, with the Entity collided with passed as a parameter
/// </summary>
public Action<T> OnEntityAction;

public Collider Collider;

public EntityCollider(Action<T> onEntityAction, Collider collider = null)
: base(active: true, visible: true) {
OnEntityAction = onEntityAction;
Collider = collider;
}

public override void Added(Entity entity) {
base.Added(entity);
//Only called if Component is added post Scene Begin and Entity Adding and Awake time.
if (Scene != null) {
if (!Scene.Tracker.IsEntityTracked<T>()) {
patch_Tracker.AddTypeToTracker(typeof(T));
}
patch_Tracker.Refresh(Scene);
}
}

public override void EntityAdded(Scene scene) {
if (!scene.Tracker.IsEntityTracked<T>()) {
patch_Tracker.AddTypeToTracker(typeof(T));
}
base.EntityAdded(scene);
}

public override void EntityAwake() {
patch_Tracker.Refresh(Scene);
}

public override void Update() {
if (OnEntityAction == null) {
return;
}

Collider collider = Entity.Collider;
if (Collider != null) {
Entity.Collider = Collider;
}

Entity.CollideDo(OnEntityAction);

Entity.Collider = collider;
}

public override void DebugRender(Camera camera) {
if (Collider != null) {
Collider collider = Entity.Collider;
Entity.Collider = Collider;
Collider.Render(camera, Color.HotPink);
Entity.Collider = collider;
JaThePlayer marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
72 changes: 72 additions & 0 deletions Celeste.Mod.mm/Mod/Entities/EntityColliderByComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Monocle;
using System;
using Microsoft.Xna.Framework;

namespace Celeste.Mod.Entities {
/// <summary>
/// Allows for Collision with any type of entity in the game, similar to a PlayerCollider or PufferCollider.
/// Collision is done by component, as in, it will get all the components of the type and try to collide with their entities.
/// Performs the Action provided on collision.
/// </summary>
/// <typeparam name="T">The specific type of Component this component should try to collide with</typeparam>
public class EntityColliderByComponent<T> : Component where T : Component {
/// <summary>
/// The Action invoked on Collision, with the Component collided with passed as a parameter
/// </summary>
public Action<T> OnComponentAction;

public Collider Collider;

public EntityColliderByComponent(Action<T> onComponentAction, Collider collider = null)
: base(active: true, visible: true) {
OnComponentAction = onComponentAction;
Collider = collider;
}

public override void Added(Entity entity) {
base.Added(entity);
//Only called if Component is added post Scene Begin and Entity Adding and Awake time.
if (Scene != null) {
if (!Scene.Tracker.IsComponentTracked<T>()) {
patch_Tracker.AddTypeToTracker(typeof(T));
}
patch_Tracker.Refresh(Scene);
}
}

public override void EntityAdded(Scene scene) {
if (!scene.Tracker.IsComponentTracked<T>()) {
patch_Tracker.AddTypeToTracker(typeof(T));
}
base.EntityAdded(scene);
}

public override void EntityAwake() {
patch_Tracker.Refresh(Scene);
}

public override void Update() {
if (OnComponentAction == null) {
return;
}

Collider collider = Entity.Collider;
if (Collider != null) {
Entity.Collider = Collider;
}

Entity.CollideDoByComponent(OnComponentAction);

Entity.Collider = collider;
}

public override void DebugRender(Camera camera) {
if (Collider != null) {
Collider collider = Entity.Collider;
Entity.Collider = Collider;
Collider.Render(camera, Color.HotPink);
Entity.Collider = collider;
}
}
}
}
162 changes: 109 additions & 53 deletions Celeste.Mod.mm/Patches/Monocle/Tracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,26 @@ class patch_Tracker : Tracker {
private static Type[] _temporaryAllTypes;

private static Type[] GetAllTypesUncached() => FakeAssembly.GetFakeEntryAssembly().GetTypesSafe();


private static int TrackedTypeVersion;

private int currentVersion;

public extern void orig_ctor();

[MonoModConstructor]
public void ctor() {
orig_ctor();
currentVersion = TrackedTypeVersion;
}

[MonoModReplace]
private static List<Type> GetSubclasses(Type type) {
bool shouldNullOutCache = _temporaryAllTypes is null;
_temporaryAllTypes ??= GetAllTypesUncached();

List<Type> subclasses = new();
foreach (Type otherType in _temporaryAllTypes)
{
foreach (Type otherType in _temporaryAllTypes) {
if (type != otherType && type.IsAssignableFrom(otherType))
subclasses.Add(otherType);
}
Expand All @@ -56,72 +67,117 @@ private static List<Type> GetSubclasses(Type type) {
// Let's do that now instead.
if (shouldNullOutCache)
_temporaryAllTypes = null;

return subclasses;
}

public static extern void orig_Initialize();
public new static void Initialize() {
_temporaryAllTypes = GetAllTypesUncached();

orig_Initialize();

// search for entities with [TrackedAs]
int oldVersion = TrackedTypeVersion;
foreach (Type type in _temporaryAllTypes) {
object[] customAttributes = type.GetCustomAttributes(typeof(TrackedAsAttribute), inherit: false);
foreach (object customAttribute in customAttributes) {
TrackedAsAttribute trackedAs = customAttribute as TrackedAsAttribute;
Type trackedAsType = trackedAs.TrackedAsType;
bool inherited = trackedAs.Inherited;
if (typeof(Entity).IsAssignableFrom(type)) {
if (!type.IsAbstract) {
// this is an entity. copy the registered types for the target entity
if (!TrackedEntityTypes.ContainsKey(type)) {
TrackedEntityTypes.Add(type, new List<Type>());
}
TrackedEntityTypes[type].AddRange(TrackedEntityTypes.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
TrackedEntityTypes[type] = TrackedEntityTypes[type].Distinct().ToList();
}
if (inherited) {
// do the same for subclasses
foreach (Type subclass in GetSubclasses(type)) {
if (!subclass.IsAbstract) {
if (!TrackedEntityTypes.ContainsKey(subclass))
TrackedEntityTypes.Add(subclass, new List<Type>());
TrackedEntityTypes[subclass].AddRange(TrackedEntityTypes.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
TrackedEntityTypes[subclass] = TrackedEntityTypes[subclass].Distinct().ToList();
}
}
}
} else if (typeof(Component).IsAssignableFrom(type)) {
if (!type.IsAbstract) {
// this is an component. copy the registered types for the target component
if (!TrackedComponentTypes.ContainsKey(type)) {
TrackedComponentTypes.Add(type, new List<Type>());
}
TrackedComponentTypes[type].AddRange(TrackedComponentTypes.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
TrackedComponentTypes[type] = TrackedComponentTypes[type].Distinct().ToList();
}
if (inherited) {
// do the same for subclasses
foreach (Type subclass in GetSubclasses(type)) {
if (!subclass.IsAbstract) {
if (!TrackedComponentTypes.ContainsKey(subclass))
TrackedComponentTypes.Add(subclass, new List<Type>());
TrackedComponentTypes[subclass].AddRange(TrackedComponentTypes.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
TrackedComponentTypes[subclass] = TrackedComponentTypes[subclass].Distinct().ToList();
}
}
}
} else {
// this is neither an entity nor a component. Help!
throw new Exception("Type '" + type.Name + "' cannot be TrackedAs because it does not derive from Entity or Component");
}
AddTypeToTracker(type, trackedAs.TrackedAsType, trackedAs.Inherited);
}
}

TrackedTypeVersion = oldVersion;
// don't hold references to all the types anymore
_temporaryAllTypes = null;
}

public static void AddTypeToTracker(Type type, Type trackedAs = null, bool inheritAll = false) {
AddTypeToTracker(type, trackedAs, inheritAll ? GetSubclasses(type).ToArray() : Array.Empty<Type>());
}

public static void AddTypeToTracker(Type type, Type trackedAs = null, params Type[] subtypes) {
TheDavSmasher marked this conversation as resolved.
Show resolved Hide resolved
Type trackedAsType = trackedAs != null && trackedAs.IsAssignableFrom(type) ? trackedAs : type;
bool? trackedEntity = typeof(Entity).IsAssignableFrom(type) ? true : typeof(Component).IsAssignableFrom(type) ? false : null;
if (trackedEntity == null) {
// this is neither an entity nor a component. Help!
throw new Exception("Type '" + type.Name + "' cannot be Tracked" + (trackedAsType != type ? "As" : "") + " because it does not derive from Entity or Component");
}
bool updated = false;
// copy the registered types for the target type
((bool) trackedEntity ? StoredEntityTypes : StoredComponentTypes).Add(type);
Dictionary<Type, List<Type>> tracked = (bool) trackedEntity ? TrackedEntityTypes : TrackedComponentTypes;
if (!type.IsAbstract) {
if (!tracked.TryGetValue(type, out List<Type> value)) {
value = new List<Type>();
tracked.Add(type, value);
}
int cnt = value.Count;
value.AddRange(tracked.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
List<Type> result = tracked[type] = value.Distinct().ToList();
updated = cnt != result.Count;
}
// do the same for subclasses
foreach (Type subtype in subtypes) {
if (trackedAsType.IsAssignableFrom(subtype) && !subtype.IsAbstract) {
if (!tracked.TryGetValue(subtype, out List<Type> value)) {
value = new List<Type>();
tracked.Add(subtype, value);
}
int cnt = value.Count;
value.AddRange(tracked.TryGetValue(trackedAsType, out List<Type> list) ? list : new List<Type>());
List<Type> result = tracked[subtype] = value.Distinct().ToList();
updated = cnt != result.Count;
}
}
if (updated) {
TrackedTypeVersion++;
}
}

/// <summary>
/// Ensures the <paramref name="toUpdate"/>'s tracker contains all entities of all tracked Types from the <paramref name="toUpdate"/>.
/// Must be called if a type is added to the tracker manually and if the <paramref name="toUpdate"/>'s Tracker isn't refreshed.
/// If called back to back without a type added to the Tracker, it won't go through again, for performance.
/// <paramref name="force"/> will make ensure the Refresh happens, even if run back to back.
/// Only the <paramref name="toUpdate"/>'s Tracker's refreshed state is changed.
/// If <paramref name="toUpdate"/> is null, it will default to Engine.Scene.
/// </summary>
public static void Refresh(Scene toUpdate = null, bool force = false) {
Scene scene = toUpdate ?? Engine.Scene;
if ((scene.Tracker as patch_Tracker).currentVersion >= TrackedTypeVersion && !force) {
return;
}
(scene.Tracker as patch_Tracker).currentVersion = TrackedTypeVersion;
foreach (Type entityType in StoredEntityTypes) {
if (!scene.Tracker.Entities.ContainsKey(entityType)) {
scene.Tracker.Entities.Add(entityType, new List<Entity>());
}
}
foreach (Type componentType in StoredComponentTypes) {
if (!scene.Tracker.Components.ContainsKey(componentType)) {
scene.Tracker.Components.Add(componentType, new List<Component>());
}
}
foreach (Entity entity in scene.Entities) {
foreach (Component component in entity.Components) {
Type componentType = component.GetType();
if (!TrackedComponentTypes.TryGetValue(componentType, out List<Type> componentTypes)
|| scene.Tracker.Components[componentType].Contains(component)) {
continue;
}
foreach (Type trackedType in componentTypes) {
scene.Tracker.Components[trackedType].Add(component);
}
}
Type entityType = entity.GetType();
if (!TrackedEntityTypes.TryGetValue(entityType, out List<Type> entityTypes)
|| scene.Tracker.Entities[entityType].Contains(entity)) {
continue;
}
foreach (Type trackedType in entityTypes) {
scene.Tracker.Entities[trackedType].Add(entity);
}
}
}
}
}