EverestAPI / EverestAPI/Everest
add attribute to make entities always load in the first room entered, regardless of what room they are placed in
- Dominant language
- C#
- Stars
- 516
- Forks
- 106
- Avg merge
- 4d 9h
- Merged PRs (30d)
- 5
Description
### i am planning on implementing this myself
Yes
### describe your request
i don't really have a good name for this attribute _(currently i'm calling it `GlobalEntityAttribute`, but global is already a different thing, so if anyone else has a better name i'm happy to change it)_, but it's useful for a variety of things, principally map-wide controllers.
currently i have this in an as-of-yet unreleased helper, so i have a working implementation, but it seems common enough to deserve a place in Everest.
it works by filtering out any entitydata entries corresponding to classes with the annotation during `MapData.Load`, storing them in a `List>` associated with the mapdata, and then adding them to the level in `LevelLoader.LoadingThread`.
the way the entity names corresponding to attribute-having classes are obtained currently requires the entities to be tracked, but this could be easily changed.
### additional context
full example code
```cs
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GlobalEntityAttribute : Attribute {
private static string[] _entityNames;
private static string[] entityNames {
get {
if (_entityNames != null)
return _entityNames;
_entityNames = Tracker.TrackedEntityTypes
.SelectMany((pair) => {
if (pair.Key.IsDefined(typeof(GlobalEntityAttribute))) {
var attrs = pair.Key
.GetCustomAttributes()
.SelectMany((a) =>
a.IDs.SelectMany((id) => {
var parts = id.Split('=');
if (parts.Length > 0)
return new string[] { parts[0] };
return new string[] { };
})
);
return attrs.ToList();
}
return new List();
}).ToArray();
return _entityNames;
}
}
internal static void Load() {
Everest.Events.LevelLoader.OnLoadingThread += onLoad;
On.Celeste.MapData.Load += onMapDataLoad;
}
internal static void Unload() {
Everest.Events.LevelLoader.OnLoadingThread -= onLoad;
On.Celeste.MapData.Load -= onMapDataLoad;
}
private static void onMapDataLoad(On.Celeste.MapData.orig_Load orig, MapData mapData) {
orig(mapData);
var dyndata = DynamicData.For(mapData);
List> entities;
if (dyndata.TryGet("global_entities", out entities)) {
entities.Clear();
} else {
entities = new();
dyndata.Set("global_entities", entities);
}
var names = entityNames;
foreach (var levelData in mapData.Levels) {
levelData.Entities.RemoveAll((e) => {
if (!names.Contains(e.Name))
return false;
entities.Add(new(e, levelData.Position));
return true;
});
}
}
private static void onLoad(Level level) {
var mapData = level.Session.MapData;
var dyndata = DynamicData.For(mapData);
if (dyndata.TryGet>>("global_entities", out var shadowData)) {
foreach (var shadow in shadowData) {
var loader = Level.EntityLoaders[shadow.Item1.Name];
var entity = loader.Invoke(level, shadow.Item1.Level, shadow.Item2, shadow.Item1);
if (entity is not null)
level.Add(entity);
}
}
}
}
```
Contributor guide
Assessment
This issue has not been assessed yet.