EverestAPI / EverestAPI/Everest

add attribute to make entities always load in the first room entered, regardless of what room they are placed in

Open
#653 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.