Move the ServerShared data model onto CultLib's CultCache

Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.

Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.

References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.

AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].

Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.

Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 19:00:42 +02:00
commit a2b54889b4
50 changed files with 537 additions and 542 deletions

View file

@ -25,6 +25,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CultLibRoot)\packages\cultmath\src\CultMath\CultMath.csproj" />
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Caching\GameCult.Caching.csproj" />
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,57 @@
using System;
using System.Linq;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
// Aetheria's stores: one home store per type, composed here and nowhere else.
public static class AetheriaStores
{
public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(NameFile) };
public static readonly Type[] RunTypes = { typeof(OrbitData), typeof(BodyData), typeof(SavedZone), typeof(SavedGame) };
public static readonly Type[] PlayerTypes = { typeof(PlayerSettings), typeof(InputLayout) };
// Attaches (hydrates) the catalog read-only unless catalogWritable, then the run and player stores when given.
// A read-only catalog throws when a [CultGlobal] type routed to it has no record. A writable catalog is being
// authored (the importer starts from an empty file), so it is not held to that.
public static CultCache Open(string catalogPath, string runPath = null, string playerPath = null, bool catalogWritable = false)
{
var cache = new CultCache();
try
{
cache.AddBackingStore(new SingleFileMessagePackBackingStore(catalogPath, !catalogWritable), CatalogTypes);
if (runPath != null) cache.AddBackingStore(new SingleFileMessagePackBackingStore(runPath), RunTypes);
if (playerPath != null) cache.AddBackingStore(new SingleFileMessagePackBackingStore(playerPath), PlayerTypes);
var missing = catalogWritable ? null : cache.Registry.AllDescriptors.FirstOrDefault(descriptor =>
descriptor.IsGlobal &&
CatalogTypes.Any(home => home.IsAssignableFrom(descriptor.DocumentType)) &&
cache.AllStoredDocuments.All(stored => stored.Descriptor != descriptor));
if (missing != null)
throw new InvalidOperationException($"Catalog {catalogPath} has no {missing.SchemaName} record; catalog globals are authored, never invented.");
return cache;
}
catch
{
cache.Dispose();
throw;
}
}
}
public static class CultRecordRefs
{
public static bool IsSet(this CultRecordKey key) => !string.IsNullOrEmpty(key.Value);
public static bool IsSet<T>(this CultRecordRef<T> reference) => reference.Key.IsSet();
// The document a reference names, or null when it is unset or names nothing this cache holds.
public static T Get<T>(this CultCache cache, CultRecordRef<T> reference) where T : class =>
reference.IsSet() ? cache.Get<T>(reference.Key) : null;
// A reference to a document this cache holds. The key comes from the cache, never from the document.
public static CultRecordRef<T> RefOf<T>(this CultCache cache, T document) where T : class =>
new CultRecordRef<T>(cache.TryGetHandle(document)?.Key ??
throw new InvalidOperationException($"This cache holds no such {document.GetType().Name}."));
// Writes a document to its home store; the cache mints its key the first time.
public static CultRecordRef<T> Upsert<T>(this CultCache cache, T document) where T : class =>
new CultRecordRef<T>(cache.UpsertAsync(document).Result.Key);
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a37c91e4d05b4f8e82c6b1d9e4f0a2c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,4 +1,5 @@
using GameCult.Caching;
using System;
using CultMath;
using static CultMath.math;
@ -29,7 +30,7 @@ public abstract class MoveToState : BaseState
public class MoveToOrbitState : MoveToState
{
public Guid Orbit { get; set; }
public CultRecordKey Orbit { get; set; }
public MoveToOrbitState(Agent agent) : base(agent) { }
protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(Orbit);

View file

@ -1,31 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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 JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject,
Union(0, typeof(StationTowing)),
Union(1, typeof(Mining)),
Union(2, typeof(Survey)),
Union(3, typeof(HaulingTask)),
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
public abstract class AgentTask : DatabaseEntry
// Agent tasks are owned by their Agent and not persisted.
[JsonObject(MemberSerialization.OptIn)]
public abstract class AgentTask
{
[JsonProperty("priority"), Key(1)]
public int Priority;
[JsonProperty("zone"), Key(2)]
public Guid Zone;
[JsonProperty("reserved"), Key(3)]
public bool Reserved;
[IgnoreMember] public abstract TaskType Type { get; }
}

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using System;
using System.Collections;
using System.Collections.Generic;
@ -19,7 +20,7 @@ public class HaulingTask : AgentTask
public Entity Target;
[JsonProperty("itemType"), Key(6)]
public Guid ItemType;
public CultRecordKey ItemType;
[JsonProperty("quantity"), Key(7)]
public int Quantity;

View file

@ -2,19 +2,19 @@
* 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 GameCult.Caching;
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
JsonObject(MemberSerialization.OptIn)]
public class Mining : AgentTask
{
[IgnoreMember] public override TaskType Type => TaskType.Mine;
[JsonProperty("asteroids"), Key(4)]
public Guid Asteroids;
public CultRecordKey Asteroids;
}

View file

@ -1,15 +1,16 @@
using GameCult.Caching;
using System;
public class PatrolOrbitsTask : AgentTask
{
public override TaskType Type => TaskType.Defend;
public Guid[] Circuit;
public CultRecordKey[] Circuit;
}
public class PatrolOrbitsState : BaseState
{
public PatrolOrbitsTask Task;
public Guid CurrentTarget
public CultRecordKey CurrentTarget
{
get => Task.Circuit[_currentTargetIndex];
}

View file

@ -2,15 +2,15 @@
* 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 GameCult.Caching;
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
JsonObject(MemberSerialization.OptIn)]
public class StationTowing : AgentTask
{
[IgnoreMember] public override TaskType Type => TaskType.Tow;
@ -19,7 +19,7 @@ public class StationTowing : AgentTask
public OrbitalEntity Station;
[JsonProperty("orbitParent"), Key(5)]
public Guid OrbitParent;
public CultRecordKey OrbitParent;
[JsonProperty("orbitDistance"), Key(6)]
public float OrbitDistance;

View file

@ -2,19 +2,19 @@
* 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 GameCult.Caching;
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
JsonObject(MemberSerialization.OptIn)]
public class Survey : AgentTask
{
[IgnoreMember] public override TaskType Type => TaskType.Explore;
[JsonProperty("station"), Key(4)]
public List<Guid> Planets;
public List<CultRecordKey> Planets;
}

View file

@ -0,0 +1,4 @@
using GameCult.Caching.MessagePack;
// CultCache serializes this assembly's documents with MathResolver ahead of MessagePack's standard resolvers.
[assembly: CultCacheFormatterResolver(typeof(MathResolver))]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d8e2b7a1c4f4e3a9b6d0f2c8a1e7b43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
@ -40,13 +41,13 @@ public class AetherDriveData : BehaviorData
[Inspectable, JsonProperty("passiveCoupling"), Key(10), RuntimeInspectable]
public PerformanceStat PassiveCoupling;
[InspectableAudioParameter, JsonProperty("rpmAudio"), Key(11), RuntimeInspectable]
[Inspectable, JsonProperty("rpmAudio"), Key(11), RuntimeInspectable]
public uint RpmAudioParameter;
[InspectableAudioParameter, JsonProperty("torqueAudio"), Key(12), RuntimeInspectable]
[Inspectable, JsonProperty("torqueAudio"), Key(12), RuntimeInspectable]
public uint TorqueRatioAudioParameter;
[InspectablePrefab, JsonProperty("particles"), Key(13)]
[Inspectable, CultInspectorAssetPath, JsonProperty("particles"), Key(13)]
public string Particles;
public override Behavior CreateInstance(EquippedItem item)

View file

@ -6,7 +6,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
@ -142,8 +141,7 @@ public interface IPopulationAssignment
}
[MessagePackObject,
JsonObject(MemberSerialization.OptIn),
JsonConverter(typeof(JsonKnownTypesConverter<PersistentBehaviorData>))]
JsonObject(MemberSerialization.OptIn)]
public abstract class PersistentBehaviorData
{
}
@ -187,7 +185,7 @@ public abstract class PersistentBehaviorData
Union(36, typeof(ConstantWeaponData)),
Union(37, typeof(ChargedWeaponData)),
Union(38, typeof(AutoWeaponData)),
JsonConverter(typeof(JsonKnownTypesConverter<BehaviorData>)), JsonObject(MemberSerialization.OptIn)]
JsonObject(MemberSerialization.OptIn)]
public abstract class BehaviorData
{
[Inspectable, JsonProperty("group"), Key(0)]

View file

@ -11,7 +11,7 @@ using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ConstantWeaponData : WeaponData
{
[InspectablePrefab, JsonProperty("ammoInterval"), Key(17)]
[Inspectable, JsonProperty("ammoInterval"), Key(17)]
public float AmmoInterval = 1;
public override Behavior CreateInstance(EquippedItem item)
@ -83,7 +83,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
OnStopFiring?.Invoke();
return false;
}
if (_data.AmmoType != Guid.Empty)
if (_data.AmmoType.IsSet())
{
if (_reloading)
{
@ -103,10 +103,10 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
if (_data.MagazineSize > 1 && _ammo > 0) _ammo--;
else
{
var cargo = Entity.FindItemInCargo(_data.AmmoType);
var cargo = Entity.FindItemInCargo(_data.AmmoType.Key);
if (cargo != null)
{
var item = cargo.ItemsOfType[_data.AmmoType][0];
var item = cargo.ItemsOfType[_data.AmmoType.Key][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);

View file

@ -9,7 +9,7 @@ using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-10), RuntimeInspectable]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class CooldownData : BehaviorData
{
[Inspectable, JsonProperty("cooldown"), Key(1), RuntimeInspectable]

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-20), RuntimeInspectable]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class EnergyDrawData : BehaviorData
{
[Inspectable, JsonProperty("draw"), Key(1), RuntimeInspectable]

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(10)]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class HeatData : BehaviorData
{
[Inspectable, JsonProperty("heat"), Key(1), RuntimeInspectable]

View file

@ -18,7 +18,7 @@ public class InstantWeaponData : WeaponData
[Inspectable, JsonProperty("cooldown"), Key(19), RuntimeInspectable]
public PerformanceStat Cooldown = new PerformanceStat();
[InspectablePrefab, JsonProperty("ammoInterval"), Key(20)]
[Inspectable, JsonProperty("ammoInterval"), Key(20)]
public bool SingleAmmoBurst;
public override Behavior CreateInstance(EquippedItem item)
@ -124,12 +124,12 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
}
var hasAmmo = true;
if (_data.AmmoType != Guid.Empty)
if (_data.AmmoType.IsSet())
{
var cargo = Entity.FindItemInCargo(_data.AmmoType);
var cargo = Entity.FindItemInCargo(_data.AmmoType.Key);
if (cargo != null)
{
var item = cargo.ItemsOfType[_data.AmmoType][0];
var item = cargo.ItemsOfType[_data.AmmoType.Key][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);
}

View file

@ -2,16 +2,17 @@
* 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 GameCult.Caching;
using System;
using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-5), RuntimeInspectable]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class ItemUsageData : BehaviorData
{
[InspectableDatabaseLink(typeof(SimpleCommodityData)), JsonProperty("item"), Key(1), RuntimeInspectable]
public Guid Item;
[Inspectable, JsonProperty("item"), Key(1), RuntimeInspectable]
public CultRecordRef<SimpleCommodityData> Item;
public override Behavior CreateInstance(EquippedItem item)
{
@ -40,10 +41,10 @@ public class ItemUsage : Behavior
public override bool Execute(float dt)
{
var cargo = Entity.FindItemInCargo(_data.Item);
var cargo = Entity.FindItemInCargo(_data.Item.Key);
if (cargo == null) return false;
var item = cargo.ItemsOfType[_data.Item][0];
var item = cargo.ItemsOfType[_data.Item.Key][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);
if (item is CraftedItemInstance craftedItemInstance)

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using System;
using System.Linq;
using MessagePack;
@ -9,7 +10,7 @@ using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(10)]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class MiningToolData : BehaviorData
{
[Inspectable, JsonProperty("dps"), Key(1)]
@ -36,7 +37,7 @@ public class MiningToolData : BehaviorData
public class MiningTool : Behavior
{
public Guid AsteroidBelt;
public CultRecordKey AsteroidBelt;
public int Asteroid;
private MiningToolData _data;
@ -56,7 +57,7 @@ public class MiningTool : Behavior
{
Range = Evaluate(_data.Range);
var belt = Entity.Zone.AsteroidBelts[AsteroidBelt];
if (AsteroidBelt != Guid.Empty &&
if (AsteroidBelt.IsSet() &&
Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid) &&
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range)
{

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using System;
using System.Linq;
using MessagePack;
@ -38,18 +39,18 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
private ResourceScannerData _data;
private float _scanTime;
private Guid _scanTarget;
private CultRecordKey _scanTarget;
public float Range { get; private set; }
public float MinimumDensity { get; private set; }
public float ScanDuration { get; private set; }
public Guid ScanTarget
public CultRecordKey ScanTarget
{
get => _scanTarget;
set
{
if (value != _scanTarget)
if (!value.Equals(_scanTarget))
{
_scanTarget = value;
_scanTime = 0;
@ -90,7 +91,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
}
else
{
if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planetData.Orbit)) < Range)
if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planetData.Orbit.Key)) < Range)
{
_scanTime += dt;
if (_scanTime > ScanDuration)

View file

@ -1,4 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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/. */
@ -10,18 +10,19 @@ using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class StatModifierData : BehaviorData
{
[Inspectable, JsonProperty("stat"), Key(1)]
[Inspectable, JsonProperty("stat"), Key(1)]
public StatReference Stat = new StatReference();
[Inspectable, JsonProperty("modifier"), Key(2)]
[Inspectable, JsonProperty("modifier"), Key(2)]
public PerformanceStat Modifier = new PerformanceStat();
[Inspectable, JsonProperty("type"), Key(3)]
[Inspectable, JsonProperty("type"), Key(3)]
public StatModifierType Type;
// The simple name of a BehaviorData type, as InspectableType writes it
[InspectableType(typeof(BehaviorData)), JsonProperty("requireBehavior"), Key(4)]
public Type RequireBehavior;
public string RequireBehavior;
public override Behavior CreateInstance(EquippedItem item)
{
return new StatModifier(this, item);
@ -33,13 +34,12 @@ public class StatModifierData : BehaviorData
}
}
[Order(-4)]
public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwaysUpdatedBehavior
{
private StatModifierData _data;
private PerformanceStat[] _stats;
private static Type[] _statObjects;
private bool _applied;
@ -70,14 +70,14 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
if (typeof(EquippableItemData).IsAssignableFrom(targetType))
_stats = Entity.Equipment
.Select(hp => hp.EquippableItem)
.Where(gear => _data.RequireBehavior == null || ItemManager.GetData(gear).Behaviors.Any(behavior => behavior.GetType() == _data.RequireBehavior))
.Where(gear => string.IsNullOrEmpty(_data.RequireBehavior) || ItemManager.GetData(gear).Behaviors.Any(behavior => behavior.GetType().Name == _data.RequireBehavior))
.Where(gear => ItemManager.GetData(gear).GetType() == targetType)
.Select(gear => statField.GetValue(ItemManager.GetData(gear)) as PerformanceStat)
.ToArray();
else
_stats = Entity.Equipment
.Select(hp => hp.EquippableItem)
.Where(gear => _data.RequireBehavior == null || ItemManager.GetData(gear).Behaviors.Any(behavior => behavior.GetType() == _data.RequireBehavior))
.Where(gear => string.IsNullOrEmpty(_data.RequireBehavior) || ItemManager.GetData(gear).Behaviors.Any(behavior => behavior.GetType().Name == _data.RequireBehavior))
.SelectMany(gear => ItemManager.GetData(gear).Behaviors)
.Where(behaviorData => behaviorData.GetType() == targetType)
.Select(behaviorData => statField.GetValue(behaviorData) as PerformanceStat)
@ -135,7 +135,7 @@ public class StatReference
{
[InspectableType(typeof(BehaviorData)), JsonProperty("behavior"), Key(1)]
public string Target;
[Inspectable, JsonProperty("stat"), Key(2)]
public string Stat;
}
}

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-25)]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class SwitchData : BehaviorData
{
public override Behavior CreateInstance(EquippedItem item)

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-22)]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ThermotoggleData : BehaviorData
{
[InspectableTemperature, JsonProperty("targetTemp"), Key(1)]

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
@ -22,7 +23,7 @@ public class ThrusterData : BehaviorData
[Inspectable, JsonProperty("energy"), Key(4), RuntimeInspectable]
public PerformanceStat EnergyUsage = new PerformanceStat();
[InspectablePrefab, JsonProperty("Particles"), Key(5)]
[Inspectable, CultInspectorAssetPath, JsonProperty("Particles"), Key(5)]
public string ParticlesPrefab;
public override Behavior CreateInstance(EquippedItem item)

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack;
using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-20)]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class TriggerData : BehaviorData
{
public override Behavior CreateInstance(EquippedItem item)

View file

@ -23,7 +23,6 @@ public class VelocityLimitData : BehaviorData
}
}
[Order(100)]
public class VelocityLimit : Behavior
{
public float Limit { get; private set; }

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
@ -24,10 +25,10 @@ public abstract class WeaponData : BehaviorData
[Inspectable, JsonProperty("damage"), Key(2), RuntimeInspectable]
public PerformanceStat Damage = new PerformanceStat();
[InspectableRangedFloat(0,1), JsonProperty("penetration"), Key(3), RuntimeInspectable]
[Inspectable, CultInspectorRange(0, 1), JsonProperty("penetration"), Key(3), RuntimeInspectable]
public PerformanceStat Penetration = new PerformanceStat();
[InspectableRangedFloat(0,1), JsonProperty("damageSpread"), Key(4)]
[Inspectable, CultInspectorRange(0, 1), JsonProperty("damageSpread"), Key(4)]
public PerformanceStat DamageSpread = new PerformanceStat();
[Inspectable, JsonProperty("minRange"), Key(5)]
@ -39,28 +40,28 @@ public abstract class WeaponData : BehaviorData
[InspectableAnimationCurve, JsonProperty("damageCurve"), Key(7)]
public BezierCurve DamageCurve;
[InspectablePrefab, JsonProperty("effect"), Key(8)]
[Inspectable, CultInspectorAssetPath, JsonProperty("effect"), Key(8)]
public string EffectPrefab;
[InspectablePrefab, JsonProperty("energy"), Key(9), RuntimeInspectable]
[Inspectable, JsonProperty("energy"), Key(9), RuntimeInspectable]
public PerformanceStat Energy = new PerformanceStat();
[InspectablePrefab, JsonProperty("heat"), Key(10), RuntimeInspectable]
[Inspectable, JsonProperty("heat"), Key(10), RuntimeInspectable]
public PerformanceStat Heat = new PerformanceStat();
[InspectablePrefab, JsonProperty("visibility"), Key(11), RuntimeInspectable]
[Inspectable, JsonProperty("visibility"), Key(11), RuntimeInspectable]
public PerformanceStat Visibility = new PerformanceStat();
[InspectableDatabaseLink(typeof(SimpleCommodityData)), JsonProperty("ammo"), Key(12), RuntimeInspectable]
public Guid AmmoType;
[Inspectable, JsonProperty("ammo"), Key(12), RuntimeInspectable]
public CultRecordRef<SimpleCommodityData> AmmoType;
[InspectablePrefab, JsonProperty("magSize"), Key(13)]
[Inspectable, JsonProperty("magSize"), Key(13)]
public int MagazineSize;
[InspectablePrefab, JsonProperty("reloadTime"), Key(14)]
[Inspectable, JsonProperty("reloadTime"), Key(14)]
public float ReloadTime = 1;
[InspectablePrefab, JsonProperty("spread"), Key(15)]
[Inspectable, JsonProperty("spread"), Key(15)]
public PerformanceStat Spread = new PerformanceStat();
[Inspectable, JsonProperty("velocity"), Key(16)]

View file

@ -8,7 +8,7 @@ using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(1000), RuntimeInspectable]
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class WearData : BehaviorData
{
[InspectableTemperature, JsonProperty("perSecond"), Key(1)]

View file

@ -1,79 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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 GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
[DatabaseCategory("Galaxy"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class Faction : DatabaseEntry, INamedEntry
[CultDocument("aetheria.faction", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class Faction
{
[Inspectable, JsonProperty("name"), Key(1)]
[Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name;
[Inspectable, JsonProperty("shortName"), Key(2)]
public string ShortName;
[InspectableText, JsonProperty("description"), Key(3)]
[Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(3)]
public string Description;
[InspectableTexture, JsonProperty("logo"), Key(4)]
[Inspectable, CultInspectorAssetPath, JsonProperty("logo"), Key(4)]
public string Logo;
[InspectableDatabaseLink(typeof(PersonalityAttribute)), JsonProperty("personality"), Key(5)]
public Dictionary<Guid, float> Personality = new Dictionary<Guid, float>();
[Inspectable, CultReference(typeof(PersonalityAttribute), many: true), JsonProperty("personality"), Key(5)]
public Dictionary<CultRecordRef<PersonalityAttribute>, float> Personality = new Dictionary<CultRecordRef<PersonalityAttribute>, float>();
// [Inspectable, JsonProperty("hostile"), Key(6)]
// public bool PlayerHostile;
[InspectableColor, JsonProperty("primaryColor"), Key(7)]
public float3 PrimaryColor;
[InspectableColor, JsonProperty("secondaryColor"), Key(8)]
public float3 SecondaryColor;
[InspectableDatabaseLink(typeof(NameFile)), JsonProperty("nameFile"), Key(9)]
public Guid GeonameFile;
[Inspectable, JsonProperty("nameFile"), Key(9)]
public CultRecordRef<NameFile> GeonameFile;
[InspectableDatabaseLink(typeof(HullData)), JsonProperty("bossHull"), Key(10)]
public Guid BossHull;
[Inspectable, JsonProperty("bossHull"), Key(10)]
public CultRecordRef<HullData> BossHull;
[Inspectable, JsonProperty("influence"), Key(11)]
public int InfluenceDistance = 4;
[InspectableDatabaseLink(typeof(Faction)), RangedFloat(0, 1), JsonProperty("allegiance"), Key(12)]
public Dictionary<Guid, float> Allegiance = new Dictionary<Guid, float>();
[InspectableSoundBank, JsonProperty("overworldMusic"), Key(13)]
[Inspectable, CultReference(typeof(Faction), many: true), CultInspectorRange(0, 1), JsonProperty("allegiance"), Key(12)]
public Dictionary<CultRecordRef<Faction>, float> Allegiance = new Dictionary<CultRecordRef<Faction>, float>();
[Inspectable, JsonProperty("overworldMusic"), Key(13)]
public uint OverworldMusic;
[InspectableSoundBank, JsonProperty("combatMusic"), Key(14)]
[Inspectable, JsonProperty("combatMusic"), Key(14)]
public uint CombatMusic;
[InspectableSoundBank, JsonProperty("bossMusic"), Key(15)]
[Inspectable, JsonProperty("bossMusic"), Key(15)]
public uint BossMusic;
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
}
[Inspectable, MessagePackObject]
public class NameFile : DatabaseEntry, INamedEntry
[CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject]
public class NameFile
{
[Key(1)] public string Name;
[CultName, Key(1)] public string Name;
[Key(2)] public string[] Names;
[IgnoreMember]
public string EntryName
{
get => Name;
set => Name = value;
}
}

View file

@ -2,6 +2,7 @@
* 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 GameCult.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
@ -288,7 +289,7 @@ public abstract class Entity
// TODO: Inter-faction hostility
// When the entity faction owns the zone, they are hostile to trespassers or those hostile to them
if (Faction.ID == Zone.GalaxyZone.Owner?.ID)
if (Faction == Zone.GalaxyZone.Owner)
return recursive ? !(other.PresencePermitted?.Value ?? true) : !(other.PresencePermitted?.Value ?? true)|| other.IsHostileTo(this, true);
return !recursive && other.IsHostileTo(this, true);
@ -301,7 +302,7 @@ public abstract class Entity
return FactionRelationship.Neutral;
if (this is Ship {IsPlayerShip: true})
return Zone.Galaxy.FactionRelationships[faction];
return faction.ID == Faction.ID ? FactionRelationship.Beloved : FactionRelationship.Neutral;
return faction == Faction ? FactionRelationship.Beloved : FactionRelationship.Neutral;
}
public static bool IsPresencePermitted(FactionRelationship relationship, SecurityLevel securityLevel) =>
@ -316,7 +317,7 @@ public abstract class Entity
public ConsumableItemEffect FindActiveConsumable(ConsumableItemData data)
{
return _activeConsumables.FirstOrDefault(ac => ac.Data.ID == data.ID);
return _activeConsumables.FirstOrDefault(ac => ac.Data == data);
}
public bool CanActivateConsumable(ConsumableItemData data)
@ -328,10 +329,11 @@ public abstract class Entity
{
if (!CanActivateConsumable(data)) return false;
var bay = FindItemInCargo(data.ID);
var key = ItemManager.ItemData.RefOf<ItemData>(data).Key;
var bay = FindItemInCargo(key);
if (bay == null) return false;
var item = (ConsumableItem) bay.ItemsOfType[data.ID].First();
var item = (ConsumableItem) bay.ItemsOfType[key].First();
ActivateConsumable(item);
bay.Remove(item);
return true;
@ -377,7 +379,7 @@ public abstract class Entity
public void GenerateWeaponGroups()
{
foreach (var group in Weapons
.GroupBy(w => w.Item.EquippableItem.Data.LinkID)
.GroupBy(w => w.Item.EquippableItem.Data.Key)
.OrderBy(wg=>wg.Average(w=>w.Range))
.Select((weapons, index) => (weapons, index)))
{
@ -394,7 +396,7 @@ public abstract class Entity
Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y];
}
public int CountItemsInCargo(Guid itemDataID)
public int CountItemsInCargo(CultRecordKey itemDataID)
{
int sum = 0;
foreach (var x in CargoBays)
@ -408,7 +410,7 @@ public abstract class Entity
return sum;
}
public EquippedCargoBay FindItemInCargo(Guid itemDataID)
public EquippedCargoBay FindItemInCargo(CultRecordKey itemDataID)
{
return CargoBays.FirstOrDefault(c => c.ItemsOfType.ContainsKey(itemDataID));
}
@ -431,7 +433,7 @@ public abstract class Entity
// Attempts to move a given number of items of the given type to the target Entity
// Returns the number of items successfully transferred
public int TryTransferItems(Entity target, Guid itemDataID, int quantity)
public int TryTransferItems(Entity target, CultRecordKey itemDataID, int quantity)
{
int quantityTransferred = 0;
while (quantityTransferred < quantity)
@ -1052,7 +1054,7 @@ public class ConsumableItemEffect
{
Item = item;
Entity = entity;
Data = (ConsumableItemData) item.Data.Value;
Data = (ConsumableItemData) entity.ItemManager.GetData(item);
RemainingDuration = Data.Duration;
Behaviors = Data.Behaviors
@ -1329,7 +1331,7 @@ public class EquippedCargoBay : EquippedItem
public readonly ItemInstance[,] Occupancy;
public readonly Dictionary<Guid, List<ItemInstance>> ItemsOfType = new Dictionary<Guid, List<ItemInstance>>();
public readonly Dictionary<CultRecordKey, List<ItemInstance>> ItemsOfType = new Dictionary<CultRecordKey, List<ItemInstance>>();
public new readonly CargoBayData Data;
@ -1362,7 +1364,7 @@ public class EquippedCargoBay : EquippedItem
// Check whether the given item will fit when its origin is placed at the given coordinate
public bool ItemFits(ItemInstance item, int2 cargoCoord)
{
var itemData = item.Data.Value;
var itemData = ItemManager.GetData(item);
// Check every cell of the item's shape
foreach (var i in itemData.Shape.Coordinates)
{
@ -1395,7 +1397,7 @@ public class EquippedCargoBay : EquippedItem
// For simple commodities, search for existing item stacks to add to
foreach (var cargoItem in Cargo.Keys)
{
if (item.Data != cargoItem.Data) continue;
if (!item.Data.Key.Equals(cargoItem.Data.Key)) continue;
var cargoCommodity = (SimpleCommodity) cargoItem;
if (cargoCommodity.Quantity >= itemData.MaxStack) continue;
@ -1486,11 +1488,11 @@ public class EquippedCargoBay : EquippedItem
}
Cargo[item] = cargoCoord;
if(!ItemsOfType.ContainsKey(item.Data.LinkID))
ItemsOfType[item.Data.LinkID] = new List<ItemInstance>();
ItemsOfType[item.Data.LinkID].Add(item);
if(!ItemsOfType.ContainsKey(item.Data.Key))
ItemsOfType[item.Data.Key] = new List<ItemInstance>();
ItemsOfType[item.Data.Key].Add(item);
}
else if (Occupancy[cargoCoord.x, cargoCoord.y] is SimpleCommodity cargoCommodity && cargoCommodity.Data == item.Data)
else if (Occupancy[cargoCoord.x, cargoCoord.y] is SimpleCommodity cargoCommodity && cargoCommodity.Data.Key.Equals(item.Data.Key))
{
if (cargoCommodity.Quantity + item.Quantity <= itemData.MaxStack)
{
@ -1530,9 +1532,9 @@ public class EquippedCargoBay : EquippedItem
}
Cargo[item] = cargoCoord;
if(!ItemsOfType.ContainsKey(item.Data.LinkID))
ItemsOfType[item.Data.LinkID] = new List<ItemInstance>();
ItemsOfType[item.Data.LinkID].Add(item);
if(!ItemsOfType.ContainsKey(item.Data.Key))
ItemsOfType[item.Data.Key] = new List<ItemInstance>();
ItemsOfType[item.Data.Key].Add(item);
Mass += ItemManager.GetMass(item);
ThermalMass += ItemManager.GetThermalMass(item);
@ -1555,9 +1557,9 @@ public class EquippedCargoBay : EquippedItem
Occupancy[v.x, v.y] = null;
Cargo.Remove(item);
ItemsOfType[item.Data.LinkID].Remove(item);
if (!ItemsOfType[item.Data.LinkID].Any())
ItemsOfType.Remove(item.Data.LinkID);
ItemsOfType[item.Data.Key].Remove(item);
if (!ItemsOfType[item.Data.Key].Any())
ItemsOfType.Remove(item.Data.Key);
Mass -= ItemManager.GetMass(item);
ThermalMass -= ItemManager.GetThermalMass(item);
@ -1584,9 +1586,9 @@ public class EquippedCargoBay : EquippedItem
Occupancy[v.x, v.y] = null;
Cargo.Remove(item);
ItemsOfType[item.Data.LinkID].Remove(item);
if (!ItemsOfType[item.Data.LinkID].Any())
ItemsOfType.Remove(item.Data.LinkID);
ItemsOfType[item.Data.Key].Remove(item);
if (!ItemsOfType[item.Data.Key].Any())
ItemsOfType.Remove(item.Data.Key);
Mass -= ItemManager.GetMass(item);
ThermalMass -= ItemManager.GetThermalMass(item);

View file

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using MessagePack;
using CultMath;
@ -13,7 +14,7 @@ public static class EntitySerializer
if (entity is OrbitalEntity orbital)
pack = new OrbitalEntityPack
{
Orbit = orbital.OrbitData,
Orbit = new CultRecordRef<OrbitData>(orbital.OrbitData),
SecurityLevel = orbital.SecurityLevel,
SecurityRadius = orbital.SecurityRadius
};
@ -27,7 +28,7 @@ public static class EntitySerializer
else throw new ArgumentException("Attempted to pack an instance of abstract class Entity!");
pack.Settings = entity.Settings;
// Filter item behavior collections by those with any persistent behaviors
// For each item create an object containing the item position and a list of persistent behaviors
// Then turn that into a dictionary mapping from item position to an array of every behaviors persistent data
@ -40,7 +41,7 @@ public static class EntitySerializer
pack.Hull = entity.Hull;
pack.Name = entity.Name;
pack.Faction = entity.Faction?.ID ?? Guid.Empty;
pack.Faction = entity.Faction == null ? default : entity.ItemManager.ItemData.RefOf(entity.Faction);
pack.Equipment = entity.Equipment.Select(e => (e.Position, e.EquippableItem)).ToArray();
pack.CargoBays = entity.CargoBays.Select(e => (e.Position, e.EquippableItem)).ToArray();
pack.DockingBays = entity.DockingBays.Select(e => (e.Position, e.EquippableItem)).ToArray();
@ -70,7 +71,7 @@ public static class EntitySerializer
private static Ship Unpack(ItemManager itemManager, Zone zone, ShipPack pack, bool instantiate = false)
{
var entity = new Ship(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(pack.Hull) : pack.Hull, pack.Settings);
Restore(itemManager, zone, pack, entity, instantiate);
entity.Position = pack.Position;
@ -81,7 +82,7 @@ public static class EntitySerializer
private static OrbitalEntity Unpack(ItemManager itemManager, Zone zone, OrbitalEntityPack pack, bool instantiate = false)
{
var entity = new OrbitalEntity(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(pack.Hull) : pack.Hull, pack.Orbit, pack.Settings);
var entity = new OrbitalEntity(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(pack.Hull) : pack.Hull, pack.Orbit.Key, pack.Settings);
Restore(itemManager, zone, pack, entity, instantiate);
entity.SecurityLevel = pack.SecurityLevel;
entity.SecurityRadius = pack.SecurityRadius;
@ -92,7 +93,7 @@ public static class EntitySerializer
private static void Restore(ItemManager itemManager, Zone zone, EntityPack pack, Entity entity, bool instantiate = false)
{
entity.Name = pack.Name;
entity.Faction = itemManager.ItemData.Get<Faction>(pack.Faction);
entity.Faction = itemManager.ItemData.Get(pack.Faction);
entity.Children = pack.Children.Select(c =>
{
var child = Unpack(itemManager, zone, c, instantiate);
@ -131,12 +132,12 @@ public static class EntitySerializer
if(!instantiate)
entity.Temperature = pack.Temperature;
if(!instantiate)
entity.Armor = pack.Armor;
var hullData = itemManager.GetData(entity.Hull);
foreach(var v in hullData.Shape.Coordinates)
entity.HullConductivity[v.x,v.y] = pack.Conductivity[v.x,v.y];
@ -166,13 +167,13 @@ public class ShipPack : EntityPack
[MessagePackObject]
public class OrbitalEntityPack : EntityPack
{
[Key(17)] public Guid Orbit;
[Key(17)] public CultRecordRef<OrbitData> Orbit;
[Key(18)] public int Story = -1;
[Key(19)] public SecurityLevel SecurityLevel;
[Key(20)] public float SecurityRadius;
}
[MessagePackObject,
[MessagePackObject,
Union(0, typeof(OrbitalEntityPack)),
Union(1, typeof(ShipPack))]
public abstract class EntityPack
@ -191,7 +192,7 @@ public abstract class EntityPack
[Key(12)] public (int2 position, ItemInstance item)[][] DockingBayContents;
[Key(13)] public EntityPack[] Children;
[Key(14)] public EntitySettings Settings;
[Key(15)] public Guid Faction;
[Key(15)] public CultRecordRef<Faction> Faction;
[Key(16)] public int[][] WeaponGroups;
private int _price;
@ -199,7 +200,7 @@ public abstract class EntityPack
public int Price(ItemManager itemManager)
{
if (_price != 0) return _price;
var hullData = itemManager.GetData(Hull);
_price = hullData.Price;
@ -223,7 +224,7 @@ public abstract class EntityPack
{
foreach (var (_, item) in t)
{
var itemData = item.Data.Value;
var itemData = itemManager.GetData(item);
if (item is SimpleCommodity s)
_price += itemData.Price * s.Quantity;
else
@ -235,7 +236,7 @@ public abstract class EntityPack
{
foreach (var (_, item) in t)
{
var itemData = item.Data.Value;
var itemData = itemManager.GetData(item);
if (item is SimpleCommodity s)
_price += itemData.Price * s.Quantity;
else

View file

@ -15,9 +15,6 @@ using float2 = CultMath.float2;
public static class Extensions
{
//public static IDatabaseEntry Get(this Guid entry) => Database.Get(entry);
//public static T Get<T>(this Guid entry) where T : class, IDatabaseEntry => Database.Get(entry) as T;
public static bool IsImplementationOf(this Type baseType, Type interfaceType)
{
return baseType.GetInterfaces().Any(interfaceType.Equals);

View file

@ -4,36 +4,30 @@
using System;
using System.Collections.Generic;
using GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
// A manufacturer's branded variant of a generic design: its name, its flavor text, and how it builds each of
// the design's roles. Which products exist is what a faction makes, so market segmentation and regional
// progression are authored here rather than as duplicate item entries.
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FactionProductData : DatabaseEntry, INamedEntry
[CultDocument("aetheria.factionproductdata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FactionProductData
{
[Inspectable, JsonProperty("name"), Key(1)]
[Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name;
[InspectableText, JsonProperty("description"), Key(2)]
[Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(2)]
public string Description;
[InspectableDatabaseLink(typeof(CraftedItemData)), JsonProperty("design"), Key(3)]
public Guid Design;
[Inspectable, JsonProperty("design"), Key(3)]
public CultRecordRef<CraftedItemData> Design;
[InspectableDatabaseLink(typeof(Faction)), JsonProperty("manufacturer"), Key(4)]
public Guid Manufacturer;
[Inspectable, JsonProperty("manufacturer"), Key(4)]
public CultRecordRef<Faction> Manufacturer;
[Inspectable, JsonProperty("roles"), Key(5)]
public List<ProductRole> Roles = new List<ProductRole>();
[IgnoreMember]
public string EntryName
{
get => Name;
set => Name = value;
}
}
// How good this product's part is in one of its design's roles: a pseudo-gaussian distribution whose mean is

View file

@ -1,3 +1,4 @@
using GameCult.Caching;
using System;
using System.Collections.Generic;
using System.IO;
@ -29,7 +30,7 @@ public class Galaxy
private Action<string> Log { get; }
public bool IsPrelude { get; }
private HashSet<Guid> _containedFactions;
private HashSet<CultRecordKey> _containedFactions;
private GalaxyZone[] _exitPath;
private Dictionary<Faction, MarkovNameGenerator> _nameGenerators = new Dictionary<Faction, MarkovNameGenerator>();
private readonly CultCache _cache;
@ -51,13 +52,14 @@ public class Galaxy
Log = log;
Background = savedGame.Background;
Factions = savedGame.Factions.Select(cultCache.Get<Faction>).ToArray();
Factions = savedGame.Factions.Select(faction => cultCache.Get(faction)).ToArray();
for (var i = 0; i < Factions.Length; i++)
{
FactionRelationships[Factions[i]] = savedGame.Relationships[i];
}
Zones = savedGame.Zones.Select(zone =>
var savedZones = savedGame.Zones.Select(zone => cultCache.Get(zone)).ToArray();
Zones = savedZones.Select(zone =>
{
return new GalaxyZone
{
@ -69,9 +71,9 @@ public class Galaxy
foreach (var i in savedGame.DiscoveredZones) DiscoveredZones.Add(Zones[i]);
for (var i = 0; i < Zones.Length; i++)
{
Zones[i].AdjacentZones = savedGame.Zones[i].AdjacentZones.Select(azi => Zones[azi]).ToList();
Zones[i].Factions = savedGame.Zones[i].Factions.Select(mi => Factions[mi]).ToArray();
Zones[i].Owner = savedGame.Zones[i].Owner < 0 ? null : Factions[savedGame.Zones[i].Owner];
Zones[i].AdjacentZones = savedZones[i].AdjacentZones.Select(azi => Zones[azi]).ToList();
Zones[i].Factions = savedZones[i].Factions.Select(mi => Factions[mi]).ToArray();
Zones[i].Owner = savedZones[i].Owner < 0 ? null : Factions[savedZones[i].Owner];
}
HomeZones = savedGame.HomeZones.ToDictionary(
@ -269,7 +271,7 @@ public class Galaxy
// Choose some megas to have bosses placed based on whether a boss hull is assigned
var bossMegas = Factions
.Where(m => m.BossHull != Guid.Empty)
.Where(m => m.BossHull.IsSet())
.Take(bossCount)
.ToArray();
@ -335,7 +337,7 @@ public class Galaxy
progressCallback?.Invoke($"Feeding Markov Chains: {i + 1} / {Factions.Length}");
//if(progressCallback!=null) Thread.Sleep(250); // Inserting Delay to make it seem like it's doing more work lmao
var faction = Factions[i];
_nameGenerators[faction] = new MarkovNameGenerator(ref random, cache.Get<NameFile>(faction.GeonameFile).Names, nameGeneratorSettings);
_nameGenerators[faction] = new MarkovNameGenerator(ref random, cache.Get(faction.GeonameFile).Names, nameGeneratorSettings);
}
// Generate zone name using the owner's name generator, otherwise assign catalogue ID
@ -435,10 +437,10 @@ public class Galaxy
}
}
public bool ContainsFaction(Guid factionID)
public bool ContainsFaction(CultRecordRef<Faction> faction)
{
_containedFactions ??= new HashSet<Guid>(Factions.Select(f => f.ID));
return _containedFactions.Contains(factionID);
_containedFactions ??= new HashSet<CultRecordKey>(Factions.Select(f => _cache.RefOf(f).Key));
return _containedFactions.Contains(faction.Key);
}
class DijkstraVertex

View file

@ -1,8 +1,9 @@
using System.Collections.Generic;
using JsonKnownTypes;using MessagePack;
using GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.inputlayout", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class InputLayout
{
[Key(0), JsonProperty("rows")] public InputLayoutRow[] Rows;
@ -12,7 +13,7 @@ public class InputLayout
foreach (var row in Rows)
{
if (!(row is InputLayoutKeyRow keyRow)) continue;
foreach (var column in keyRow.Columns)
{
if (column is InputLayoutBindableKey bindableKey)
@ -24,8 +25,7 @@ public class InputLayout
}
}
[JsonConverter(typeof(JsonKnownTypesConverter<InputLayoutRow>)),
MessagePackObject,
[MessagePackObject,
Union(0, typeof(InputLayoutRowSpacer)),
Union(1, typeof(InputLayoutKeyRow))
]
@ -43,8 +43,7 @@ public class InputLayoutKeyRow : InputLayoutRow
[Key(0), JsonProperty("columns")] public InputLayoutColumn[] Columns;
}
[JsonConverter(typeof(JsonKnownTypesConverter<InputLayoutColumn>)),
MessagePackObject,
[MessagePackObject,
Union(0, typeof(InputLayoutColumnSpacer)),
Union(1, typeof(InputLayoutKey)),
Union(2, typeof(InputLayoutBindableKey)),
@ -82,6 +81,7 @@ public class InputLayoutBindableKey : InputLayoutKey, IBindableButton
}
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class InputLayoutMultiRowKey : InputLayoutBindableKey
{
[Key(4), JsonProperty("height")] public int Height;
@ -100,4 +100,4 @@ public class InputLayoutMouseButton : IBindableButton
public interface IBindableButton
{
public string InputSystemPath { get; set; }
}
}

View file

@ -6,7 +6,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JsonKnownTypes;
using GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
@ -268,17 +268,17 @@ public class Shape
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<ItemData>))]
public abstract class ItemData : DatabaseEntry, INamedEntry
[JsonObject(MemberSerialization.OptIn)]
public abstract class ItemData
{
[Inspectable, JsonProperty("name"), Key(1)]
[Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name;
[InspectableText, JsonProperty("description"), Key(2)]
[Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(2)]
public string Description;
[InspectableDatabaseLink(typeof(Faction)), JsonProperty("creator"), Key(3)]
public Guid Manufacturer;
[Inspectable, JsonProperty("creator"), Key(3)]
public CultRecordRef<Faction> Manufacturer;
[Inspectable, JsonProperty("mass"), Key(4)]
public float Mass;
@ -295,16 +295,9 @@ public abstract class ItemData : DatabaseEntry, INamedEntry
[Inspectable, JsonProperty("price"), Key(8)]
public int Price = 0;
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject]
[CultDocument("aetheria.simplecommoditydata", "1"), Inspectable, MessagePackObject]
public class SimpleCommodityData : ItemData
{
// MessagePack keys 6, 7, 8 and 11 belonged to removed resource-distribution fields; do not reuse them.
@ -316,7 +309,7 @@ public class SimpleCommodityData : ItemData
public SimpleCommodityCategory Category;
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<CraftedItemData>))]
[JsonObject(MemberSerialization.OptIn)]
public abstract class CraftedItemData : ItemData
{
// The named slots this design is assembled from. A stat may read the quality of the part filling one.
@ -334,17 +327,17 @@ public class ItemRole
public string Name;
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject]
[CultDocument("aetheria.compoundcommoditydata", "1"), Inspectable, MessagePackObject]
public class CompoundCommodityData : CraftedItemData
{
[InspectableDatabaseLink(typeof(PersonalityAttribute)), JsonProperty("demandProfile"), Key(10)]
public Dictionary<Guid, float> DemandProfile = new Dictionary<Guid, float>();
[Inspectable, CultReference(typeof(PersonalityAttribute), many: true), JsonProperty("demandProfile"), Key(10)]
public Dictionary<CultRecordRef<PersonalityAttribute>, float> DemandProfile = new Dictionary<CultRecordRef<PersonalityAttribute>, float>();
[Inspectable, JsonProperty("category"), Key(11)]
public CompoundCommodityCategory Category;
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject]
[CultDocument("aetheria.consumableitemdata", "1"), Inspectable, MessagePackObject]
public class ConsumableItemData : CraftedItemData
{
[Inspectable, JsonProperty("behaviors"), Key(10)]
@ -356,7 +349,7 @@ public class ConsumableItemData : CraftedItemData
[Inspectable, JsonProperty("duration"), Key(12)]
public float Duration;
[InspectableTexture, JsonProperty("icon"), Key(13)]
[Inspectable, CultInspectorAssetPath, JsonProperty("icon"), Key(13)]
public string Icon;
[Inspectable, JsonProperty("effectiveness"), Key(14)]
@ -366,7 +359,7 @@ public class ConsumableItemData : CraftedItemData
[JsonObject(MemberSerialization.OptIn)]
public abstract class EquippableItemData : CraftedItemData
{
[InspectableTexture, JsonProperty("schematic"), Key(10)]
[Inspectable, CultInspectorAssetPath, JsonProperty("schematic"), Key(10)]
public string Schematic;
[Inspectable, JsonProperty("behaviors"), Key(11)]
@ -396,10 +389,10 @@ public abstract class EquippableItemData : CraftedItemData
// [Inspectable, JsonProperty("sfx"), Key(19)]
// public string SoundEffectTrigger;
[InspectableTexture, JsonProperty("actionIcon"), Key(20)]
[Inspectable, CultInspectorAssetPath, JsonProperty("actionIcon"), Key(20)]
public string ActionBarIcon;
[InspectableSoundBank, JsonProperty("soundBank"), Key(21)]
[Inspectable, JsonProperty("soundBank"), Key(21)]
public uint SoundBank;
[Inspectable, JsonProperty("audioStats"), Key(22)]
@ -446,14 +439,14 @@ public abstract class EquippableItemData : CraftedItemData
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AudioStat
{
[InspectableAudioParameter, JsonProperty("parameter"), Key(0)]
[Inspectable, JsonProperty("parameter"), Key(0)]
public uint Parameter;
[Inspectable, JsonProperty("stat"), Key(1)]
public PerformanceStat Stat = new PerformanceStat();
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.geardata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class GearData : EquippableItemData
{
[Inspectable, JsonProperty("hardpointType"), Key(23)]
@ -462,7 +455,7 @@ public class GearData : EquippableItemData
[IgnoreMember] public override HardpointType HardpointType => Hardpoint;
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.cargobaydata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class CargoBayData : EquippableItemData
{
[Inspectable, JsonProperty("interiorShape"), Key(24)]
@ -471,14 +464,14 @@ public class CargoBayData : EquippableItemData
[IgnoreMember] public override HardpointType HardpointType => HardpointType.Tool;
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.dockingbaydata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class DockingBayData : CargoBayData
{
[Inspectable, JsonProperty("maxSize"), Key(25)]
public int2 MaxSize;
}
[DatabaseCategory("Items"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.weaponitemdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class WeaponItemData : GearData
{
[Inspectable, JsonProperty("range"), Key(24)]
@ -497,13 +490,13 @@ public class WeaponItemData : GearData
public WeaponModifiers WeaponModifiers;
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.hulldata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class HullData : EquippableItemData
{
[Inspectable, JsonProperty("hardpoints"), Key(23)]
public List<HardpointData> Hardpoints = new List<HardpointData>();
[InspectablePrefab, JsonProperty("prefab"), Key(24)]
[Inspectable, CultInspectorAssetPath, JsonProperty("prefab"), Key(24)]
public string Prefab;
[Inspectable, JsonProperty("hullType"), Key(25)]
@ -541,7 +534,7 @@ public class HullData : EquippableItemData
}
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class HardpointData : ITintInspector
public class HardpointData
{
[Inspectable, JsonProperty("type"), Key(0)] public HardpointType Type;
[Inspectable, JsonProperty("position"), Key(1)] public int2 Position;
@ -628,10 +621,10 @@ public class PerformanceStat
}
}
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class PersonalityAttribute : DatabaseEntry, INamedEntry
[CultDocument("aetheria.personalityattribute", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class PersonalityAttribute
{
[Inspectable, JsonProperty("name"), Key(1)]
[Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name;
[Inspectable, JsonProperty("low"), Key(2)]
@ -640,9 +633,4 @@ public class PersonalityAttribute : DatabaseEntry, INamedEntry
[Inspectable, JsonProperty("high"), Key(3)]
public string HighName;
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
}

View file

@ -7,7 +7,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonKnownTypes;
using GameCult.Caching;
//using JM.LinqFaster;
using MessagePack;
using MessagePack.Formatters;
@ -19,34 +19,28 @@ using static CultMath.math;
Union(1, typeof(CompoundCommodity)),
Union(2, typeof(EquippableItem)),
Union(3, typeof(ConsumableItem)),
JsonObject(MemberSerialization.OptIn),
JsonConverter(typeof(JsonKnownTypesConverter<ItemInstance>))]
JsonObject(MemberSerialization.OptIn)]
public abstract class ItemInstance
{
[JsonProperty("data"), Key(0)] public DatabaseLink<ItemData> Data;
[JsonProperty("data"), Key(0)] public CultRecordRef<ItemData> Data;
[JsonProperty("rotation"), Key(1)] public ItemRotation Rotation;
}
[Union(0, typeof(CompoundCommodity)),
Union(1, typeof(EquippableItem)),
Union(2, typeof(ConsumableItem)),
JsonObject(MemberSerialization.OptIn),
JsonConverter(typeof(JsonKnownTypesConverter<CraftedItemInstance>))]
JsonObject(MemberSerialization.OptIn)]
public abstract class CraftedItemInstance : ItemInstance
{
// The workmanship of this particular unit, as distinct from the parts that went into it.
[JsonProperty("quality"), Key(2)] public float Quality;
// Keys 9 and 10, past every key the derived instances use: an item saved before these existed has no
// element there at all. Keys 3 to 6 sit inside the old range and read back as nil, which a non-nullable
// Guid cannot deserialize, so nothing new may go there.
// How good the part filling each of the design's roles turned out. Empty on items made before roles
// existed; stats then read Quality as they always did.
[JsonProperty("ingredients"), Key(9)] public List<RoleFill> Ingredients = new List<RoleFill>();
// The manufacturer's branded product this was built as, naming it and carrying its flavor text.
[JsonProperty("product"), Key(10)] public Guid Product;
[JsonProperty("product"), Key(10)] public CultRecordRef<FactionProductData> Product;
// The quality a stat reads for one of the design's roles. An unnamed role, a design without roles, and an
// item built before roles existed all fall back to this item's own workmanship.

View file

@ -1,4 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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/. */
@ -6,6 +6,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using CultMath;
using static CultMath.math;
using float2 = CultMath.float2;
@ -17,36 +18,12 @@ using float4 = CultMath.float4;
public class ItemManager
{
public Random Random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
// public Dictionary<string, GalaxyMapLayerData> MapLayers = new Dictionary<string, GalaxyMapLayerData>();
// public SimpleCommodityData[] Resources;
// public Dictionary<Guid, List<IController>> CorporationControllers = new Dictionary<Guid, List<IController>>();
// public Dictionary<Guid, ZoneDefinition> GalaxyZones;
private Action<string> _logger;
private double _time;
private float _deltaTime;
private Dictionary<Guid, Zone> _zones = new Dictionary<Guid, Zone>();
// private Guid _forceLoadZone;
// public GlobalData GlobalData => _globalData ?? (_globalData = ItemData.GetAll<GlobalData>().FirstOrDefault());
public CultCache ItemData { get; }
public GameplaySettings GameplaySettings { get; }
// public double Time
// {
// get => _time;
// set
// {
// _deltaTime = (float) (value - _time);
// _time = value;
// //Log($"GameContext delta time: {_deltaTime}");
// }
// }
// private readonly Dictionary<CraftedItemData, int> Tier = new Dictionary<CraftedItemData, int>();
public ItemManager(CultCache itemData, GameplaySettings settings, Action<string> logger)
{
ItemData = itemData;
@ -59,34 +36,28 @@ public class ItemManager
_logger(s);
}
public SimpleCommodityData GetData(SimpleCommodity item)
{
return item.Data.Value as SimpleCommodityData;
}
// The one resolution path from an item instance to its design
public ItemData GetData(ItemInstance item) => ItemData.Get(item.Data);
public CraftedItemData GetData(CraftedItemInstance item)
{
return item.Data.Value as CraftedItemData;
}
public SimpleCommodityData GetData(SimpleCommodity item) => GetData((ItemInstance) item) as SimpleCommodityData;
public EquippableItemData GetData(EquippableItem item)
{
return item.Data.Value as EquippableItemData;
}
public CraftedItemData GetData(CraftedItemInstance item) => GetData((ItemInstance) item) as CraftedItemData;
public EquippableItemData GetData(EquippableItem item) => GetData((ItemInstance) item) as EquippableItemData;
public float GetMass(ItemInstance item)
{
return item switch
{
CraftedItemInstance _ => item.Data.Value.Mass,
SimpleCommodity commodity => item.Data.Value.Mass * commodity.Quantity,
CraftedItemInstance _ => GetData(item).Mass,
SimpleCommodity commodity => GetData(item).Mass * commodity.Quantity,
_ => 0
};
}
public float GetThermalMass(ItemInstance item)
{
var data = item.Data.Value;
var data = GetData(item);
return item switch
{
CraftedItemInstance _ => data.Mass * data.SpecificHeat,
@ -106,7 +77,7 @@ public class ItemManager
pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
var durability = pow(item.Durability / data.Durability, durabilityExponent * stat.DurabilityExponentMultiplier);
var result = lerp(stat.Min, stat.Max, quality * durability);
if (float.IsNaN(result))
if (float.IsNaN(result))
throw new InvalidOperationException($"Performance Stat on {data.Name} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {data.Durability}");
return result;
@ -124,20 +95,19 @@ public class ItemManager
{
var newItem = new SimpleCommodity
{
Data = new DatabaseLink<ItemData>{LinkID = item.ID},
Data = ItemData.RefOf<ItemData>(item),
Quantity = count
};
//ItemData.Add(newItem);
return newItem;
}
_logger("Attempted to create Simple Commodity instance using missing or incorrect item id");
return null;
}
public ItemInstance Instantiate(ItemInstance item)
{
var data = item.Data.Value;
var data = GetData(item);
if(data is CraftedItemData c)
{
var i = CreateInstance(c);
@ -159,24 +129,23 @@ public class ItemManager
{
return new EquippableItem
{
Data = new DatabaseLink<ItemData> {LinkID = item.ID}, Quality = quality, Durability = equippableItemData.Durability
Data = ItemData.RefOf<ItemData>(item), Quality = quality, Durability = equippableItemData.Durability
};
}
var newCommodity = new CompoundCommodity
{
Data = new DatabaseLink<ItemData>{LinkID = item.ID},
Data = ItemData.RefOf<ItemData>(item),
Quality = quality
};
return newCommodity;
}
public CraftedItemInstance CreateInstance(CraftedItemData item)
{
if (item == null)
{
throw new NullReferenceException("Attempted to create crafted item instance using missing or incorrect item data!");
return null;
}
var quality = Random.NextFloat();
@ -194,7 +163,7 @@ public class ItemManager
// the design's roles filled with a part whose quality is drawn from that manufacturer's distribution for it.
public CraftedItemInstance CreateInstance(FactionProductData product)
{
var design = ItemData.Get<CraftedItemData>(product.Design);
var design = ItemData.Get(product.Design);
if (design == null)
{
_logger($"Product {product.Name} names a design that does not exist!");
@ -202,7 +171,7 @@ public class ItemManager
}
var instance = CreateInstance(design);
instance.Product = product.ID;
instance.Product = ItemData.RefOf(product);
if (design.Roles == null) return instance;
foreach (var role in design.Roles)
{
@ -226,4 +195,4 @@ public class ItemManager
int upgrades = (int) ((item.Quality - tier.Quality) / .0499f);
return (tier, upgrades);
}
}
}

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using GameCult.Caching;
using CultMath;
using static CultMath.math;
using Random = CultMath.Random;
@ -57,7 +58,7 @@ public class LoadoutGenerator
return null;
}
var hull = ItemManager.CreateInstance(hullProduct) as EquippableItem;
var entity = new OrbitalEntity(ItemManager, null, hull, Guid.Empty, ItemManager.GameplaySettings.DefaultEntitySettings);
var entity = new OrbitalEntity(ItemManager, null, hull, default, ItemManager.GameplaySettings.DefaultEntitySettings);
entity.Faction = Faction;
OutfitEntity(entity);
return EntitySerializer.Pack(entity) as OrbitalEntityPack;
@ -72,7 +73,7 @@ public class LoadoutGenerator
return null;
}
var hull = ItemManager.CreateInstance(hullProduct) as EquippableItem;
var entity = new OrbitalEntity(ItemManager, null, hull, Guid.Empty, ItemManager.GameplaySettings.DefaultEntitySettings);
var entity = new OrbitalEntity(ItemManager, null, hull, default, ItemManager.GameplaySettings.DefaultEntitySettings);
entity.Faction = Faction;
// Hardpoint gear goes in first so the docking bay can only take space that gear left free
@ -123,11 +124,11 @@ public class LoadoutGenerator
public (FactionProductData product, T design)[] RandomProducts<T>(int count, float sizeExponent, Predicate<T> filter = null, bool required = false) where T : EquippableItemData
{
var candidates = ItemManager.ItemData.GetAll<FactionProductData>()
.Select(product => (product, design: ItemManager.ItemData.Get<CraftedItemData>(product.Design) as T))
.Select(product => (product, design: ItemManager.ItemData.Get(product.Design) as T))
.Where(entry =>
entry.design != null &&
entry.design.Price > 0 &&
entry.product.Manufacturer != Guid.Empty &&
entry.product.Manufacturer.IsSet() &&
(filter?.Invoke(entry.design) ?? true))
.ToArray();
var available = candidates.Where(entry => IsAvailable(entry.product)).ToArray();
@ -153,12 +154,13 @@ public class LoadoutGenerator
Galaxy.ContainsFaction(product.Manufacturer) && (Faction == null || Faction.Allegiance.ContainsKey(product.Manufacturer));
// Prioritize products from the zone faction and its allies, penalizing distance to the manufacturer's headquarters
private float ManufacturerPreference(Guid manufacturer)
private float ManufacturerPreference(CultRecordRef<Faction> manufacturer)
{
if (Faction == null || Galaxy == null) return 1;
var allegiance = manufacturer == Faction.ID ? 1 :
var maker = ItemManager.ItemData.Get(manufacturer);
var allegiance = maker == Faction ? 1 :
Faction.Allegiance.TryGetValue(manufacturer, out var a) ? a : 0;
var home = Galaxy.HomeZones.FirstOrDefault(h => h.Key.ID == manufacturer).Value;
var home = maker != null && Galaxy.HomeZones.TryGetValue(maker, out var h) ? h : null;
var distance = home != null && Zone?.Distance != null && Zone.Distance.TryGetValue(home, out var d) ? d : 0;
return allegiance / (1 + distance);
}

View file

@ -15,12 +15,12 @@ public interface IFactionResolver
public abstract class ZoneConstraint
{
public bool Flip = false;
public bool Test(GalaxyZone zone)
{
return Flip ^ TestZone(zone);
}
protected abstract bool TestZone(GalaxyZone zone);
}
@ -34,14 +34,14 @@ public class FactionPresenceConstraint : ZoneConstraint
protected override bool TestZone(GalaxyZone zone)
{
return zone.Factions.Any(f=>f.ID == TargetFaction?.ID);
return zone.Factions.Any(f => f == TargetFaction);
}
}
public class FactionOwnerConstraint : ZoneConstraint
{
private Faction TargetFaction { get; }
public FactionOwnerConstraint(string[] args, IFactionResolver resolver)
{
TargetFaction = resolver.ResolveFaction(args[0]);
@ -49,7 +49,7 @@ public class FactionOwnerConstraint : ZoneConstraint
protected override bool TestZone(GalaxyZone zone)
{
return zone.Owner.ID == TargetFaction?.ID;
return TargetFaction != null && zone.Owner == TargetFaction;
}
}
@ -57,7 +57,7 @@ public class DistanceConstraint : ZoneConstraint
{
private GalaxyZone _targetZone;
private Predicate<int> _test;
public DistanceConstraint(string[] args, IZoneResolver zoneResolver)
{
if (args.Length == 3)
@ -86,4 +86,4 @@ public class DistanceConstraint : ZoneConstraint
{
return _targetZone != null && _test(zone.Distance[_targetZone]);
}
}
}

View file

@ -1,35 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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 GameCult.Caching;
using CultMath;
using static CultMath.math;
using float2 = CultMath.float2;
public class OrbitalEntity : Entity
{
public Guid OrbitData;
public CultRecordKey OrbitData;
public SecurityLevel SecurityLevel;
public float SecurityRadius;
public LocationStory Story;
public bool CanTow;
public OrbitalEntity(ItemManager itemManager, Zone zone, EquippableItem hull, Guid orbit, EntitySettings settings) : base(itemManager, zone, hull, settings)
public OrbitalEntity(ItemManager itemManager, Zone zone, EquippableItem hull, CultRecordKey orbit, EntitySettings settings) : base(itemManager, zone, hull, settings)
{
OrbitData = orbit;
}
public override void Update(float delta)
{
if (OrbitData != Guid.Empty)
if (OrbitData.IsSet())
{
Position.xz = Zone.GetOrbitPosition(OrbitData);
Velocity = Zone.GetOrbitVelocity(OrbitData);
}
base.Update(delta);
}

View file

@ -1,9 +1,10 @@
using GameCult.Caching;
using System;
using System.Collections.Generic;
using System.Globalization;
using MessagePack;
[MessagePackObject]
[CultDocument("aetheria.playersettings", "1"), CultGlobal, MessagePackObject]
public class PlayerSettings
{
[Key(0)] public string Name = "Anonymous";

View file

@ -1,34 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using MessagePack;
using CultMath;
using static CultMath.math;
[MessagePackObject]
[CultDocument("aetheria.savedgame", "1"), CultGlobal, MessagePackObject]
public class SavedGame
{
[Key(0)]
public SavedZone[] Zones;
public CultRecordRef<SavedZone>[] Zones;
[Key(1)]
public Guid[] Factions;
public CultRecordRef<Faction>[] Factions;
[Key(2)]
public Dictionary<int, int> HomeZones;
[Key(3)]
public Dictionary<int, int> BossZones;
[Key(4)]
public int Entrance;
[Key(5)]
public int Exit;
[Key(6)]
public int CurrentZone;
[Key(7)]
public int CurrentZoneEntity;
@ -46,59 +47,61 @@ public class SavedGame
[Key(12)]
public FactionRelationship[] Relationships;
public SavedGame() { }
public SavedGame(Galaxy galaxy, Zone currentZone, Entity currentEntity)
// Writes a SavedZone record for every galaxy zone through the cache.
public SavedGame(CultCache cache, Galaxy galaxy, Zone currentZone, Entity currentEntity)
{
DiscoveredZones = galaxy.DiscoveredZones.Select(dz => Array.IndexOf(galaxy.Zones, dz)).ToArray();
Background = galaxy.Background;
Factions = galaxy.HomeZones.Keys.Select(f => f.ID).ToArray();
var factions = galaxy.HomeZones.Keys.ToArray();
Factions = factions.Select(f => cache.RefOf(f)).ToArray();
Relationships = galaxy.Factions.Select(f => galaxy.FactionRelationships[f]).ToArray();
HomeZones = galaxy.HomeZones.ToDictionary(
x => Array.IndexOf(Factions, x.Key.ID),
x => Array.IndexOf(factions, x.Key),
x => Array.IndexOf(galaxy.Zones, x.Value));
BossZones = galaxy.BossZones.ToDictionary(
x => Array.IndexOf(Factions, x.Key.ID),
x => Array.IndexOf(factions, x.Key),
x => Array.IndexOf(galaxy.Zones, x.Value));
Zones = galaxy.Zones.Select(zone => new SavedZone
Zones = galaxy.Zones.Select(zone => cache.Upsert(new SavedZone
{
Name = zone.Name,
Position = zone.Position,
AdjacentZones = zone.AdjacentZones.Select(az=> Array.IndexOf(galaxy.Zones, az)).ToArray(),
Factions = zone.Factions.Select(f=> Array.IndexOf(Factions, f.ID)).ToArray(),
Factions = zone.Factions.Select(f=> Array.IndexOf(factions, f)).ToArray(),
Contents = zone.Contents?.PackZone(),
Owner = zone.Owner == null ? -1 : Array.IndexOf(Factions, zone.Owner.ID)
}).ToArray();
Owner = zone.Owner == null ? -1 : Array.IndexOf(factions, zone.Owner)
})).ToArray();
CurrentZone = Array.FindIndex(galaxy.Zones, zone => zone.Contents == currentZone);
CurrentZoneEntity = currentZone.Entities.IndexOf(currentEntity);
Entrance = Array.IndexOf(galaxy.Zones, galaxy.Entrance);
Exit = Array.IndexOf(galaxy.Zones, galaxy.Exit);
}
}
[MessagePackObject]
[CultDocument("aetheria.savedzone", "1"), MessagePackObject]
public class SavedZone
{
[Key(0)]
public string Name;
[Key(1)]
public float2 Position;
[Key(2)]
public int[] AdjacentZones;
[Key(3)]
public int[] Factions;
[Key(4)]
public int Owner;
[Key(5)]
public ZonePack Contents;
}
@ -115,7 +118,7 @@ public abstract class SavedActionBarBinding
[MessagePackObject]
public class SavedActionBarConsumableBinding : SavedActionBarBinding
{
[Key(0)] public DatabaseLink<ConsumableItemData> Target;
[Key(0)] public CultRecordRef<ConsumableItemData> Target;
}
[MessagePackObject]
@ -129,4 +132,4 @@ public class SavedActionBarGearBinding : SavedActionBarBinding
public class SavedActionBarWeaponGroupBinding : SavedActionBarBinding
{
[Key(0)] public int Group;
}
}

View file

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GameCult.Caching;
using UniRx;
using CultMath;
using static CultMath.math;
@ -17,17 +18,15 @@ using Random = CultMath.Random;
public class Zone
{
public Action<string> Log;
//public HashSet<Guid> Planets = new HashSet<Guid>();
public ReactiveCollection<Entity> Entities = new ReactiveCollection<Entity>();
//public Dictionary<Guid, OrbitData> Orbits = new Dictionary<Guid, OrbitData>();
public Dictionary<Guid, BodyData> Planets = new Dictionary<Guid, BodyData>();
public Dictionary<Guid, Planet> PlanetInstances = new Dictionary<Guid, Planet>();
public Dictionary<CultRecordKey, BodyData> Planets = new Dictionary<CultRecordKey, BodyData>();
public Dictionary<CultRecordKey, Planet> PlanetInstances = new Dictionary<CultRecordKey, Planet>();
public Dictionary<Guid, Orbit> Orbits = new Dictionary<Guid, Orbit>();
public Dictionary<Guid, AsteroidBelt> AsteroidBelts = new Dictionary<Guid, AsteroidBelt>();
public Dictionary<CultRecordKey, Orbit> Orbits = new Dictionary<CultRecordKey, Orbit>();
public Dictionary<CultRecordKey, AsteroidBelt> AsteroidBelts = new Dictionary<CultRecordKey, AsteroidBelt>();
public PlanetSettings Settings;
private HashSet<Guid> _updatedOrbits = new HashSet<Guid>();
private HashSet<CultRecordKey> _updatedOrbits = new HashSet<CultRecordKey>();
private ItemManager _itemManager;
private double _time;
@ -35,7 +34,7 @@ public class Zone
public List<Agent> Agents = new List<Agent>();
private List<Task> BeltUpdates = new List<Task>();
public float Time
{
get => (float) _time;
@ -53,28 +52,30 @@ public class Zone
_itemManager = itemManager;
Settings = settings;
_random = new Random(Convert.ToUInt32(abs(galaxyZone?.Name.GetHashCode() ?? 1337)));
var cache = itemManager.ItemData;
foreach (var orbit in pack.Orbits)
{
Orbits.Add(orbit.ID, new Orbit(Settings, orbit));
Orbits.Add(orbit.Key, new Orbit(Settings, cache.Get(orbit)));
}
foreach (var planet in pack.Planets)
foreach (var body in pack.Planets)
{
Planets.Add(planet.ID, planet);
var planet = cache.Get(body);
Planets.Add(body.Key, planet);
switch (planet)
{
case AsteroidBeltData belt:
AsteroidBelts[belt.ID] = new AsteroidBelt(belt);
AsteroidBelts[body.Key] = new AsteroidBelt(belt);
break;
case SunData sun:
PlanetInstances.Add(sun.ID, new Sun(settings, sun, Orbits[planet.Orbit]));
PlanetInstances.Add(body.Key, new Sun(settings, sun, Orbits[planet.Orbit.Key]));
break;
case GasGiantData gas:
PlanetInstances.Add(gas.ID, new GasGiant(settings, gas, Orbits[planet.Orbit]));
PlanetInstances.Add(body.Key, new GasGiant(settings, gas, Orbits[planet.Orbit.Key]));
break;
default:
PlanetInstances.Add(planet.ID, new Planet(settings, planet, Orbits[planet.Orbit]));
PlanetInstances.Add(body.Key, new Planet(settings, planet, Orbits[planet.Orbit.Key]));
break;
}
}
@ -111,15 +112,15 @@ public class Zone
Radius = Pack.Radius,
Mass = Pack.Mass,
Entities = Entities.Select(EntitySerializer.Pack).ToList(),
Orbits = Orbits.Values.Select(o=>o.Data).ToList(),
Planets = Planets.Values.ToList(),
Orbits = Orbits.Keys.Select(key => new CultRecordRef<OrbitData>(key)).ToList(),
Planets = Planets.Keys.Select(key => new CultRecordRef<BodyData>(key)).ToList(),
Time = _time
};
}
public void AddOrbit(OrbitData orbit)
{
Orbits.Add(orbit.ID, new Orbit(Settings, orbit));
Orbits.Add(_itemManager.ItemData.RefOf(orbit).Key, new Orbit(Settings, orbit));
}
public void Update(float deltaTime)
@ -142,25 +143,25 @@ public class Zone
belt.Value.OrbitPosition = belt.Value.NewOrbitPosition;
BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));
}
foreach(var agent in Agents)
agent.Update(deltaTime);
foreach (var entity in Entities.ToArray()) entity.Update(deltaTime);
}
// Determine orbital position recursively, caching parent positions to avoid repeated calculations
public float2 GetOrbitPosition(Guid orbitID)
public float2 GetOrbitPosition(CultRecordKey orbitID)
{
// Root orbit is fixed at origin
if(orbitID==Guid.Empty)
if(!orbitID.IsSet())
return float2.zero;
if (!Orbits.ContainsKey(orbitID))
{
Log?.Invoke("Requested orbit is not part of this zone!");
return float2.zero;
}
if (!_updatedOrbits.Contains(orbitID))
{
var orbit = Orbits[orbitID];
@ -169,7 +170,7 @@ public class Zone
{
var phase = (float) frac(_time / orbit.Period);
pos = OrbitData.Evaluate(frac(phase + orbit.Data.Phase)) * orbit.Data.Distance;
if (float.IsNaN(pos.x))
{
//_context.Log("Orbit position is NaN, something went very wrong!");
@ -177,9 +178,9 @@ public class Zone
}
}
var parentPosition = Orbits[orbitID].Data.Parent == Guid.Empty
? Orbits[orbitID].Data.FixedPosition :
GetOrbitPosition(orbit.Data.Parent);
var parentPosition = !Orbits[orbitID].Data.Parent.IsSet()
? Orbits[orbitID].Data.FixedPosition :
GetOrbitPosition(orbit.Data.Parent.Key);
Orbits[orbitID].Position = parentPosition + pos;
_updatedOrbits.Add(orbitID);
}
@ -187,14 +188,14 @@ public class Zone
return Orbits[orbitID].Position;
}
public float2 GetOrbitVelocity(Guid orbit)
public float2 GetOrbitVelocity(CultRecordKey orbit)
{
if (Orbits.ContainsKey(orbit))
return Orbits[orbit].Velocity;
return float2.zero;
}
public int NearestAsteroid(Guid planetDataID, float2 position)
public int NearestAsteroid(CultRecordKey planetDataID, float2 position)
{
var beltData = Planets[planetDataID] as AsteroidBeltData;
@ -215,16 +216,16 @@ public class Zone
return nearest;
}
public bool AsteroidExists(Guid planetDataID, int asteroid) => ((AsteroidBeltData) Planets[planetDataID]).Asteroids.Length > asteroid && asteroid >= 0;
public bool AsteroidExists(CultRecordKey planetDataID, int asteroid) => ((AsteroidBeltData) Planets[planetDataID]).Asteroids.Length > asteroid && asteroid >= 0;
private void UpdateAsteroidTransforms(Guid planetDataID)
private void UpdateAsteroidTransforms(CultRecordKey planetDataID)
{
var beltData = Planets[planetDataID] as AsteroidBeltData;
var belt = AsteroidBelts[planetDataID];
var orbitData = Orbits[beltData.Orbit].Data;
belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent);
var orbitData = Orbits[beltData.Orbit.Key].Data;
belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent.Key);
for (var i = 0; i < beltData.Asteroids.Length; i++)
{
float size;
@ -245,7 +246,7 @@ public class Zone
}
}
public OrbitData CreateOrbit(Guid parent, float2 position)
public OrbitData CreateOrbit(CultRecordKey parent, float2 position)
{
var parentPosition = GetOrbitPosition(parent);
var delta = position - parentPosition;
@ -257,16 +258,15 @@ public class Zone
var orbit = new OrbitData
{
ID = Guid.NewGuid(),
Distance = distance,
Parent = parent,
Parent = new CultRecordRef<OrbitData>(parent),
Phase = storedPhase
};
Orbits.Add(orbit.ID, new Orbit(Settings, orbit));
Orbits.Add(_itemManager.ItemData.Upsert(orbit).Key, new Orbit(Settings, orbit));
return orbit;
}
public void MineAsteroid(Entity miner, Guid asteroidBelt, int asteroid, float damage, float efficiency, float penetration)
public void MineAsteroid(Entity miner, CultRecordKey asteroidBelt, int asteroid, float damage, float efficiency, float penetration)
{
var beltData = Planets[asteroidBelt] as AsteroidBeltData;
var belt = AsteroidBelts[asteroidBelt];
@ -274,15 +274,15 @@ public class Zone
var size = beltData.Asteroids[asteroid].Size;
var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(size);
if (!belt.Damage.ContainsKey(asteroid))
belt.Damage[asteroid] = 0;
belt.Damage[asteroid] = belt.Damage[asteroid] + damage;
if (!belt.MiningAccumulator.ContainsKey((miner, asteroid)))
belt.MiningAccumulator[(miner, asteroid)] = 0;
belt.MiningAccumulator[(miner, asteroid)] = belt.MiningAccumulator[(miner, asteroid)] + damage;
if (belt.Damage[asteroid] > asteroidHitpoints)
{
belt.RespawnTimers[asteroid] = Settings.AsteroidRespawnTime.Evaluate(size);
@ -309,11 +309,11 @@ public class Zone
public SecurityLevel GetSecurityLevel(float2 pos)
{
if (GalaxyZone.Owner==null) return SecurityLevel.Open;
var security = SecurityLevel.Open;
foreach (var entity in Entities)
{
if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.ID == GalaxyZone.Owner.ID)
if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction == GalaxyZone.Owner)
{
if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
security = orbitalEntity.SecurityLevel;
@ -322,7 +322,7 @@ public class Zone
return security;
}
public float GetHeight(float2 position)
{
var result = -PowerPulse(length(position)/(Pack.Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth;

View file

@ -1,10 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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 GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using UniRx;
@ -17,14 +18,14 @@ using float3 = CultMath.float3;
public class ZonePack
{
[JsonProperty("planets"), Key(0)]
public List<BodyData> Planets = new List<BodyData>();
public List<CultRecordRef<BodyData>> Planets = new List<CultRecordRef<BodyData>>();
[JsonProperty("orbits"), Key(1)]
public List<OrbitData> Orbits = new List<OrbitData>();
public List<CultRecordRef<OrbitData>> Orbits = new List<CultRecordRef<OrbitData>>();
[JsonProperty("entities"), Key(2)]
public List<EntityPack> Entities = new List<EntityPack>();
[JsonProperty("radius"), Key(3)]
public float Radius = 2000;
@ -33,29 +34,24 @@ public class ZonePack
[JsonProperty("time"), Key(5)]
public double Time;
}
[MessagePackObject,
JsonObject(MemberSerialization.OptIn),
Union(0, typeof(PlanetData)),
Union(1, typeof(AsteroidBeltData)),
Union(2, typeof(GasGiantData)),
Union(3, typeof(SunData))]
public abstract class BodyData : DatabaseEntry, INamedEntry
[JsonObject(MemberSerialization.OptIn)]
public abstract class BodyData
{
[JsonProperty("name"), Key(1)]
[CultName, JsonProperty("name"), Key(1)]
public string Name = "";
[JsonProperty("orbit"), Key(2)]
public Guid Orbit;
public CultRecordRef<OrbitData> Orbit;
[JsonProperty("mass"), Key(3)]
public float Mass = 0;
[JsonProperty("resources"), Key(4)]
public Dictionary<Guid, float> Resources = new Dictionary<Guid, float>();
[CultReference(typeof(SimpleCommodityData), many: true), JsonProperty("resources"), Key(4)]
public Dictionary<CultRecordRef<SimpleCommodityData>, float> Resources = new Dictionary<CultRecordRef<SimpleCommodityData>, float>();
[JsonProperty("bodyRadiusMul")] [Key(5)]
public float BodyRadiusMultiplier = 1;
@ -68,42 +64,36 @@ public abstract class BodyData : DatabaseEntry, INamedEntry
[JsonProperty("depthExp")] [Key(8)]
public float GravityDepthExponent = 16;
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.planetdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class PlanetData : BodyData
{
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.asteroidbeltdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AsteroidBeltData : BodyData
{
[JsonProperty("asteroids"), Key(9)]
public Asteroid[] Asteroids;
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
[CultDocument("aetheria.gasgiantdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class GasGiantData : BodyData
{
[JsonProperty("firstOffsetDomainRotationSpeed"), Key(9)]
public float FirstOffsetDomainRotationSpeed = 1;
[JsonProperty("firstOffsetRotationSpeed"), Key(10)]
public float FirstOffsetRotationSpeed = 1;
[JsonProperty("secondOffsetDomainRotationSpeed"), Key(11)]
public float SecondOffsetDomainRotationSpeed = 1;
[JsonProperty("secondOffsetRotationSpeed"), Key(12)]
public float SecondOffsetRotationSpeed = 1;
[JsonProperty("albedoRotationSpeed"), Key(13)]
public float AlbedoRotationSpeed = 1;
@ -118,19 +108,20 @@ public class GasGiantData : BodyData
[JsonProperty("depthExp")] [Key(17)]
public float WaveSpeedMultiplier = 8;
[JsonProperty("materialOverrides"), Key(18)]
public List<string> MaterialOverrides = new List<string>();
[JsonProperty("colors"), Key(19)]
public float4[] Colors = new float4[0];
}
[CultDocument("aetheria.sundata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class SunData : GasGiantData
{
[JsonProperty("lightColor"), Key(20)]
public float3 LightColor = float3.zero;
[JsonProperty("fogTintColor"), Key(21)]
public float3 FogTintColor = float3.zero;
@ -143,32 +134,32 @@ public class Asteroid
{
[JsonProperty("distance"), Key(0)]
public float Distance;
[JsonProperty("phase"), Key(1)]
public float Phase;
[JsonProperty("size"), Key(2)]
public float Size;
[JsonProperty("rotationSpeed"), Key(3)]
public float RotationSpeed;
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class OrbitData : DatabaseEntry
[CultDocument("aetheria.orbitdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class OrbitData
{
[JsonProperty("parent"), Key(1)]
public Guid Parent;
public CultRecordRef<OrbitData> Parent;
[JsonProperty("distance"), Key(2)]
public float Distance;
[JsonProperty("phase"), Key(3)]
public float Phase;
[JsonProperty("phase"), Key(4)]
public float2 FixedPosition = float2.zero;
// [JsonProperty("period"), Key(4)]
// public float Period;
@ -177,4 +168,4 @@ public class OrbitData : DatabaseEntry
phase *= PI * 2;
return new float2(cos(phase), sin(phase));
}
}
}

View file

@ -1,4 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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/. */
@ -43,6 +43,7 @@ public static class ZoneGenerator
bool isTutorial = false)
{
var pack = new ZonePack();
var cache = itemManager.ItemData;
var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ (uint) pcg3d(galaxyZone.Position).x);
@ -50,7 +51,7 @@ public static class ZoneGenerator
pack.Radius = zoneSettings.ZoneRadius.Evaluate(density);
pack.Mass = zoneSettings.ZoneMass.Evaluate(density);
var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density);
//Debug.Log($"Generating zone at position {zone.Position} with radius {zoneRadius} and mass {zoneMass}");
var planets = new List<GeneratorPlanet>();
@ -58,10 +59,10 @@ public static class ZoneGenerator
{
var zoneBoundary = new Circle(float2.zero, pack.Radius * zoneSettings.ZoneBoundaryRadius);
float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point);
var occupiedAreas = new List<Circle>();
float tangentRadius(float2 point) => min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point)));
var startPosition = random.NextFloat(pack.Radius * .25f, pack.Radius * .5f) * random.NextFloat2Direction();
occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition)));
@ -91,52 +92,29 @@ public static class ZoneGenerator
}
else
planets.AddRange(GenerateEntities(zoneSettings, ref random, pack.Mass, pack.Radius, float2.zero));
// Create collections to map between zone generator output and database entries
// Every orbit is a record; parents link to records, so each orbit holds its key before any child names it
var orbitMap = new Dictionary<GeneratorPlanet, OrbitData>();
var orbitInverseMap = new Dictionary<OrbitData, GeneratorPlanet>();
// Create orbit database entries
pack.Orbits = planets.Select(planet =>
{
var data = new OrbitData
foreach (var planet in planets)
cache.Upsert(orbitMap[planet] = new OrbitData
{
FixedPosition = planet.FixedPosition,
Distance = planet.Distance,
//Period = planet.Period,
Phase = planet.Phase
};
orbitMap[planet] = data;
orbitInverseMap[data] = planet;
return data;
}).ToList();
});
foreach (var planet in planets.Where(p => p.Parent != null))
{
orbitMap[planet].Parent = cache.RefOf(orbitMap[planet.Parent]);
cache.Upsert(orbitMap[planet]);
}
pack.Orbits = planets.Select(planet => cache.RefOf(orbitMap[planet])).ToList();
// Link OrbitData parents to database GUIDs
foreach (var data in pack.Orbits)
data.Parent = orbitInverseMap[data].Parent != null
? orbitMap[orbitInverseMap[data].Parent].ID
: Guid.Empty;
// Cache resource densities
// var resourceMaps = mapLayers.Values
// .ToDictionary(m => m.ID, m => m.Evaluate(zone.Position, settings.ShapeSettings));
pack.Planets = planets.Where(p=>!p.Empty).Select(planet =>
{
// Dictionary<Guid, float> planetResources = new Dictionary<Guid, float>();
BodyType bodyType = planet.Belt ? BodyType.Asteroid :
planet.Mass > zoneSettings.SunMass ? BodyType.Sun :
planet.Mass > zoneSettings.GasGiantMass ? BodyType.GasGiant :
planet.Mass > zoneSettings.PlanetMass ? BodyType.Planet : BodyType.Planetoid;
// foreach (var r in resources)
// {
// if ((bodyType & r.ResourceBodyType) != 0)
// {
// float quantity = ResourceValue(ref random, settings, r, r.ResourceDensity.Aggregate(1f, (m, rdm) => m * resourceMaps[rdm]));
// if (r.Floor < quantity) planetResources.Add(r.ID, quantity);
// }
// }
BodyData planetData;
switch (bodyType)
@ -159,12 +137,10 @@ public static class ZoneGenerator
}
planetData.Mass = planet.Mass;
planetData.Orbit = orbitMap[planet].ID;
// planetData.Resources = planetResources;
planetData.Name = planetData.ID.ToString().Substring(0, 8);
planetData.Orbit = cache.RefOf(orbitMap[planet]);
if (planetData is AsteroidBeltData beltData)
{
beltData.Asteroids =
beltData.Asteroids =
Enumerable.Range(0, (int) zoneSettings.AsteroidCount.Evaluate(beltData.Mass * orbitMap[planet].Distance))
.Select(_ => new Asteroid
{
@ -197,12 +173,12 @@ public static class ZoneGenerator
float primary = random.NextFloat();
float right = frac(primary + zoneSettings.GasGiantBandColorSeparation);
float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation);
// Create n time keys from 0 to 1
var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f);
var times = Enumerable.Range(0, bandCount)
.Select(i => (float) i / (bandCount-1));
// Each band has a chance of being either the primary or one of the adjacent hues
// Saturation and Value are random with curves applied
gas.Colors = times
@ -211,7 +187,7 @@ public static class ZoneGenerator
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()),
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time))
.ToArray();
gas.FirstOffsetDomainRotationSpeed = 0;
}
gas.AlbedoRotationSpeed = -3;
@ -219,7 +195,10 @@ public static class ZoneGenerator
gas.SecondOffsetRotationSpeed = 10;
gas.SecondOffsetDomainRotationSpeed = -25;
}
return planetData;
// A body is named for the first eight characters of the key the cache mints for it
planetData.Name = cache.Upsert(planetData).Key.Value.Substring(0, 8);
return cache.Upsert(planetData);
}).ToList();
var nearestFaction = galaxy.Factions.MinBy(f => galaxy.HomeZones[f].Distance[galaxyZone]);
@ -253,30 +232,28 @@ public static class ZoneGenerator
{
var lagrangeOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = baseOrbit.Parent,
Distance = baseOrbit.Distance,
Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
};
pack.Orbits.Add(lagrangeOrbit);
pack.Orbits.Add(cache.Upsert(lagrangeOrbit));
return lagrangeOrbit;
}
void PlaceTurret(OrbitData orbit, LoadoutGenerator loadoutGenerator, int distanceMultiplier)
{
var phase = 20f * distanceMultiplier / orbit.Distance;
var turretOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = orbit.Distance,
Phase = orbit.Phase + phase
};
pack.Orbits.Add(turretOrbit);
pack.Orbits.Add(cache.Upsert(turretOrbit));
var turret = loadoutGenerator.GenerateTurretLoadout();
if (turret == null) return;
turret.Orbit = turretOrbit.ID;
turret.Orbit = cache.RefOf(turretOrbit);
pack.Entities.Add(turret);
}
@ -301,7 +278,7 @@ public static class ZoneGenerator
itemManager.Log($"Story station for {story.Faction?.Name} could not be generated; its story has nowhere to live!");
continue;
}
station.Orbit = lagrangeOrbit.ID;
station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = story.Security;
station.SecurityRadius = pack.Radius;
station.Story = i;
@ -318,7 +295,7 @@ public static class ZoneGenerator
var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout();
if (station == null) continue;
station.Orbit = lagrangeOrbit.ID;
station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = security;
station.SecurityRadius = pack.Radius;
pack.Entities.Add(station);
@ -336,7 +313,7 @@ public static class ZoneGenerator
return pack;
}
// static float ResourceValue(ref Random random, ZoneGenerationSettings settings, SimpleCommodityData resource, float density)
// {
// return random.NextPowerDistribution(resource.Minimum, resource.Maximum, resource.Exponent,
@ -357,20 +334,20 @@ public static class ZoneGenerator
// There is some chance of generating a rosette or binary system
// Probabilities which are fixed for the entire galaxy are in GlobalData, contained in the GameContext
var rosette = random.NextFloat() < settings.RosetteProbability;
if (rosette)
{
// Create a rosette with a number of vertices between 2 and 9 inclusive
root.ExpandRosette(ref random, (int)(random.NextFloat(1, 5) + random.NextFloat(1, 5)));
// Create a small number of less massive "captured" planets orbiting past the rosette
root.ExpandSolar(
ref random,
count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)),
massMulMin: .6f,
massMulMax: .8f,
distMulMin: 1.25f,
distMulMax: 1.75f,
count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)),
massMulMin: .6f,
massMulMax: .8f,
distMulMin: 1.25f,
distMulMax: 1.75f,
jupiterJump: 1,
massFraction: .1f);
@ -381,9 +358,9 @@ public static class ZoneGenerator
// Give each child in the rosette its own mini solar system
p.ExpandSolar(
ref random,
count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)),
massMulMin: 0.75f,
massMulMax: 2.5f,
count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)),
massMulMin: 0.75f,
massMulMax: 2.5f,
distMulMin: 1 + m * .25f,
distMulMax: 1.05f + m * .5f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1,
@ -396,9 +373,9 @@ public static class ZoneGenerator
// Create a regular old boring solar system
root.ExpandSolar(
ref random,
count: random.NextInt(5, 15),
massMulMin: 0.75f,
massMulMax: 2.5f,
count: random.NextInt(5, 15),
massMulMin: 0.75f,
massMulMax: 2.5f,
distMulMin: 1.1f,
distMulMax: 1.25f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1,
@ -412,13 +389,13 @@ public static class ZoneGenerator
{
// Get all children that are above the satellite creation mass floor and not rosette members
var satelliteCandidates = rosette
? root.AllPlanets().Where(p =>
p != root &&
p.Parent != root &&
? root.AllPlanets().Where(p =>
p != root &&
p.Parent != root &&
p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p))
: root.AllPlanets().Where(p =>
p != root &&
: root.AllPlanets().Where(p =>
p != root &&
p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p));
@ -455,7 +432,7 @@ public static class ZoneGenerator
var beltCandidates = rosette
? root.AllPlanets().Where(p => p != root && p.Parent != root && p.Mass < settings.BeltMassCeiling && !binaries.Contains(p) && p.Children.Count == 0)
: root.AllPlanets().Where(p => p != root && p.Mass < settings.BeltMassCeiling && !binaries.Contains(p) && p.Children.Count == 0);
foreach(var planet in beltCandidates.Reverse())
if (random.NextFloat() < settings.BeltProbability && !planet.Parent.Children.Any(p=>p.Belt))
planet.Belt = true;
@ -498,29 +475,29 @@ public class GeneratorPlanet
public void ExpandRosette(ref Random random, int vertices)
{
//Debug.Log("Expanding Rosette");
// Rosette children replace the parent, parent orbital node is left empty
Empty = true;
// Masses in a rosette alternate, so every sequential pair has the same shared mass
var sharedMass = Mass / vertices * 2;
// Proportion of the masses of sequential pairs is random, but only for even vertex counts
var proportion = vertices % 2 == 0 ? random.NextFloat(.5f,.95f) : .5f;
// Place children at a fixed distance in the center of the range
var dist = (ChildDistanceMinimum + ChildDistanceMaximum) / 2;
// Position of first child
var p0 = new float2(0, dist);
// Position of second child
var p1 = OrbitData.Evaluate(1.0f / vertices) * dist;
// Maximum child distance is half the distance to the neighbor minus the neighbor's radius
var p0ChildDist = (distance(p0, p1) * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f;
var p1ChildDist = (distance(p0, p1) * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f;
for (int i = 0; i < vertices; i++)
{
var child = new GeneratorPlanet
@ -547,14 +524,14 @@ public class GeneratorPlanet
// Expansion is impossible when child space is full
if (count == 0 || ChildDistanceMaximum < ChildDistanceMinimum)
return;
var masses = new float[count];
var distances = new float[count];
// Accumulate total mass
// Accumulate total mass
// Initialize first mass and distance, actual number doesn't matter since total mass will be divided proportionally
float massTotal = distances[0] = masses[0] = 1;
// Masses and distances multiply from one planet to the next
// Mass typically increases exponentially as you go further out
// jupiterJump is an additional mass multiplier applied after half of the planets
@ -580,7 +557,7 @@ public class GeneratorPlanet
distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum,
(oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]);
}
for (var i = 0; i < count; i++)
{
// Only instantiate children above the mass floor
@ -606,4 +583,4 @@ public class GeneratorPlanet
}
}
}
}
}

View file

@ -4,6 +4,8 @@
Condition="'$(SkipCultLibRevisionCheck)' != 'true' and '@(ProjectReference->AnyHaveMetadataValue('Identity', '$(CultLibRoot)\packages\cultmath\src\CultMath\CultMath.csproj'))' == 'true'">
<Error Condition="!Exists('$(CultLibRoot)\packages\cultmath\src\CultMath\CultMath.csproj')"
Text="CultLibRoot '$(CultLibRoot)' does not contain the required CultMath project." />
<Error Condition="!Exists('$(CultLibRoot)\src\GameCult.Caching\GameCult.Caching.csproj')"
Text="CultLibRoot '$(CultLibRoot)' does not contain the required GameCult.Caching project." />
<Exec Command="&quot;$(GitExecutable)&quot; -C &quot;$(CultLibRoot)&quot; rev-parse HEAD"
ConsoleToMSBuild="true"
StandardOutputImportance="Low">