Dictionary<int2, PersistentBehaviorData[]> has no hash-resistant comparer under CultCache's UntrustedData security, so any run store holding an entity threw MessagePackSerializationException on reopen, even when the dictionary was empty. EntityPack.PersistedBehaviors (key 5, unchanged) becomes (int2 position, PersistentBehaviorData[] data)[], matching Equipment/CargoBays/DockingBays. Pack writes pairs; Unpack builds a local lookup before restoring behaviors. PersistentBehaviorData had no concrete leaf and no Union, so nothing could ever exercise its wire format; added MarkerPersistentBehaviorData as the one placeholder leaf so ReopenedPackKeepsPersistedBehaviors can round-trip real data ahead of a real IPersistentBehavior implementer landing. Tests: RunSaveTests.BarePack uses an empty pairs array. Added ReopenedPackKeepsPersistedBehaviors (a hand-built pack with two positions, each with one persisted behavior, round-trips through commit and reopen) and ReopenSurvivesAGeneratedShipWithEntities (a real ship from LoadoutGenerator against a catalog fixture, committed into a SavedZone and reopened, pinning the defect at the layer Soul's probe found it). MaterializedLotsSurviveSaveAndReload and CommitKeepsOnlyReachableLots go green; 36 of 36 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
211 lines
No EOL
5.7 KiB
C#
211 lines
No EOL
5.7 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using MessagePack;
|
|
using Newtonsoft.Json;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
|
|
public abstract class Behavior
|
|
{
|
|
public BehaviorData Data { get; }
|
|
public EquippedItem Item { get; }
|
|
private ConsumableItemEffect Consumable { get; }
|
|
protected ItemManager ItemManager { get; }
|
|
|
|
protected Entity Entity => Item?.Entity ?? Consumable.Entity;
|
|
public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp;
|
|
|
|
public float3 Direction
|
|
{
|
|
get
|
|
{
|
|
if(Item != null)
|
|
{
|
|
var hardpoint = Entity.Hardpoints[Item.Position.x, Item.Position.y];
|
|
if (hardpoint != null && Entity.HardpointTransforms.ContainsKey(hardpoint))
|
|
{
|
|
return normalize(Entity.HardpointTransforms[hardpoint].direction);
|
|
}
|
|
else
|
|
{
|
|
var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation);
|
|
return float3(itemDirection.x, 0, itemDirection.y);
|
|
}
|
|
}
|
|
|
|
return float3(Entity.Direction.x, 0, Entity.Direction.y);
|
|
}
|
|
}
|
|
|
|
protected Behavior(BehaviorData data, EquippedItem item)
|
|
{
|
|
Data = data;
|
|
Item = item;
|
|
ItemManager = Item.ItemManager;
|
|
}
|
|
|
|
protected Behavior(BehaviorData data, ConsumableItemEffect consumable)
|
|
{
|
|
Data = data;
|
|
Consumable = consumable;
|
|
ItemManager = consumable.Entity.ItemManager;
|
|
}
|
|
|
|
public float Evaluate(PerformanceStat stat) => Item?.Evaluate(stat) ?? Consumable.Evaluate(stat);
|
|
protected void AddHeat(float heat) => Item?.AddHeat(heat); // TODO: Heat for Consumables
|
|
|
|
protected void CauseDamage(float damage)
|
|
{
|
|
if (Item != null)
|
|
{
|
|
Item.EquippableItem.Durability -= damage;
|
|
Entity.ItemDamage.OnNext((Item, damage));
|
|
}
|
|
else
|
|
{
|
|
Consumable.Entity.Hull.Durability -= damage;
|
|
Consumable.Entity.HullDamage.OnNext(damage);
|
|
}
|
|
}
|
|
|
|
protected void CauseWearDamage(float multiplier)
|
|
{
|
|
if (Item != null)
|
|
{
|
|
CauseDamage(Item.Wear * multiplier);
|
|
}
|
|
}
|
|
|
|
public virtual bool Execute(float dt)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public interface IActivatedBehavior
|
|
{
|
|
void Activate();
|
|
void Deactivate();
|
|
}
|
|
|
|
public interface IAnalogBehavior
|
|
{
|
|
float Axis { get; set; }
|
|
}
|
|
|
|
public interface IEventBehavior
|
|
{
|
|
void ResetEvents();
|
|
}
|
|
|
|
public interface IInitializableBehavior
|
|
{
|
|
void Initialize();
|
|
}
|
|
|
|
public interface IInteractiveBehavior
|
|
{
|
|
bool Exposed { get; }
|
|
}
|
|
|
|
public interface IAlwaysUpdatedBehavior
|
|
{
|
|
void Update(float delta);
|
|
}
|
|
|
|
public interface IProgressBehavior
|
|
{
|
|
float Progress { get; }
|
|
}
|
|
|
|
public interface IOrderedBehavior
|
|
{
|
|
int Order { get; }
|
|
}
|
|
|
|
public interface IPersistentBehavior//<T> where T : IBehavior
|
|
{
|
|
PersistentBehaviorData Store();
|
|
void Restore(PersistentBehaviorData data);
|
|
}
|
|
|
|
public interface IPopulationAssignment
|
|
{
|
|
int AssignedPopulation { get; set; }
|
|
}
|
|
|
|
[MessagePackObject,
|
|
JsonObject(MemberSerialization.OptIn),
|
|
Union(0, typeof(MarkerPersistentBehaviorData))]
|
|
public abstract class PersistentBehaviorData
|
|
{
|
|
}
|
|
|
|
// No IBehavior implements IPersistentBehavior yet, so this abstract type has no production writer. This one
|
|
// concrete leaf exists so the wire format (EntitySerializer's PersistedBehaviors pairs array) can be exercised by
|
|
// tests ahead of a real persistent behavior landing.
|
|
[MessagePackObject]
|
|
public class MarkerPersistentBehaviorData : PersistentBehaviorData
|
|
{
|
|
[Key(0)] public int Value;
|
|
}
|
|
|
|
[Inspectable,
|
|
Union(0, typeof(GuidedWeaponData)),
|
|
Union(1, typeof(LauncherData)),
|
|
Union(2, typeof(ReactorData)),
|
|
Union(3, typeof(RadiatorData)),
|
|
Union(4, typeof(StatModifierData)),
|
|
Union(5, typeof(SensorData)),
|
|
Union(6, typeof(ReflectorData)),
|
|
Union(7, typeof(ShieldData)),
|
|
Union(8, typeof(ThrusterData)),
|
|
Union(9, typeof(WearData)),
|
|
Union(10, typeof(VelocityConversionData)),
|
|
Union(11, typeof(VelocityLimitData)),
|
|
Union(12, typeof(AetherDriveData)),
|
|
// Union(14, typeof(TowingControllerData)),
|
|
Union(15, typeof(CooldownData)),
|
|
Union(16, typeof(HeatData)),
|
|
// Union(17, typeof(HitscanData)),
|
|
Union(18, typeof(ItemUsageData)),
|
|
//Union(19, typeof(RadianceData)),
|
|
Union(20, typeof(SwitchData)),
|
|
Union(21, typeof(TriggerData)),
|
|
Union(22, typeof(VisibilityData)),
|
|
Union(23, typeof(ThermotoggleData)),
|
|
Union(24, typeof(EnergyDrawData)),
|
|
// Union(25, typeof(MiningControllerData)),
|
|
Union(26, typeof(MiningToolData)),
|
|
// Union(27, typeof(SurveyControllerData)),
|
|
Union(28, typeof(ResourceScannerData)),
|
|
// Union(29, typeof(WanderControllerData)),
|
|
// Union(30, typeof(HaulingControllerData)),
|
|
Union(31, typeof(CapacitorData)),
|
|
Union(32, typeof(CockpitData)),
|
|
Union(33, typeof(HeatStorageData)),
|
|
Union(34, typeof(TurretControllerData)),
|
|
Union(35, typeof(InstantWeaponData)),
|
|
Union(36, typeof(ConstantWeaponData)),
|
|
Union(37, typeof(ChargedWeaponData)),
|
|
Union(38, typeof(AutoWeaponData)),
|
|
JsonObject(MemberSerialization.OptIn)]
|
|
public abstract class BehaviorData
|
|
{
|
|
[Inspectable, JsonProperty("group"), Key(0)]
|
|
public int Group;
|
|
|
|
public abstract Behavior CreateInstance(EquippedItem item);
|
|
public abstract Behavior CreateInstance(ConsumableItemEffect consumable);
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString().FormatTypeName();
|
|
}
|
|
} |