Query target contacts from daemon snapshots

This commit is contained in:
Meta Vens 2026-06-21 17:30:14 +01:00
commit 710ce0fc70
3 changed files with 152 additions and 17 deletions

View file

@ -1838,6 +1838,8 @@ static void RequireDaemonRenderQueryAuthority(string root)
"public static int QueryCompassMarkers(",
"public static int[] QueryVisibleEntityIndices(",
"public static int QueryVisibleEntityIndices(",
"AetheriaRuntimeDaemonEntityContact",
"public static bool TryQueryEntityContact(",
"AetheriaRuntimeDaemonWormholeExit",
"public static AetheriaRuntimeDaemonWormholeExit[] QueryWormholeExits(",
"public static int QueryWormholeExits(",
@ -7712,12 +7714,22 @@ static void RequireMainMenuContinueRunState(string root)
"lockWeapon.RestoreRuntimeState(",
"drive.RestoreRuntimeState(",
"resourceScanner.RestoreRuntimeState(",
"AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(",
"GetObservedInfoGathered(CurrentEntity, target)",
"IsObservedHostileContact(CurrentEntity, CurrentEntity.Target.Value)",
"_observedEntityFacadesByRecordKey",
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
"RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)",
"entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled",
"RestoreDroppedPickupsFromDaemonZoneState"
};
if (actionGameManager.Contains("_authoritativeDaemonEntities", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Unity entity wrappers are observed daemon facades, not authoritative state; keep the daemon as authority.");
}
var missingGameplaySymbols = requiredGameplaySymbols
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
.ToArray();
@ -7732,7 +7744,10 @@ static void RequireMainMenuContinueRunState(string root)
var forbiddenGameplaySymbols = new[]
{
"if (run == null ||\r\n run.CurrentZoneIndex < 0 ||\r\n run.CurrentZoneEntityIndex < 0)",
"var currentEntityKey = $\"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1\""
"var currentEntityKey = $\"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1\"",
"TargetVisibilityFill.fillAmount = Mathf.Lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold) / (1 - threshold));",
"VisibilityToTargetFill.fillAmount = Mathf.Lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold);",
"CurrentEntity.Target.Value.IsHostileTo(CurrentEntity)"
};
var forbiddenGameplayHits = forbiddenGameplaySymbols

View file

@ -66,7 +66,7 @@ public class ActionGameManager : MonoBehaviour
private string _lastAppliedAuthoritativeDaemonFramePath = "";
private string _lastAppliedAuthoritativeDaemonRunId = "";
private int _lastAppliedAuthoritativeDaemonZoneIndex = -1;
private readonly Dictionary<string, Entity> _authoritativeDaemonEntities = new Dictionary<string, Entity>(StringComparer.Ordinal);
private readonly Dictionary<string, Entity> _observedEntityFacadesByRecordKey = new Dictionary<string, Entity>(StringComparer.Ordinal);
private static DirectoryInfo _gameDataDirectory;
public static DirectoryInfo GameDataDirectory
{
@ -2665,14 +2665,14 @@ public class ActionGameManager : MonoBehaviour
{
if (!string.Equals(_lastAppliedAuthoritativeDaemonRunId, runId, StringComparison.Ordinal) ||
_lastAppliedAuthoritativeDaemonZoneIndex != zoneIndex ||
_authoritativeDaemonEntities.Count != entitySnapshots.Count)
_observedEntityFacadesByRecordKey.Count != entitySnapshots.Count)
{
return false;
}
foreach (var snapshot in entitySnapshots)
{
if (!_authoritativeDaemonEntities.ContainsKey(snapshot.RecordKey))
if (!_observedEntityFacadesByRecordKey.ContainsKey(snapshot.RecordKey))
{
return false;
}
@ -2688,7 +2688,7 @@ public class ActionGameManager : MonoBehaviour
{
foreach (var entitySnapshot in entitySnapshots)
{
if (!_authoritativeDaemonEntities.TryGetValue(entitySnapshot.RecordKey, out var entity))
if (!_observedEntityFacadesByRecordKey.TryGetValue(entitySnapshot.RecordKey, out var entity))
continue;
entity.DaemonEntityIndex = EntityIndexFromRecordKey(entitySnapshot.RecordKey);
@ -2702,10 +2702,10 @@ public class ActionGameManager : MonoBehaviour
if (entity.Settings != null)
entity.Settings.ShutdownPerformance = (float)entitySnapshot.ShutdownPerformance;
entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia);
RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, _authoritativeDaemonEntities);
RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, _observedEntityFacadesByRecordKey);
}
foreach (var entity in _authoritativeDaemonEntities.Values)
foreach (var entity in _observedEntityFacadesByRecordKey.Values)
{
entity.EntityInfoGathered.Clear();
entity.EntityHostility.Clear();
@ -2717,15 +2717,15 @@ public class ActionGameManager : MonoBehaviour
foreach (var entitySnapshot in entitySnapshots)
{
if (!_authoritativeDaemonEntities.TryGetValue(entitySnapshot.RecordKey, out var entity))
if (!_observedEntityFacadesByRecordKey.TryGetValue(entitySnapshot.RecordKey, out var entity))
continue;
RestoreEntityContactsFromTypedSnapshot(entity, entitySnapshot, _authoritativeDaemonEntities);
if (_authoritativeDaemonEntities.TryGetValue(entitySnapshot.TargetEntityKey, out var target))
RestoreEntityContactsFromTypedSnapshot(entity, entitySnapshot, _observedEntityFacadesByRecordKey);
if (_observedEntityFacadesByRecordKey.TryGetValue(entitySnapshot.TargetEntityKey, out var target))
entity.Target.Value = target;
}
if (_authoritativeDaemonEntities.TryGetValue(currentEntityKey, out var currentEntity) &&
if (_observedEntityFacadesByRecordKey.TryGetValue(currentEntityKey, out var currentEntity) &&
CurrentEntity != currentEntity)
{
RestoreCurrentEntityBinding(currentEntity, Array.Empty<AetheriaRuntimeActionBarBindingSnapshot>());
@ -2788,9 +2788,9 @@ public class ActionGameManager : MonoBehaviour
entity.Target.Value = target;
}
_authoritativeDaemonEntities.Clear();
_observedEntityFacadesByRecordKey.Clear();
foreach (var restoredEntity in restoredEntities)
_authoritativeDaemonEntities[restoredEntity.Key] = restoredEntity.Value;
_observedEntityFacadesByRecordKey[restoredEntity.Key] = restoredEntity.Value;
if (restoredEntities.TryGetValue(currentEntityKey, out var currentEntity))
RestoreCurrentEntityBinding(currentEntity, actionBarBindings);
@ -3135,6 +3135,42 @@ public class ActionGameManager : MonoBehaviour
return FindDaemonZoneSnapshot(CurrentDaemonGalaxyZone);
}
private bool TryQueryDaemonEntityContact(
Entity observer,
Entity target,
out AetheriaRuntimeDaemonEntityContact contact)
{
contact = default;
if (observer == null || target == null)
return false;
return AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(
FindCurrentDaemonZoneSnapshot(),
observer.DaemonEntityIndex,
target.DaemonEntityIndex,
out contact);
}
private float GetObservedInfoGathered(Entity observer, Entity target)
{
if (TryQueryDaemonEntityContact(observer, target, out var contact))
return (float)contact.InfoGathered;
return observer != null &&
target != null &&
observer.EntityInfoGathered.TryGetValue(target, out var infoGathered)
? infoGathered
: 0f;
}
private bool IsObservedHostileContact(Entity observer, Entity target)
{
if (TryQueryDaemonEntityContact(observer, target, out var contact))
return contact.Hostile;
return target != null && target.IsHostileTo(observer);
}
public void RequestTowToStation()
{
TryRequestDaemonTowToStation();
@ -3442,8 +3478,10 @@ public class ActionGameManager : MonoBehaviour
if (target != null)
{
var threshold = Settings.GameplaySettings.TargetDetectionInfoThreshold;
TargetVisibilityFill.fillAmount = Mathf.Lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold) / (1 - threshold));
VisibilityToTargetFill.fillAmount = Mathf.Lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold);
var targetInfoGathered = GetObservedInfoGathered(CurrentEntity, target);
var visibilityToTarget = GetObservedInfoGathered(target, CurrentEntity);
TargetVisibilityFill.fillAmount = Mathf.Lerp(.25f, .75f, (targetInfoGathered - threshold) / (1 - threshold));
VisibilityToTargetFill.fillAmount = Mathf.Lerp(.25f, .75f, visibilityToTarget / threshold);
var targetHull = ItemManager.GetRuntimeItem(target.Hull);
var targetMaxDurability = targetHull?.Durability > 0
? (float)targetHull.Durability
@ -3887,7 +3925,7 @@ public class ActionGameManager : MonoBehaviour
return "";
}
foreach (var pair in _authoritativeDaemonEntities)
foreach (var pair in _observedEntityFacadesByRecordKey)
{
if (ReferenceEquals(pair.Value, entity))
return pair.Key;
@ -4009,7 +4047,9 @@ public class ActionGameManager : MonoBehaviour
foreach (var (targetLock, indicator, spin) in _lockingIndicators)
{
var showLockingIndicator = targetLock.Lock > .01f && CurrentEntity.Target.Value != null && CurrentEntity.Target.Value.IsHostileTo(CurrentEntity);
var showLockingIndicator = targetLock.Lock > .01f &&
CurrentEntity.Target.Value != null &&
IsObservedHostileContact(CurrentEntity, CurrentEntity.Target.Value);
indicator.gameObject.SetActive(showLockingIndicator);
if(showLockingIndicator)
{

View file

@ -188,6 +188,44 @@ namespace GameCult.Aetheria.State.Verse
public bool Hostile { get; }
}
public readonly struct AetheriaRuntimeDaemonEntityContact
{
public AetheriaRuntimeDaemonEntityContact(
int observerEntityIndex,
int targetEntityIndex,
double targetPositionX,
double targetPositionZ,
double deltaX,
double deltaZ,
double distance,
double infoGathered,
bool hostile,
bool visible)
{
ObserverEntityIndex = observerEntityIndex;
TargetEntityIndex = targetEntityIndex;
TargetPositionX = targetPositionX;
TargetPositionZ = targetPositionZ;
DeltaX = deltaX;
DeltaZ = deltaZ;
Distance = Math.Max(0, distance);
InfoGathered = infoGathered;
Hostile = hostile;
Visible = visible;
}
public int ObserverEntityIndex { get; }
public int TargetEntityIndex { get; }
public double TargetPositionX { get; }
public double TargetPositionZ { get; }
public double DeltaX { get; }
public double DeltaZ { get; }
public double Distance { get; }
public double InfoGathered { get; }
public bool Hostile { get; }
public bool Visible { get; }
}
public readonly struct AetheriaRuntimeDaemonWormholeExit
{
public AetheriaRuntimeDaemonWormholeExit(
@ -521,6 +559,48 @@ namespace GameCult.Aetheria.State.Verse
return entityIndices.Count;
}
public static bool TryQueryEntityContact(
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
int targetEntityIndex,
out AetheriaRuntimeDaemonEntityContact entityContact)
{
entityContact = default;
if (zone == null || observerEntityIndex < 0 || targetEntityIndex < 0)
return false;
var entities = BuildEntityMap(zone);
if (!entities.TryGetValue(observerEntityIndex, out var observer) ||
!entities.TryGetValue(targetEntityIndex, out var target))
{
return false;
}
foreach (var contact in observer.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
{
if (contact == null || contact.TargetEntityIndex != targetEntityIndex)
continue;
var deltaX = target.PositionX - observer.PositionX;
var deltaZ = target.PositionZ - observer.PositionZ;
var distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ);
entityContact = new AetheriaRuntimeDaemonEntityContact(
observer.EntityIndex,
target.EntityIndex,
target.PositionX,
target.PositionZ,
deltaX,
deltaZ,
distance,
contact.InfoGathered,
contact.Hostile,
contact.Visible);
return true;
}
return false;
}
public static AetheriaRuntimeDaemonWormholeExit[] QueryWormholeExits(
AetheriaRuntimeRunCheckpointCommit? run,
AetheriaRuntimeZoneSnapshotCommit? zone,