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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="$(CultLibRoot)\packages\cultmath\src\CultMath\CultMath.csproj" /> <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" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
</Project> </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 System;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
@ -29,7 +30,7 @@ public abstract class MoveToState : BaseState
public class MoveToOrbitState : MoveToState public class MoveToOrbitState : MoveToState
{ {
public Guid Orbit { get; set; } public CultRecordKey Orbit { get; set; }
public MoveToOrbitState(Agent agent) : base(agent) { } public MoveToOrbitState(Agent agent) : base(agent) { }
protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(Orbit); 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[MessagePackObject, // Agent tasks are owned by their Agent and not persisted.
Union(0, typeof(StationTowing)), [JsonObject(MemberSerialization.OptIn)]
Union(1, typeof(Mining)), public abstract class AgentTask
Union(2, typeof(Survey)),
Union(3, typeof(HaulingTask)),
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
public abstract class AgentTask : DatabaseEntry
{ {
[JsonProperty("priority"), Key(1)] [JsonProperty("priority"), Key(1)]
public int Priority; public int Priority;
[JsonProperty("zone"), Key(2)]
public Guid Zone;
[JsonProperty("reserved"), Key(3)] [JsonProperty("reserved"), Key(3)]
public bool Reserved; public bool Reserved;
[IgnoreMember] public abstract TaskType Type { get; } [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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,7 +20,7 @@ public class HaulingTask : AgentTask
public Entity Target; public Entity Target;
[JsonProperty("itemType"), Key(6)] [JsonProperty("itemType"), Key(6)]
public Guid ItemType; public CultRecordKey ItemType;
[JsonProperty("quantity"), Key(7)] [JsonProperty("quantity"), Key(7)]
public int Quantity; public int Quantity;

View file

@ -2,19 +2,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[MessagePackObject, [MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))] JsonObject(MemberSerialization.OptIn)]
public class Mining : AgentTask public class Mining : AgentTask
{ {
[IgnoreMember] public override TaskType Type => TaskType.Mine; [IgnoreMember] public override TaskType Type => TaskType.Mine;
[JsonProperty("asteroids"), Key(4)] [JsonProperty("asteroids"), Key(4)]
public Guid Asteroids; public CultRecordKey Asteroids;
} }

View file

@ -1,15 +1,16 @@
using GameCult.Caching;
using System; using System;
public class PatrolOrbitsTask : AgentTask public class PatrolOrbitsTask : AgentTask
{ {
public override TaskType Type => TaskType.Defend; public override TaskType Type => TaskType.Defend;
public Guid[] Circuit; public CultRecordKey[] Circuit;
} }
public class PatrolOrbitsState : BaseState public class PatrolOrbitsState : BaseState
{ {
public PatrolOrbitsTask Task; public PatrolOrbitsTask Task;
public Guid CurrentTarget public CultRecordKey CurrentTarget
{ {
get => Task.Circuit[_currentTargetIndex]; 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[MessagePackObject, [MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))] JsonObject(MemberSerialization.OptIn)]
public class StationTowing : AgentTask public class StationTowing : AgentTask
{ {
[IgnoreMember] public override TaskType Type => TaskType.Tow; [IgnoreMember] public override TaskType Type => TaskType.Tow;
@ -19,7 +19,7 @@ public class StationTowing : AgentTask
public OrbitalEntity Station; public OrbitalEntity Station;
[JsonProperty("orbitParent"), Key(5)] [JsonProperty("orbitParent"), Key(5)]
public Guid OrbitParent; public CultRecordKey OrbitParent;
[JsonProperty("orbitDistance"), Key(6)] [JsonProperty("orbitDistance"), Key(6)]
public float OrbitDistance; public float OrbitDistance;

View file

@ -2,19 +2,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[MessagePackObject, [MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))] JsonObject(MemberSerialization.OptIn)]
public class Survey : AgentTask public class Survey : AgentTask
{ {
[IgnoreMember] public override TaskType Type => TaskType.Explore; [IgnoreMember] public override TaskType Type => TaskType.Explore;
[JsonProperty("station"), Key(4)] [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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
using CultMath; using CultMath;
@ -40,13 +41,13 @@ public class AetherDriveData : BehaviorData
[Inspectable, JsonProperty("passiveCoupling"), Key(10), RuntimeInspectable] [Inspectable, JsonProperty("passiveCoupling"), Key(10), RuntimeInspectable]
public PerformanceStat PassiveCoupling; public PerformanceStat PassiveCoupling;
[InspectableAudioParameter, JsonProperty("rpmAudio"), Key(11), RuntimeInspectable] [Inspectable, JsonProperty("rpmAudio"), Key(11), RuntimeInspectable]
public uint RpmAudioParameter; public uint RpmAudioParameter;
[InspectableAudioParameter, JsonProperty("torqueAudio"), Key(12), RuntimeInspectable] [Inspectable, JsonProperty("torqueAudio"), Key(12), RuntimeInspectable]
public uint TorqueRatioAudioParameter; public uint TorqueRatioAudioParameter;
[InspectablePrefab, JsonProperty("particles"), Key(13)] [Inspectable, CultInspectorAssetPath, JsonProperty("particles"), Key(13)]
public string Particles; public string Particles;
public override Behavior CreateInstance(EquippedItem item) public override Behavior CreateInstance(EquippedItem item)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ public class InstantWeaponData : WeaponData
[Inspectable, JsonProperty("cooldown"), Key(19), RuntimeInspectable] [Inspectable, JsonProperty("cooldown"), Key(19), RuntimeInspectable]
public PerformanceStat Cooldown = new PerformanceStat(); public PerformanceStat Cooldown = new PerformanceStat();
[InspectablePrefab, JsonProperty("ammoInterval"), Key(20)] [Inspectable, JsonProperty("ammoInterval"), Key(20)]
public bool SingleAmmoBurst; public bool SingleAmmoBurst;
public override Behavior CreateInstance(EquippedItem item) public override Behavior CreateInstance(EquippedItem item)
@ -124,12 +124,12 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
} }
var hasAmmo = true; 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) if (cargo != null)
{ {
var item = cargo.ItemsOfType[_data.AmmoType][0]; var item = cargo.ItemsOfType[_data.AmmoType.Key][0];
if (item is SimpleCommodity simpleCommodity) if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1); 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Linq; using System.Linq;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-5), RuntimeInspectable] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class ItemUsageData : BehaviorData public class ItemUsageData : BehaviorData
{ {
[InspectableDatabaseLink(typeof(SimpleCommodityData)), JsonProperty("item"), Key(1), RuntimeInspectable] [Inspectable, JsonProperty("item"), Key(1), RuntimeInspectable]
public Guid Item; public CultRecordRef<SimpleCommodityData> Item;
public override Behavior CreateInstance(EquippedItem item) public override Behavior CreateInstance(EquippedItem item)
{ {
@ -40,10 +41,10 @@ public class ItemUsage : Behavior
public override bool Execute(float dt) public override bool Execute(float dt)
{ {
var cargo = Entity.FindItemInCargo(_data.Item); var cargo = Entity.FindItemInCargo(_data.Item.Key);
if (cargo == null) return false; 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) if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1); cargo.Remove(simpleCommodity, 1);
if (item is CraftedItemInstance craftedItemInstance) 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Linq; using System.Linq;
using MessagePack; using MessagePack;
@ -9,7 +10,7 @@ using Newtonsoft.Json;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(10)] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class MiningToolData : BehaviorData public class MiningToolData : BehaviorData
{ {
[Inspectable, JsonProperty("dps"), Key(1)] [Inspectable, JsonProperty("dps"), Key(1)]
@ -36,7 +37,7 @@ public class MiningToolData : BehaviorData
public class MiningTool : Behavior public class MiningTool : Behavior
{ {
public Guid AsteroidBelt; public CultRecordKey AsteroidBelt;
public int Asteroid; public int Asteroid;
private MiningToolData _data; private MiningToolData _data;
@ -56,7 +57,7 @@ public class MiningTool : Behavior
{ {
Range = Evaluate(_data.Range); Range = Evaluate(_data.Range);
var belt = Entity.Zone.AsteroidBelts[AsteroidBelt]; var belt = Entity.Zone.AsteroidBelts[AsteroidBelt];
if (AsteroidBelt != Guid.Empty && if (AsteroidBelt.IsSet() &&
Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid) && Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid) &&
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range) 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Linq; using System.Linq;
using MessagePack; using MessagePack;
@ -38,18 +39,18 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
private ResourceScannerData _data; private ResourceScannerData _data;
private float _scanTime; private float _scanTime;
private Guid _scanTarget; private CultRecordKey _scanTarget;
public float Range { get; private set; } public float Range { get; private set; }
public float MinimumDensity { get; private set; } public float MinimumDensity { get; private set; }
public float ScanDuration { get; private set; } public float ScanDuration { get; private set; }
public Guid ScanTarget public CultRecordKey ScanTarget
{ {
get => _scanTarget; get => _scanTarget;
set set
{ {
if (value != _scanTarget) if (!value.Equals(_scanTarget))
{ {
_scanTarget = value; _scanTarget = value;
_scanTime = 0; _scanTime = 0;
@ -90,7 +91,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
} }
else 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; _scanTime += dt;
if (_scanTime > ScanDuration) 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
@ -10,18 +10,19 @@ using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class StatModifierData : BehaviorData public class StatModifierData : BehaviorData
{ {
[Inspectable, JsonProperty("stat"), Key(1)] [Inspectable, JsonProperty("stat"), Key(1)]
public StatReference Stat = new StatReference(); public StatReference Stat = new StatReference();
[Inspectable, JsonProperty("modifier"), Key(2)] [Inspectable, JsonProperty("modifier"), Key(2)]
public PerformanceStat Modifier = new PerformanceStat(); public PerformanceStat Modifier = new PerformanceStat();
[Inspectable, JsonProperty("type"), Key(3)] [Inspectable, JsonProperty("type"), Key(3)]
public StatModifierType Type; public StatModifierType Type;
// The simple name of a BehaviorData type, as InspectableType writes it
[InspectableType(typeof(BehaviorData)), JsonProperty("requireBehavior"), Key(4)] [InspectableType(typeof(BehaviorData)), JsonProperty("requireBehavior"), Key(4)]
public Type RequireBehavior; public string RequireBehavior;
public override Behavior CreateInstance(EquippedItem item) public override Behavior CreateInstance(EquippedItem item)
{ {
return new StatModifier(this, item); return new StatModifier(this, item);
@ -33,13 +34,12 @@ public class StatModifierData : BehaviorData
} }
} }
[Order(-4)]
public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwaysUpdatedBehavior public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwaysUpdatedBehavior
{ {
private StatModifierData _data; private StatModifierData _data;
private PerformanceStat[] _stats; private PerformanceStat[] _stats;
private static Type[] _statObjects; private static Type[] _statObjects;
private bool _applied; private bool _applied;
@ -70,14 +70,14 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
if (typeof(EquippableItemData).IsAssignableFrom(targetType)) if (typeof(EquippableItemData).IsAssignableFrom(targetType))
_stats = Entity.Equipment _stats = Entity.Equipment
.Select(hp => hp.EquippableItem) .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) .Where(gear => ItemManager.GetData(gear).GetType() == targetType)
.Select(gear => statField.GetValue(ItemManager.GetData(gear)) as PerformanceStat) .Select(gear => statField.GetValue(ItemManager.GetData(gear)) as PerformanceStat)
.ToArray(); .ToArray();
else else
_stats = Entity.Equipment _stats = Entity.Equipment
.Select(hp => hp.EquippableItem) .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) .SelectMany(gear => ItemManager.GetData(gear).Behaviors)
.Where(behaviorData => behaviorData.GetType() == targetType) .Where(behaviorData => behaviorData.GetType() == targetType)
.Select(behaviorData => statField.GetValue(behaviorData) as PerformanceStat) .Select(behaviorData => statField.GetValue(behaviorData) as PerformanceStat)
@ -135,7 +135,7 @@ public class StatReference
{ {
[InspectableType(typeof(BehaviorData)), JsonProperty("behavior"), Key(1)] [InspectableType(typeof(BehaviorData)), JsonProperty("behavior"), Key(1)]
public string Target; public string Target;
[Inspectable, JsonProperty("stat"), Key(2)] [Inspectable, JsonProperty("stat"), Key(2)]
public string Stat; public string Stat;
} }

View file

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

View file

@ -7,7 +7,7 @@ using System.Linq;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(-22)] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ThermotoggleData : BehaviorData public class ThermotoggleData : BehaviorData
{ {
[InspectableTemperature, JsonProperty("targetTemp"), Key(1)] [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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
using CultMath; using CultMath;
@ -22,7 +23,7 @@ public class ThrusterData : BehaviorData
[Inspectable, JsonProperty("energy"), Key(4), RuntimeInspectable] [Inspectable, JsonProperty("energy"), Key(4), RuntimeInspectable]
public PerformanceStat EnergyUsage = new PerformanceStat(); public PerformanceStat EnergyUsage = new PerformanceStat();
[InspectablePrefab, JsonProperty("Particles"), Key(5)] [Inspectable, CultInspectorAssetPath, JsonProperty("Particles"), Key(5)]
public string ParticlesPrefab; public string ParticlesPrefab;
public override Behavior CreateInstance(EquippedItem item) public override Behavior CreateInstance(EquippedItem item)

View file

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

View file

@ -23,7 +23,6 @@ public class VelocityLimitData : BehaviorData
} }
} }
[Order(100)]
public class VelocityLimit : Behavior public class VelocityLimit : Behavior
{ {
public float Limit { get; private set; } 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -24,10 +25,10 @@ public abstract class WeaponData : BehaviorData
[Inspectable, JsonProperty("damage"), Key(2), RuntimeInspectable] [Inspectable, JsonProperty("damage"), Key(2), RuntimeInspectable]
public PerformanceStat Damage = new PerformanceStat(); 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(); 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(); public PerformanceStat DamageSpread = new PerformanceStat();
[Inspectable, JsonProperty("minRange"), Key(5)] [Inspectable, JsonProperty("minRange"), Key(5)]
@ -39,28 +40,28 @@ public abstract class WeaponData : BehaviorData
[InspectableAnimationCurve, JsonProperty("damageCurve"), Key(7)] [InspectableAnimationCurve, JsonProperty("damageCurve"), Key(7)]
public BezierCurve DamageCurve; public BezierCurve DamageCurve;
[InspectablePrefab, JsonProperty("effect"), Key(8)] [Inspectable, CultInspectorAssetPath, JsonProperty("effect"), Key(8)]
public string EffectPrefab; public string EffectPrefab;
[InspectablePrefab, JsonProperty("energy"), Key(9), RuntimeInspectable] [Inspectable, JsonProperty("energy"), Key(9), RuntimeInspectable]
public PerformanceStat Energy = new PerformanceStat(); public PerformanceStat Energy = new PerformanceStat();
[InspectablePrefab, JsonProperty("heat"), Key(10), RuntimeInspectable] [Inspectable, JsonProperty("heat"), Key(10), RuntimeInspectable]
public PerformanceStat Heat = new PerformanceStat(); public PerformanceStat Heat = new PerformanceStat();
[InspectablePrefab, JsonProperty("visibility"), Key(11), RuntimeInspectable] [Inspectable, JsonProperty("visibility"), Key(11), RuntimeInspectable]
public PerformanceStat Visibility = new PerformanceStat(); public PerformanceStat Visibility = new PerformanceStat();
[InspectableDatabaseLink(typeof(SimpleCommodityData)), JsonProperty("ammo"), Key(12), RuntimeInspectable] [Inspectable, JsonProperty("ammo"), Key(12), RuntimeInspectable]
public Guid AmmoType; public CultRecordRef<SimpleCommodityData> AmmoType;
[InspectablePrefab, JsonProperty("magSize"), Key(13)] [Inspectable, JsonProperty("magSize"), Key(13)]
public int MagazineSize; public int MagazineSize;
[InspectablePrefab, JsonProperty("reloadTime"), Key(14)] [Inspectable, JsonProperty("reloadTime"), Key(14)]
public float ReloadTime = 1; public float ReloadTime = 1;
[InspectablePrefab, JsonProperty("spread"), Key(15)] [Inspectable, JsonProperty("spread"), Key(15)]
public PerformanceStat Spread = new PerformanceStat(); public PerformanceStat Spread = new PerformanceStat();
[Inspectable, JsonProperty("velocity"), Key(16)] [Inspectable, JsonProperty("velocity"), Key(16)]

View file

@ -8,7 +8,7 @@ using Newtonsoft.Json;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), Order(1000), RuntimeInspectable] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class WearData : BehaviorData public class WearData : BehaviorData
{ {
[InspectableTemperature, JsonProperty("perSecond"), Key(1)] [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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
using CultMath; using CultMath;
[DatabaseCategory("Galaxy"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.faction", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class Faction : DatabaseEntry, INamedEntry public class Faction
{ {
[Inspectable, JsonProperty("name"), Key(1)] [Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name; public string Name;
[Inspectable, JsonProperty("shortName"), Key(2)] [Inspectable, JsonProperty("shortName"), Key(2)]
public string ShortName; public string ShortName;
[InspectableText, JsonProperty("description"), Key(3)] [Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(3)]
public string Description; public string Description;
[InspectableTexture, JsonProperty("logo"), Key(4)] [Inspectable, CultInspectorAssetPath, JsonProperty("logo"), Key(4)]
public string Logo; public string Logo;
[InspectableDatabaseLink(typeof(PersonalityAttribute)), JsonProperty("personality"), Key(5)] [Inspectable, CultReference(typeof(PersonalityAttribute), many: true), JsonProperty("personality"), Key(5)]
public Dictionary<Guid, float> Personality = new Dictionary<Guid, float>(); public Dictionary<CultRecordRef<PersonalityAttribute>, float> Personality = new Dictionary<CultRecordRef<PersonalityAttribute>, float>();
// [Inspectable, JsonProperty("hostile"), Key(6)] // [Inspectable, JsonProperty("hostile"), Key(6)]
// public bool PlayerHostile; // public bool PlayerHostile;
[InspectableColor, JsonProperty("primaryColor"), Key(7)] [InspectableColor, JsonProperty("primaryColor"), Key(7)]
public float3 PrimaryColor; public float3 PrimaryColor;
[InspectableColor, JsonProperty("secondaryColor"), Key(8)] [InspectableColor, JsonProperty("secondaryColor"), Key(8)]
public float3 SecondaryColor; public float3 SecondaryColor;
[InspectableDatabaseLink(typeof(NameFile)), JsonProperty("nameFile"), Key(9)] [Inspectable, JsonProperty("nameFile"), Key(9)]
public Guid GeonameFile; public CultRecordRef<NameFile> GeonameFile;
[InspectableDatabaseLink(typeof(HullData)), JsonProperty("bossHull"), Key(10)] [Inspectable, JsonProperty("bossHull"), Key(10)]
public Guid BossHull; public CultRecordRef<HullData> BossHull;
[Inspectable, JsonProperty("influence"), Key(11)] [Inspectable, JsonProperty("influence"), Key(11)]
public int InfluenceDistance = 4; 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; public uint OverworldMusic;
[InspectableSoundBank, JsonProperty("combatMusic"), Key(14)] [Inspectable, JsonProperty("combatMusic"), Key(14)]
public uint CombatMusic; public uint CombatMusic;
[InspectableSoundBank, JsonProperty("bossMusic"), Key(15)] [Inspectable, JsonProperty("bossMusic"), Key(15)]
public uint BossMusic; public uint BossMusic;
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
} }
[Inspectable, MessagePackObject] [CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject]
public class NameFile : DatabaseEntry, INamedEntry public class NameFile
{ {
[Key(1)] public string Name; [CultName, Key(1)] public string Name;
[Key(2)] public string[] Names; [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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using GameCult.Caching;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -288,7 +289,7 @@ public abstract class Entity
// TODO: Inter-faction hostility // TODO: Inter-faction hostility
// When the entity faction owns the zone, they are hostile to trespassers or those hostile to them // 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.PresencePermitted?.Value ?? true) : !(other.PresencePermitted?.Value ?? true)|| other.IsHostileTo(this, true);
return !recursive && other.IsHostileTo(this, true); return !recursive && other.IsHostileTo(this, true);
@ -301,7 +302,7 @@ public abstract class Entity
return FactionRelationship.Neutral; return FactionRelationship.Neutral;
if (this is Ship {IsPlayerShip: true}) if (this is Ship {IsPlayerShip: true})
return Zone.Galaxy.FactionRelationships[faction]; 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) => public static bool IsPresencePermitted(FactionRelationship relationship, SecurityLevel securityLevel) =>
@ -316,7 +317,7 @@ public abstract class Entity
public ConsumableItemEffect FindActiveConsumable(ConsumableItemData data) 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) public bool CanActivateConsumable(ConsumableItemData data)
@ -328,10 +329,11 @@ public abstract class Entity
{ {
if (!CanActivateConsumable(data)) return false; 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; if (bay == null) return false;
var item = (ConsumableItem) bay.ItemsOfType[data.ID].First(); var item = (ConsumableItem) bay.ItemsOfType[key].First();
ActivateConsumable(item); ActivateConsumable(item);
bay.Remove(item); bay.Remove(item);
return true; return true;
@ -377,7 +379,7 @@ public abstract class Entity
public void GenerateWeaponGroups() public void GenerateWeaponGroups()
{ {
foreach (var group in Weapons 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)) .OrderBy(wg=>wg.Average(w=>w.Range))
.Select((weapons, index) => (weapons, index))) .Select((weapons, index) => (weapons, index)))
{ {
@ -394,7 +396,7 @@ public abstract class Entity
Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y]; Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y];
} }
public int CountItemsInCargo(Guid itemDataID) public int CountItemsInCargo(CultRecordKey itemDataID)
{ {
int sum = 0; int sum = 0;
foreach (var x in CargoBays) foreach (var x in CargoBays)
@ -408,7 +410,7 @@ public abstract class Entity
return sum; return sum;
} }
public EquippedCargoBay FindItemInCargo(Guid itemDataID) public EquippedCargoBay FindItemInCargo(CultRecordKey itemDataID)
{ {
return CargoBays.FirstOrDefault(c => c.ItemsOfType.ContainsKey(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 // Attempts to move a given number of items of the given type to the target Entity
// Returns the number of items successfully transferred // 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; int quantityTransferred = 0;
while (quantityTransferred < quantity) while (quantityTransferred < quantity)
@ -1052,7 +1054,7 @@ public class ConsumableItemEffect
{ {
Item = item; Item = item;
Entity = entity; Entity = entity;
Data = (ConsumableItemData) item.Data.Value; Data = (ConsumableItemData) entity.ItemManager.GetData(item);
RemainingDuration = Data.Duration; RemainingDuration = Data.Duration;
Behaviors = Data.Behaviors Behaviors = Data.Behaviors
@ -1329,7 +1331,7 @@ public class EquippedCargoBay : EquippedItem
public readonly ItemInstance[,] Occupancy; 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; 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 // Check whether the given item will fit when its origin is placed at the given coordinate
public bool ItemFits(ItemInstance item, int2 cargoCoord) 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 // Check every cell of the item's shape
foreach (var i in itemData.Shape.Coordinates) 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 // For simple commodities, search for existing item stacks to add to
foreach (var cargoItem in Cargo.Keys) 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; var cargoCommodity = (SimpleCommodity) cargoItem;
if (cargoCommodity.Quantity >= itemData.MaxStack) continue; if (cargoCommodity.Quantity >= itemData.MaxStack) continue;
@ -1486,11 +1488,11 @@ public class EquippedCargoBay : EquippedItem
} }
Cargo[item] = cargoCoord; Cargo[item] = cargoCoord;
if(!ItemsOfType.ContainsKey(item.Data.LinkID)) if(!ItemsOfType.ContainsKey(item.Data.Key))
ItemsOfType[item.Data.LinkID] = new List<ItemInstance>(); ItemsOfType[item.Data.Key] = new List<ItemInstance>();
ItemsOfType[item.Data.LinkID].Add(item); 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) if (cargoCommodity.Quantity + item.Quantity <= itemData.MaxStack)
{ {
@ -1530,9 +1532,9 @@ public class EquippedCargoBay : EquippedItem
} }
Cargo[item] = cargoCoord; Cargo[item] = cargoCoord;
if(!ItemsOfType.ContainsKey(item.Data.LinkID)) if(!ItemsOfType.ContainsKey(item.Data.Key))
ItemsOfType[item.Data.LinkID] = new List<ItemInstance>(); ItemsOfType[item.Data.Key] = new List<ItemInstance>();
ItemsOfType[item.Data.LinkID].Add(item); ItemsOfType[item.Data.Key].Add(item);
Mass += ItemManager.GetMass(item); Mass += ItemManager.GetMass(item);
ThermalMass += ItemManager.GetThermalMass(item); ThermalMass += ItemManager.GetThermalMass(item);
@ -1555,9 +1557,9 @@ public class EquippedCargoBay : EquippedItem
Occupancy[v.x, v.y] = null; Occupancy[v.x, v.y] = null;
Cargo.Remove(item); Cargo.Remove(item);
ItemsOfType[item.Data.LinkID].Remove(item); ItemsOfType[item.Data.Key].Remove(item);
if (!ItemsOfType[item.Data.LinkID].Any()) if (!ItemsOfType[item.Data.Key].Any())
ItemsOfType.Remove(item.Data.LinkID); ItemsOfType.Remove(item.Data.Key);
Mass -= ItemManager.GetMass(item); Mass -= ItemManager.GetMass(item);
ThermalMass -= ItemManager.GetThermalMass(item); ThermalMass -= ItemManager.GetThermalMass(item);
@ -1584,9 +1586,9 @@ public class EquippedCargoBay : EquippedItem
Occupancy[v.x, v.y] = null; Occupancy[v.x, v.y] = null;
Cargo.Remove(item); Cargo.Remove(item);
ItemsOfType[item.Data.LinkID].Remove(item); ItemsOfType[item.Data.Key].Remove(item);
if (!ItemsOfType[item.Data.LinkID].Any()) if (!ItemsOfType[item.Data.Key].Any())
ItemsOfType.Remove(item.Data.LinkID); ItemsOfType.Remove(item.Data.Key);
Mass -= ItemManager.GetMass(item); Mass -= ItemManager.GetMass(item);
ThermalMass -= ItemManager.GetThermalMass(item); ThermalMass -= ItemManager.GetThermalMass(item);

View file

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

View file

@ -15,9 +15,6 @@ using float2 = CultMath.float2;
public static class Extensions 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) public static bool IsImplementationOf(this Type baseType, Type interfaceType)
{ {
return baseType.GetInterfaces().Any(interfaceType.Equals); return baseType.GetInterfaces().Any(interfaceType.Equals);

View file

@ -4,36 +4,30 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
// A manufacturer's branded variant of a generic design: its name, its flavor text, and how it builds each of // 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 // 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. // progression are authored here rather than as duplicate item entries.
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.factionproductdata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FactionProductData : DatabaseEntry, INamedEntry public class FactionProductData
{ {
[Inspectable, JsonProperty("name"), Key(1)] [Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name; public string Name;
[InspectableText, JsonProperty("description"), Key(2)] [Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(2)]
public string Description; public string Description;
[InspectableDatabaseLink(typeof(CraftedItemData)), JsonProperty("design"), Key(3)] [Inspectable, JsonProperty("design"), Key(3)]
public Guid Design; public CultRecordRef<CraftedItemData> Design;
[InspectableDatabaseLink(typeof(Faction)), JsonProperty("manufacturer"), Key(4)] [Inspectable, JsonProperty("manufacturer"), Key(4)]
public Guid Manufacturer; public CultRecordRef<Faction> Manufacturer;
[Inspectable, JsonProperty("roles"), Key(5)] [Inspectable, JsonProperty("roles"), Key(5)]
public List<ProductRole> Roles = new List<ProductRole>(); 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 // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -29,7 +30,7 @@ public class Galaxy
private Action<string> Log { get; } private Action<string> Log { get; }
public bool IsPrelude { get; } public bool IsPrelude { get; }
private HashSet<Guid> _containedFactions; private HashSet<CultRecordKey> _containedFactions;
private GalaxyZone[] _exitPath; private GalaxyZone[] _exitPath;
private Dictionary<Faction, MarkovNameGenerator> _nameGenerators = new Dictionary<Faction, MarkovNameGenerator>(); private Dictionary<Faction, MarkovNameGenerator> _nameGenerators = new Dictionary<Faction, MarkovNameGenerator>();
private readonly CultCache _cache; private readonly CultCache _cache;
@ -51,13 +52,14 @@ public class Galaxy
Log = log; Log = log;
Background = savedGame.Background; 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++) for (var i = 0; i < Factions.Length; i++)
{ {
FactionRelationships[Factions[i]] = savedGame.Relationships[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 return new GalaxyZone
{ {
@ -69,9 +71,9 @@ public class Galaxy
foreach (var i in savedGame.DiscoveredZones) DiscoveredZones.Add(Zones[i]); foreach (var i in savedGame.DiscoveredZones) DiscoveredZones.Add(Zones[i]);
for (var i = 0; i < Zones.Length; i++) for (var i = 0; i < Zones.Length; i++)
{ {
Zones[i].AdjacentZones = savedGame.Zones[i].AdjacentZones.Select(azi => Zones[azi]).ToList(); Zones[i].AdjacentZones = savedZones[i].AdjacentZones.Select(azi => Zones[azi]).ToList();
Zones[i].Factions = savedGame.Zones[i].Factions.Select(mi => Factions[mi]).ToArray(); Zones[i].Factions = savedZones[i].Factions.Select(mi => Factions[mi]).ToArray();
Zones[i].Owner = savedGame.Zones[i].Owner < 0 ? null : Factions[savedGame.Zones[i].Owner]; Zones[i].Owner = savedZones[i].Owner < 0 ? null : Factions[savedZones[i].Owner];
} }
HomeZones = savedGame.HomeZones.ToDictionary( 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 // Choose some megas to have bosses placed based on whether a boss hull is assigned
var bossMegas = Factions var bossMegas = Factions
.Where(m => m.BossHull != Guid.Empty) .Where(m => m.BossHull.IsSet())
.Take(bossCount) .Take(bossCount)
.ToArray(); .ToArray();
@ -335,7 +337,7 @@ public class Galaxy
progressCallback?.Invoke($"Feeding Markov Chains: {i + 1} / {Factions.Length}"); 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 //if(progressCallback!=null) Thread.Sleep(250); // Inserting Delay to make it seem like it's doing more work lmao
var faction = Factions[i]; 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 // 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)); _containedFactions ??= new HashSet<CultRecordKey>(Factions.Select(f => _cache.RefOf(f).Key));
return _containedFactions.Contains(factionID); return _containedFactions.Contains(faction.Key);
} }
class DijkstraVertex class DijkstraVertex

View file

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

View file

@ -6,7 +6,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JsonKnownTypes; using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
using CultMath; using CultMath;
@ -268,17 +268,17 @@ public class Shape
} }
[MessagePackObject, JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<ItemData>))] [JsonObject(MemberSerialization.OptIn)]
public abstract class ItemData : DatabaseEntry, INamedEntry public abstract class ItemData
{ {
[Inspectable, JsonProperty("name"), Key(1)] [Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name; public string Name;
[InspectableText, JsonProperty("description"), Key(2)] [Inspectable, CultInspectorTextArea, JsonProperty("description"), Key(2)]
public string Description; public string Description;
[InspectableDatabaseLink(typeof(Faction)), JsonProperty("creator"), Key(3)] [Inspectable, JsonProperty("creator"), Key(3)]
public Guid Manufacturer; public CultRecordRef<Faction> Manufacturer;
[Inspectable, JsonProperty("mass"), Key(4)] [Inspectable, JsonProperty("mass"), Key(4)]
public float Mass; public float Mass;
@ -295,16 +295,9 @@ public abstract class ItemData : DatabaseEntry, INamedEntry
[Inspectable, JsonProperty("price"), Key(8)] [Inspectable, JsonProperty("price"), Key(8)]
public int Price = 0; 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 public class SimpleCommodityData : ItemData
{ {
// MessagePack keys 6, 7, 8 and 11 belonged to removed resource-distribution fields; do not reuse them. // 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; public SimpleCommodityCategory Category;
} }
[MessagePackObject, JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<CraftedItemData>))] [JsonObject(MemberSerialization.OptIn)]
public abstract class CraftedItemData : ItemData public abstract class CraftedItemData : ItemData
{ {
// The named slots this design is assembled from. A stat may read the quality of the part filling one. // 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; public string Name;
} }
[DatabaseCategory("Items"), Inspectable, MessagePackObject] [CultDocument("aetheria.compoundcommoditydata", "1"), Inspectable, MessagePackObject]
public class CompoundCommodityData : CraftedItemData public class CompoundCommodityData : CraftedItemData
{ {
[InspectableDatabaseLink(typeof(PersonalityAttribute)), JsonProperty("demandProfile"), Key(10)] [Inspectable, CultReference(typeof(PersonalityAttribute), many: true), JsonProperty("demandProfile"), Key(10)]
public Dictionary<Guid, float> DemandProfile = new Dictionary<Guid, float>(); public Dictionary<CultRecordRef<PersonalityAttribute>, float> DemandProfile = new Dictionary<CultRecordRef<PersonalityAttribute>, float>();
[Inspectable, JsonProperty("category"), Key(11)] [Inspectable, JsonProperty("category"), Key(11)]
public CompoundCommodityCategory Category; public CompoundCommodityCategory Category;
} }
[DatabaseCategory("Items"), Inspectable, MessagePackObject] [CultDocument("aetheria.consumableitemdata", "1"), Inspectable, MessagePackObject]
public class ConsumableItemData : CraftedItemData public class ConsumableItemData : CraftedItemData
{ {
[Inspectable, JsonProperty("behaviors"), Key(10)] [Inspectable, JsonProperty("behaviors"), Key(10)]
@ -356,7 +349,7 @@ public class ConsumableItemData : CraftedItemData
[Inspectable, JsonProperty("duration"), Key(12)] [Inspectable, JsonProperty("duration"), Key(12)]
public float Duration; public float Duration;
[InspectableTexture, JsonProperty("icon"), Key(13)] [Inspectable, CultInspectorAssetPath, JsonProperty("icon"), Key(13)]
public string Icon; public string Icon;
[Inspectable, JsonProperty("effectiveness"), Key(14)] [Inspectable, JsonProperty("effectiveness"), Key(14)]
@ -366,7 +359,7 @@ public class ConsumableItemData : CraftedItemData
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public abstract class EquippableItemData : CraftedItemData public abstract class EquippableItemData : CraftedItemData
{ {
[InspectableTexture, JsonProperty("schematic"), Key(10)] [Inspectable, CultInspectorAssetPath, JsonProperty("schematic"), Key(10)]
public string Schematic; public string Schematic;
[Inspectable, JsonProperty("behaviors"), Key(11)] [Inspectable, JsonProperty("behaviors"), Key(11)]
@ -396,10 +389,10 @@ public abstract class EquippableItemData : CraftedItemData
// [Inspectable, JsonProperty("sfx"), Key(19)] // [Inspectable, JsonProperty("sfx"), Key(19)]
// public string SoundEffectTrigger; // public string SoundEffectTrigger;
[InspectableTexture, JsonProperty("actionIcon"), Key(20)] [Inspectable, CultInspectorAssetPath, JsonProperty("actionIcon"), Key(20)]
public string ActionBarIcon; public string ActionBarIcon;
[InspectableSoundBank, JsonProperty("soundBank"), Key(21)] [Inspectable, JsonProperty("soundBank"), Key(21)]
public uint SoundBank; public uint SoundBank;
[Inspectable, JsonProperty("audioStats"), Key(22)] [Inspectable, JsonProperty("audioStats"), Key(22)]
@ -446,14 +439,14 @@ public abstract class EquippableItemData : CraftedItemData
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AudioStat public class AudioStat
{ {
[InspectableAudioParameter, JsonProperty("parameter"), Key(0)] [Inspectable, JsonProperty("parameter"), Key(0)]
public uint Parameter; public uint Parameter;
[Inspectable, JsonProperty("stat"), Key(1)] [Inspectable, JsonProperty("stat"), Key(1)]
public PerformanceStat Stat = new PerformanceStat(); 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 public class GearData : EquippableItemData
{ {
[Inspectable, JsonProperty("hardpointType"), Key(23)] [Inspectable, JsonProperty("hardpointType"), Key(23)]
@ -462,7 +455,7 @@ public class GearData : EquippableItemData
[IgnoreMember] public override HardpointType HardpointType => Hardpoint; [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 public class CargoBayData : EquippableItemData
{ {
[Inspectable, JsonProperty("interiorShape"), Key(24)] [Inspectable, JsonProperty("interiorShape"), Key(24)]
@ -471,14 +464,14 @@ public class CargoBayData : EquippableItemData
[IgnoreMember] public override HardpointType HardpointType => HardpointType.Tool; [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 public class DockingBayData : CargoBayData
{ {
[Inspectable, JsonProperty("maxSize"), Key(25)] [Inspectable, JsonProperty("maxSize"), Key(25)]
public int2 MaxSize; public int2 MaxSize;
} }
[DatabaseCategory("Items"), MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.weaponitemdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class WeaponItemData : GearData public class WeaponItemData : GearData
{ {
[Inspectable, JsonProperty("range"), Key(24)] [Inspectable, JsonProperty("range"), Key(24)]
@ -497,13 +490,13 @@ public class WeaponItemData : GearData
public WeaponModifiers WeaponModifiers; public WeaponModifiers WeaponModifiers;
} }
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.hulldata", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class HullData : EquippableItemData public class HullData : EquippableItemData
{ {
[Inspectable, JsonProperty("hardpoints"), Key(23)] [Inspectable, JsonProperty("hardpoints"), Key(23)]
public List<HardpointData> Hardpoints = new List<HardpointData>(); public List<HardpointData> Hardpoints = new List<HardpointData>();
[InspectablePrefab, JsonProperty("prefab"), Key(24)] [Inspectable, CultInspectorAssetPath, JsonProperty("prefab"), Key(24)]
public string Prefab; public string Prefab;
[Inspectable, JsonProperty("hullType"), Key(25)] [Inspectable, JsonProperty("hullType"), Key(25)]
@ -541,7 +534,7 @@ public class HullData : EquippableItemData
} }
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class HardpointData : ITintInspector public class HardpointData
{ {
[Inspectable, JsonProperty("type"), Key(0)] public HardpointType Type; [Inspectable, JsonProperty("type"), Key(0)] public HardpointType Type;
[Inspectable, JsonProperty("position"), Key(1)] public int2 Position; [Inspectable, JsonProperty("position"), Key(1)] public int2 Position;
@ -628,10 +621,10 @@ public class PerformanceStat
} }
} }
[DatabaseCategory("Items"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.personalityattribute", "1"), Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class PersonalityAttribute : DatabaseEntry, INamedEntry public class PersonalityAttribute
{ {
[Inspectable, JsonProperty("name"), Key(1)] [Inspectable, CultName, JsonProperty("name"), Key(1)]
public string Name; public string Name;
[Inspectable, JsonProperty("low"), Key(2)] [Inspectable, JsonProperty("low"), Key(2)]
@ -640,9 +633,4 @@ public class PersonalityAttribute : DatabaseEntry, INamedEntry
[Inspectable, JsonProperty("high"), Key(3)] [Inspectable, JsonProperty("high"), Key(3)]
public string HighName; 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JsonKnownTypes; using GameCult.Caching;
//using JM.LinqFaster; //using JM.LinqFaster;
using MessagePack; using MessagePack;
using MessagePack.Formatters; using MessagePack.Formatters;
@ -19,34 +19,28 @@ using static CultMath.math;
Union(1, typeof(CompoundCommodity)), Union(1, typeof(CompoundCommodity)),
Union(2, typeof(EquippableItem)), Union(2, typeof(EquippableItem)),
Union(3, typeof(ConsumableItem)), Union(3, typeof(ConsumableItem)),
JsonObject(MemberSerialization.OptIn), JsonObject(MemberSerialization.OptIn)]
JsonConverter(typeof(JsonKnownTypesConverter<ItemInstance>))]
public abstract class ItemInstance 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; [JsonProperty("rotation"), Key(1)] public ItemRotation Rotation;
} }
[Union(0, typeof(CompoundCommodity)), [Union(0, typeof(CompoundCommodity)),
Union(1, typeof(EquippableItem)), Union(1, typeof(EquippableItem)),
Union(2, typeof(ConsumableItem)), Union(2, typeof(ConsumableItem)),
JsonObject(MemberSerialization.OptIn), JsonObject(MemberSerialization.OptIn)]
JsonConverter(typeof(JsonKnownTypesConverter<CraftedItemInstance>))]
public abstract class CraftedItemInstance : ItemInstance public abstract class CraftedItemInstance : ItemInstance
{ {
// The workmanship of this particular unit, as distinct from the parts that went into it. // The workmanship of this particular unit, as distinct from the parts that went into it.
[JsonProperty("quality"), Key(2)] public float Quality; [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 // 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. // existed; stats then read Quality as they always did.
[JsonProperty("ingredients"), Key(9)] public List<RoleFill> Ingredients = new List<RoleFill>(); [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. // 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 // 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. // 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
@ -6,6 +6,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using GameCult.Caching;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
using float2 = CultMath.float2; using float2 = CultMath.float2;
@ -17,36 +18,12 @@ using float4 = CultMath.float4;
public class ItemManager public class ItemManager
{ {
public Random Random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue)); 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 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 CultCache ItemData { get; }
public GameplaySettings GameplaySettings { 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) public ItemManager(CultCache itemData, GameplaySettings settings, Action<string> logger)
{ {
ItemData = itemData; ItemData = itemData;
@ -59,34 +36,28 @@ public class ItemManager
_logger(s); _logger(s);
} }
public SimpleCommodityData GetData(SimpleCommodity item) // The one resolution path from an item instance to its design
{ public ItemData GetData(ItemInstance item) => ItemData.Get(item.Data);
return item.Data.Value as SimpleCommodityData;
}
public CraftedItemData GetData(CraftedItemInstance item) public SimpleCommodityData GetData(SimpleCommodity item) => GetData((ItemInstance) item) as SimpleCommodityData;
{
return item.Data.Value as CraftedItemData;
}
public EquippableItemData GetData(EquippableItem item) public CraftedItemData GetData(CraftedItemInstance item) => GetData((ItemInstance) item) as CraftedItemData;
{
return item.Data.Value as EquippableItemData; public EquippableItemData GetData(EquippableItem item) => GetData((ItemInstance) item) as EquippableItemData;
}
public float GetMass(ItemInstance item) public float GetMass(ItemInstance item)
{ {
return item switch return item switch
{ {
CraftedItemInstance _ => item.Data.Value.Mass, CraftedItemInstance _ => GetData(item).Mass,
SimpleCommodity commodity => item.Data.Value.Mass * commodity.Quantity, SimpleCommodity commodity => GetData(item).Mass * commodity.Quantity,
_ => 0 _ => 0
}; };
} }
public float GetThermalMass(ItemInstance item) public float GetThermalMass(ItemInstance item)
{ {
var data = item.Data.Value; var data = GetData(item);
return item switch return item switch
{ {
CraftedItemInstance _ => data.Mass * data.SpecificHeat, CraftedItemInstance _ => data.Mass * data.SpecificHeat,
@ -106,7 +77,7 @@ public class ItemManager
pow(item.Quality, GameplaySettings.DurabilityQualityExponent)); pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
var durability = pow(item.Durability / data.Durability, durabilityExponent * stat.DurabilityExponentMultiplier); var durability = pow(item.Durability / data.Durability, durabilityExponent * stat.DurabilityExponentMultiplier);
var result = lerp(stat.Min, stat.Max, quality * durability); 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}"); throw new InvalidOperationException($"Performance Stat on {data.Name} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {data.Durability}");
return result; return result;
@ -124,20 +95,19 @@ public class ItemManager
{ {
var newItem = new SimpleCommodity var newItem = new SimpleCommodity
{ {
Data = new DatabaseLink<ItemData>{LinkID = item.ID}, Data = ItemData.RefOf<ItemData>(item),
Quantity = count Quantity = count
}; };
//ItemData.Add(newItem);
return newItem; return newItem;
} }
_logger("Attempted to create Simple Commodity instance using missing or incorrect item id"); _logger("Attempted to create Simple Commodity instance using missing or incorrect item id");
return null; return null;
} }
public ItemInstance Instantiate(ItemInstance item) public ItemInstance Instantiate(ItemInstance item)
{ {
var data = item.Data.Value; var data = GetData(item);
if(data is CraftedItemData c) if(data is CraftedItemData c)
{ {
var i = CreateInstance(c); var i = CreateInstance(c);
@ -159,24 +129,23 @@ public class ItemManager
{ {
return new EquippableItem 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 var newCommodity = new CompoundCommodity
{ {
Data = new DatabaseLink<ItemData>{LinkID = item.ID}, Data = ItemData.RefOf<ItemData>(item),
Quality = quality Quality = quality
}; };
return newCommodity; return newCommodity;
} }
public CraftedItemInstance CreateInstance(CraftedItemData item) public CraftedItemInstance CreateInstance(CraftedItemData item)
{ {
if (item == null) if (item == null)
{ {
throw new NullReferenceException("Attempted to create crafted item instance using missing or incorrect item data!"); throw new NullReferenceException("Attempted to create crafted item instance using missing or incorrect item data!");
return null;
} }
var quality = Random.NextFloat(); 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. // the design's roles filled with a part whose quality is drawn from that manufacturer's distribution for it.
public CraftedItemInstance CreateInstance(FactionProductData product) public CraftedItemInstance CreateInstance(FactionProductData product)
{ {
var design = ItemData.Get<CraftedItemData>(product.Design); var design = ItemData.Get(product.Design);
if (design == null) if (design == null)
{ {
_logger($"Product {product.Name} names a design that does not exist!"); _logger($"Product {product.Name} names a design that does not exist!");
@ -202,7 +171,7 @@ public class ItemManager
} }
var instance = CreateInstance(design); var instance = CreateInstance(design);
instance.Product = product.ID; instance.Product = ItemData.RefOf(product);
if (design.Roles == null) return instance; if (design.Roles == null) return instance;
foreach (var role in design.Roles) foreach (var role in design.Roles)
{ {
@ -226,4 +195,4 @@ public class ItemManager
int upgrades = (int) ((item.Quality - tier.Quality) / .0499f); int upgrades = (int) ((item.Quality - tier.Quality) / .0499f);
return (tier, upgrades); return (tier, upgrades);
} }
} }

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using GameCult.Caching;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
using Random = CultMath.Random; using Random = CultMath.Random;
@ -57,7 +58,7 @@ public class LoadoutGenerator
return null; return null;
} }
var hull = ItemManager.CreateInstance(hullProduct) as EquippableItem; 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; entity.Faction = Faction;
OutfitEntity(entity); OutfitEntity(entity);
return EntitySerializer.Pack(entity) as OrbitalEntityPack; return EntitySerializer.Pack(entity) as OrbitalEntityPack;
@ -72,7 +73,7 @@ public class LoadoutGenerator
return null; return null;
} }
var hull = ItemManager.CreateInstance(hullProduct) as EquippableItem; 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; entity.Faction = Faction;
// Hardpoint gear goes in first so the docking bay can only take space that gear left free // 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 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>() 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 => .Where(entry =>
entry.design != null && entry.design != null &&
entry.design.Price > 0 && entry.design.Price > 0 &&
entry.product.Manufacturer != Guid.Empty && entry.product.Manufacturer.IsSet() &&
(filter?.Invoke(entry.design) ?? true)) (filter?.Invoke(entry.design) ?? true))
.ToArray(); .ToArray();
var available = candidates.Where(entry => IsAvailable(entry.product)).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)); 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 // 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; 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; 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; var distance = home != null && Zone?.Distance != null && Zone.Distance.TryGetValue(home, out var d) ? d : 0;
return allegiance / (1 + distance); return allegiance / (1 + distance);
} }

View file

@ -15,12 +15,12 @@ public interface IFactionResolver
public abstract class ZoneConstraint public abstract class ZoneConstraint
{ {
public bool Flip = false; public bool Flip = false;
public bool Test(GalaxyZone zone) public bool Test(GalaxyZone zone)
{ {
return Flip ^ TestZone(zone); return Flip ^ TestZone(zone);
} }
protected abstract bool TestZone(GalaxyZone zone); protected abstract bool TestZone(GalaxyZone zone);
} }
@ -34,14 +34,14 @@ public class FactionPresenceConstraint : ZoneConstraint
protected override bool TestZone(GalaxyZone zone) 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 public class FactionOwnerConstraint : ZoneConstraint
{ {
private Faction TargetFaction { get; } private Faction TargetFaction { get; }
public FactionOwnerConstraint(string[] args, IFactionResolver resolver) public FactionOwnerConstraint(string[] args, IFactionResolver resolver)
{ {
TargetFaction = resolver.ResolveFaction(args[0]); TargetFaction = resolver.ResolveFaction(args[0]);
@ -49,7 +49,7 @@ public class FactionOwnerConstraint : ZoneConstraint
protected override bool TestZone(GalaxyZone zone) 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 GalaxyZone _targetZone;
private Predicate<int> _test; private Predicate<int> _test;
public DistanceConstraint(string[] args, IZoneResolver zoneResolver) public DistanceConstraint(string[] args, IZoneResolver zoneResolver)
{ {
if (args.Length == 3) if (args.Length == 3)
@ -86,4 +86,4 @@ public class DistanceConstraint : ZoneConstraint
{ {
return _targetZone != null && _test(zone.Distance[_targetZone]); 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using GameCult.Caching;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
using float2 = CultMath.float2; using float2 = CultMath.float2;
public class OrbitalEntity : Entity public class OrbitalEntity : Entity
{ {
public Guid OrbitData; public CultRecordKey OrbitData;
public SecurityLevel SecurityLevel; public SecurityLevel SecurityLevel;
public float SecurityRadius; public float SecurityRadius;
public LocationStory Story; public LocationStory Story;
public bool CanTow; 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; OrbitData = orbit;
} }
public override void Update(float delta) public override void Update(float delta)
{ {
if (OrbitData != Guid.Empty) if (OrbitData.IsSet())
{ {
Position.xz = Zone.GetOrbitPosition(OrbitData); Position.xz = Zone.GetOrbitPosition(OrbitData);
Velocity = Zone.GetOrbitVelocity(OrbitData); Velocity = Zone.GetOrbitVelocity(OrbitData);
} }
base.Update(delta); base.Update(delta);
} }

View file

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

View file

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

View file

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GameCult.Caching;
using UniRx; using UniRx;
using CultMath; using CultMath;
using static CultMath.math; using static CultMath.math;
@ -17,17 +18,15 @@ using Random = CultMath.Random;
public class Zone public class Zone
{ {
public Action<string> Log; public Action<string> Log;
//public HashSet<Guid> Planets = new HashSet<Guid>();
public ReactiveCollection<Entity> Entities = new ReactiveCollection<Entity>(); public ReactiveCollection<Entity> Entities = new ReactiveCollection<Entity>();
//public Dictionary<Guid, OrbitData> Orbits = new Dictionary<Guid, OrbitData>(); public Dictionary<CultRecordKey, BodyData> Planets = new Dictionary<CultRecordKey, BodyData>();
public Dictionary<Guid, BodyData> Planets = new Dictionary<Guid, BodyData>(); public Dictionary<CultRecordKey, Planet> PlanetInstances = new Dictionary<CultRecordKey, Planet>();
public Dictionary<Guid, Planet> PlanetInstances = new Dictionary<Guid, Planet>();
public Dictionary<Guid, Orbit> Orbits = new Dictionary<Guid, Orbit>(); public Dictionary<CultRecordKey, Orbit> Orbits = new Dictionary<CultRecordKey, Orbit>();
public Dictionary<Guid, AsteroidBelt> AsteroidBelts = new Dictionary<Guid, AsteroidBelt>(); public Dictionary<CultRecordKey, AsteroidBelt> AsteroidBelts = new Dictionary<CultRecordKey, AsteroidBelt>();
public PlanetSettings Settings; public PlanetSettings Settings;
private HashSet<Guid> _updatedOrbits = new HashSet<Guid>(); private HashSet<CultRecordKey> _updatedOrbits = new HashSet<CultRecordKey>();
private ItemManager _itemManager; private ItemManager _itemManager;
private double _time; private double _time;
@ -35,7 +34,7 @@ public class Zone
public List<Agent> Agents = new List<Agent>(); public List<Agent> Agents = new List<Agent>();
private List<Task> BeltUpdates = new List<Task>(); private List<Task> BeltUpdates = new List<Task>();
public float Time public float Time
{ {
get => (float) _time; get => (float) _time;
@ -53,28 +52,30 @@ public class Zone
_itemManager = itemManager; _itemManager = itemManager;
Settings = settings; Settings = settings;
_random = new Random(Convert.ToUInt32(abs(galaxyZone?.Name.GetHashCode() ?? 1337))); _random = new Random(Convert.ToUInt32(abs(galaxyZone?.Name.GetHashCode() ?? 1337)));
var cache = itemManager.ItemData;
foreach (var orbit in pack.Orbits) 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) switch (planet)
{ {
case AsteroidBeltData belt: case AsteroidBeltData belt:
AsteroidBelts[belt.ID] = new AsteroidBelt(belt); AsteroidBelts[body.Key] = new AsteroidBelt(belt);
break; break;
case SunData sun: 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; break;
case GasGiantData gas: 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; break;
default: 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; break;
} }
} }
@ -111,15 +112,15 @@ public class Zone
Radius = Pack.Radius, Radius = Pack.Radius,
Mass = Pack.Mass, Mass = Pack.Mass,
Entities = Entities.Select(EntitySerializer.Pack).ToList(), Entities = Entities.Select(EntitySerializer.Pack).ToList(),
Orbits = Orbits.Values.Select(o=>o.Data).ToList(), Orbits = Orbits.Keys.Select(key => new CultRecordRef<OrbitData>(key)).ToList(),
Planets = Planets.Values.ToList(), Planets = Planets.Keys.Select(key => new CultRecordRef<BodyData>(key)).ToList(),
Time = _time Time = _time
}; };
} }
public void AddOrbit(OrbitData orbit) 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) public void Update(float deltaTime)
@ -142,25 +143,25 @@ public class Zone
belt.Value.OrbitPosition = belt.Value.NewOrbitPosition; belt.Value.OrbitPosition = belt.Value.NewOrbitPosition;
BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key))); BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));
} }
foreach(var agent in Agents) foreach(var agent in Agents)
agent.Update(deltaTime); agent.Update(deltaTime);
foreach (var entity in Entities.ToArray()) entity.Update(deltaTime); foreach (var entity in Entities.ToArray()) entity.Update(deltaTime);
} }
// Determine orbital position recursively, caching parent positions to avoid repeated calculations // 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 // Root orbit is fixed at origin
if(orbitID==Guid.Empty) if(!orbitID.IsSet())
return float2.zero; return float2.zero;
if (!Orbits.ContainsKey(orbitID)) if (!Orbits.ContainsKey(orbitID))
{ {
Log?.Invoke("Requested orbit is not part of this zone!"); Log?.Invoke("Requested orbit is not part of this zone!");
return float2.zero; return float2.zero;
} }
if (!_updatedOrbits.Contains(orbitID)) if (!_updatedOrbits.Contains(orbitID))
{ {
var orbit = Orbits[orbitID]; var orbit = Orbits[orbitID];
@ -169,7 +170,7 @@ public class Zone
{ {
var phase = (float) frac(_time / orbit.Period); var phase = (float) frac(_time / orbit.Period);
pos = OrbitData.Evaluate(frac(phase + orbit.Data.Phase)) * orbit.Data.Distance; pos = OrbitData.Evaluate(frac(phase + orbit.Data.Phase)) * orbit.Data.Distance;
if (float.IsNaN(pos.x)) if (float.IsNaN(pos.x))
{ {
//_context.Log("Orbit position is NaN, something went very wrong!"); //_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 var parentPosition = !Orbits[orbitID].Data.Parent.IsSet()
? Orbits[orbitID].Data.FixedPosition : ? Orbits[orbitID].Data.FixedPosition :
GetOrbitPosition(orbit.Data.Parent); GetOrbitPosition(orbit.Data.Parent.Key);
Orbits[orbitID].Position = parentPosition + pos; Orbits[orbitID].Position = parentPosition + pos;
_updatedOrbits.Add(orbitID); _updatedOrbits.Add(orbitID);
} }
@ -187,14 +188,14 @@ public class Zone
return Orbits[orbitID].Position; return Orbits[orbitID].Position;
} }
public float2 GetOrbitVelocity(Guid orbit) public float2 GetOrbitVelocity(CultRecordKey orbit)
{ {
if (Orbits.ContainsKey(orbit)) if (Orbits.ContainsKey(orbit))
return Orbits[orbit].Velocity; return Orbits[orbit].Velocity;
return float2.zero; return float2.zero;
} }
public int NearestAsteroid(Guid planetDataID, float2 position) public int NearestAsteroid(CultRecordKey planetDataID, float2 position)
{ {
var beltData = Planets[planetDataID] as AsteroidBeltData; var beltData = Planets[planetDataID] as AsteroidBeltData;
@ -215,16 +216,16 @@ public class Zone
return nearest; 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 beltData = Planets[planetDataID] as AsteroidBeltData;
var belt = AsteroidBelts[planetDataID]; var belt = AsteroidBelts[planetDataID];
var orbitData = Orbits[beltData.Orbit].Data; var orbitData = Orbits[beltData.Orbit.Key].Data;
belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent); belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent.Key);
for (var i = 0; i < beltData.Asteroids.Length; i++) for (var i = 0; i < beltData.Asteroids.Length; i++)
{ {
float size; 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 parentPosition = GetOrbitPosition(parent);
var delta = position - parentPosition; var delta = position - parentPosition;
@ -257,16 +258,15 @@ public class Zone
var orbit = new OrbitData var orbit = new OrbitData
{ {
ID = Guid.NewGuid(),
Distance = distance, Distance = distance,
Parent = parent, Parent = new CultRecordRef<OrbitData>(parent),
Phase = storedPhase Phase = storedPhase
}; };
Orbits.Add(orbit.ID, new Orbit(Settings, orbit)); Orbits.Add(_itemManager.ItemData.Upsert(orbit).Key, new Orbit(Settings, orbit));
return 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 beltData = Planets[asteroidBelt] as AsteroidBeltData;
var belt = AsteroidBelts[asteroidBelt]; var belt = AsteroidBelts[asteroidBelt];
@ -274,15 +274,15 @@ public class Zone
var size = beltData.Asteroids[asteroid].Size; var size = beltData.Asteroids[asteroid].Size;
var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(size); var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(size);
if (!belt.Damage.ContainsKey(asteroid)) if (!belt.Damage.ContainsKey(asteroid))
belt.Damage[asteroid] = 0; belt.Damage[asteroid] = 0;
belt.Damage[asteroid] = belt.Damage[asteroid] + damage; belt.Damage[asteroid] = belt.Damage[asteroid] + damage;
if (!belt.MiningAccumulator.ContainsKey((miner, asteroid))) if (!belt.MiningAccumulator.ContainsKey((miner, asteroid)))
belt.MiningAccumulator[(miner, asteroid)] = 0; belt.MiningAccumulator[(miner, asteroid)] = 0;
belt.MiningAccumulator[(miner, asteroid)] = belt.MiningAccumulator[(miner, asteroid)] + damage; belt.MiningAccumulator[(miner, asteroid)] = belt.MiningAccumulator[(miner, asteroid)] + damage;
if (belt.Damage[asteroid] > asteroidHitpoints) if (belt.Damage[asteroid] > asteroidHitpoints)
{ {
belt.RespawnTimers[asteroid] = Settings.AsteroidRespawnTime.Evaluate(size); belt.RespawnTimers[asteroid] = Settings.AsteroidRespawnTime.Evaluate(size);
@ -309,11 +309,11 @@ public class Zone
public SecurityLevel GetSecurityLevel(float2 pos) public SecurityLevel GetSecurityLevel(float2 pos)
{ {
if (GalaxyZone.Owner==null) return SecurityLevel.Open; if (GalaxyZone.Owner==null) return SecurityLevel.Open;
var security = SecurityLevel.Open; var security = SecurityLevel.Open;
foreach (var entity in Entities) 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) if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
security = orbitalEntity.SecurityLevel; security = orbitalEntity.SecurityLevel;
@ -322,7 +322,7 @@ public class Zone
return security; return security;
} }
public float GetHeight(float2 position) public float GetHeight(float2 position)
{ {
var result = -PowerPulse(length(position)/(Pack.Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth; 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using GameCult.Caching;
using MessagePack; using MessagePack;
using Newtonsoft.Json; using Newtonsoft.Json;
using UniRx; using UniRx;
@ -17,14 +18,14 @@ using float3 = CultMath.float3;
public class ZonePack public class ZonePack
{ {
[JsonProperty("planets"), Key(0)] [JsonProperty("planets"), Key(0)]
public List<BodyData> Planets = new List<BodyData>(); public List<CultRecordRef<BodyData>> Planets = new List<CultRecordRef<BodyData>>();
[JsonProperty("orbits"), Key(1)] [JsonProperty("orbits"), Key(1)]
public List<OrbitData> Orbits = new List<OrbitData>(); public List<CultRecordRef<OrbitData>> Orbits = new List<CultRecordRef<OrbitData>>();
[JsonProperty("entities"), Key(2)] [JsonProperty("entities"), Key(2)]
public List<EntityPack> Entities = new List<EntityPack>(); public List<EntityPack> Entities = new List<EntityPack>();
[JsonProperty("radius"), Key(3)] [JsonProperty("radius"), Key(3)]
public float Radius = 2000; public float Radius = 2000;
@ -33,29 +34,24 @@ public class ZonePack
[JsonProperty("time"), Key(5)] [JsonProperty("time"), Key(5)]
public double Time; public double Time;
} }
[MessagePackObject, [JsonObject(MemberSerialization.OptIn)]
JsonObject(MemberSerialization.OptIn), public abstract class BodyData
Union(0, typeof(PlanetData)),
Union(1, typeof(AsteroidBeltData)),
Union(2, typeof(GasGiantData)),
Union(3, typeof(SunData))]
public abstract class BodyData : DatabaseEntry, INamedEntry
{ {
[JsonProperty("name"), Key(1)] [CultName, JsonProperty("name"), Key(1)]
public string Name = ""; public string Name = "";
[JsonProperty("orbit"), Key(2)] [JsonProperty("orbit"), Key(2)]
public Guid Orbit; public CultRecordRef<OrbitData> Orbit;
[JsonProperty("mass"), Key(3)] [JsonProperty("mass"), Key(3)]
public float Mass = 0; public float Mass = 0;
[JsonProperty("resources"), Key(4)] [CultReference(typeof(SimpleCommodityData), many: true), JsonProperty("resources"), Key(4)]
public Dictionary<Guid, float> Resources = new Dictionary<Guid, float>(); public Dictionary<CultRecordRef<SimpleCommodityData>, float> Resources = new Dictionary<CultRecordRef<SimpleCommodityData>, float>();
[JsonProperty("bodyRadiusMul")] [Key(5)] [JsonProperty("bodyRadiusMul")] [Key(5)]
public float BodyRadiusMultiplier = 1; public float BodyRadiusMultiplier = 1;
@ -68,42 +64,36 @@ public abstract class BodyData : DatabaseEntry, INamedEntry
[JsonProperty("depthExp")] [Key(8)] [JsonProperty("depthExp")] [Key(8)]
public float GravityDepthExponent = 16; 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 public class PlanetData : BodyData
{ {
} }
[MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.asteroidbeltdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AsteroidBeltData : BodyData public class AsteroidBeltData : BodyData
{ {
[JsonProperty("asteroids"), Key(9)] [JsonProperty("asteroids"), Key(9)]
public Asteroid[] Asteroids; public Asteroid[] Asteroids;
} }
[MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.gasgiantdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class GasGiantData : BodyData public class GasGiantData : BodyData
{ {
[JsonProperty("firstOffsetDomainRotationSpeed"), Key(9)] [JsonProperty("firstOffsetDomainRotationSpeed"), Key(9)]
public float FirstOffsetDomainRotationSpeed = 1; public float FirstOffsetDomainRotationSpeed = 1;
[JsonProperty("firstOffsetRotationSpeed"), Key(10)] [JsonProperty("firstOffsetRotationSpeed"), Key(10)]
public float FirstOffsetRotationSpeed = 1; public float FirstOffsetRotationSpeed = 1;
[JsonProperty("secondOffsetDomainRotationSpeed"), Key(11)] [JsonProperty("secondOffsetDomainRotationSpeed"), Key(11)]
public float SecondOffsetDomainRotationSpeed = 1; public float SecondOffsetDomainRotationSpeed = 1;
[JsonProperty("secondOffsetRotationSpeed"), Key(12)] [JsonProperty("secondOffsetRotationSpeed"), Key(12)]
public float SecondOffsetRotationSpeed = 1; public float SecondOffsetRotationSpeed = 1;
[JsonProperty("albedoRotationSpeed"), Key(13)] [JsonProperty("albedoRotationSpeed"), Key(13)]
public float AlbedoRotationSpeed = 1; public float AlbedoRotationSpeed = 1;
@ -118,19 +108,20 @@ public class GasGiantData : BodyData
[JsonProperty("depthExp")] [Key(17)] [JsonProperty("depthExp")] [Key(17)]
public float WaveSpeedMultiplier = 8; public float WaveSpeedMultiplier = 8;
[JsonProperty("materialOverrides"), Key(18)] [JsonProperty("materialOverrides"), Key(18)]
public List<string> MaterialOverrides = new List<string>(); public List<string> MaterialOverrides = new List<string>();
[JsonProperty("colors"), Key(19)] [JsonProperty("colors"), Key(19)]
public float4[] Colors = new float4[0]; public float4[] Colors = new float4[0];
} }
[CultDocument("aetheria.sundata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class SunData : GasGiantData public class SunData : GasGiantData
{ {
[JsonProperty("lightColor"), Key(20)] [JsonProperty("lightColor"), Key(20)]
public float3 LightColor = float3.zero; public float3 LightColor = float3.zero;
[JsonProperty("fogTintColor"), Key(21)] [JsonProperty("fogTintColor"), Key(21)]
public float3 FogTintColor = float3.zero; public float3 FogTintColor = float3.zero;
@ -143,32 +134,32 @@ public class Asteroid
{ {
[JsonProperty("distance"), Key(0)] [JsonProperty("distance"), Key(0)]
public float Distance; public float Distance;
[JsonProperty("phase"), Key(1)] [JsonProperty("phase"), Key(1)]
public float Phase; public float Phase;
[JsonProperty("size"), Key(2)] [JsonProperty("size"), Key(2)]
public float Size; public float Size;
[JsonProperty("rotationSpeed"), Key(3)] [JsonProperty("rotationSpeed"), Key(3)]
public float RotationSpeed; public float RotationSpeed;
} }
[MessagePackObject, JsonObject(MemberSerialization.OptIn)] [CultDocument("aetheria.orbitdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class OrbitData : DatabaseEntry public class OrbitData
{ {
[JsonProperty("parent"), Key(1)] [JsonProperty("parent"), Key(1)]
public Guid Parent; public CultRecordRef<OrbitData> Parent;
[JsonProperty("distance"), Key(2)] [JsonProperty("distance"), Key(2)]
public float Distance; public float Distance;
[JsonProperty("phase"), Key(3)] [JsonProperty("phase"), Key(3)]
public float Phase; public float Phase;
[JsonProperty("phase"), Key(4)] [JsonProperty("phase"), Key(4)]
public float2 FixedPosition = float2.zero; public float2 FixedPosition = float2.zero;
// [JsonProperty("period"), Key(4)] // [JsonProperty("period"), Key(4)]
// public float Period; // public float Period;
@ -177,4 +168,4 @@ public class OrbitData : DatabaseEntry
phase *= PI * 2; phase *= PI * 2;
return new float2(cos(phase), sin(phase)); 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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
@ -43,6 +43,7 @@ public static class ZoneGenerator
bool isTutorial = false) bool isTutorial = false)
{ {
var pack = new ZonePack(); var pack = new ZonePack();
var cache = itemManager.ItemData;
var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ (uint) pcg3d(galaxyZone.Position).x); 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.Radius = zoneSettings.ZoneRadius.Evaluate(density);
pack.Mass = zoneSettings.ZoneMass.Evaluate(density); pack.Mass = zoneSettings.ZoneMass.Evaluate(density);
var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density); var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density);
//Debug.Log($"Generating zone at position {zone.Position} with radius {zoneRadius} and mass {zoneMass}"); //Debug.Log($"Generating zone at position {zone.Position} with radius {zoneRadius} and mass {zoneMass}");
var planets = new List<GeneratorPlanet>(); var planets = new List<GeneratorPlanet>();
@ -58,10 +59,10 @@ public static class ZoneGenerator
{ {
var zoneBoundary = new Circle(float2.zero, pack.Radius * zoneSettings.ZoneBoundaryRadius); var zoneBoundary = new Circle(float2.zero, pack.Radius * zoneSettings.ZoneBoundaryRadius);
float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point); float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point);
var occupiedAreas = new List<Circle>(); var occupiedAreas = new List<Circle>();
float tangentRadius(float2 point) => min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point))); 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(); var startPosition = random.NextFloat(pack.Radius * .25f, pack.Radius * .5f) * random.NextFloat2Direction();
occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition))); occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition)));
@ -91,52 +92,29 @@ public static class ZoneGenerator
} }
else else
planets.AddRange(GenerateEntities(zoneSettings, ref random, pack.Mass, pack.Radius, float2.zero)); 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 orbitMap = new Dictionary<GeneratorPlanet, OrbitData>();
var orbitInverseMap = new Dictionary<OrbitData, GeneratorPlanet>(); foreach (var planet in planets)
cache.Upsert(orbitMap[planet] = new OrbitData
// Create orbit database entries
pack.Orbits = planets.Select(planet =>
{
var data = new OrbitData
{ {
FixedPosition = planet.FixedPosition, FixedPosition = planet.FixedPosition,
Distance = planet.Distance, Distance = planet.Distance,
//Period = planet.Period,
Phase = planet.Phase Phase = planet.Phase
}; });
orbitMap[planet] = data; foreach (var planet in planets.Where(p => p.Parent != null))
orbitInverseMap[data] = planet; {
return data; orbitMap[planet].Parent = cache.RefOf(orbitMap[planet.Parent]);
}).ToList(); 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 => pack.Planets = planets.Where(p=>!p.Empty).Select(planet =>
{ {
// Dictionary<Guid, float> planetResources = new Dictionary<Guid, float>();
BodyType bodyType = planet.Belt ? BodyType.Asteroid : BodyType bodyType = planet.Belt ? BodyType.Asteroid :
planet.Mass > zoneSettings.SunMass ? BodyType.Sun : planet.Mass > zoneSettings.SunMass ? BodyType.Sun :
planet.Mass > zoneSettings.GasGiantMass ? BodyType.GasGiant : planet.Mass > zoneSettings.GasGiantMass ? BodyType.GasGiant :
planet.Mass > zoneSettings.PlanetMass ? BodyType.Planet : BodyType.Planetoid; 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; BodyData planetData;
switch (bodyType) switch (bodyType)
@ -159,12 +137,10 @@ public static class ZoneGenerator
} }
planetData.Mass = planet.Mass; planetData.Mass = planet.Mass;
planetData.Orbit = orbitMap[planet].ID; planetData.Orbit = cache.RefOf(orbitMap[planet]);
// planetData.Resources = planetResources;
planetData.Name = planetData.ID.ToString().Substring(0, 8);
if (planetData is AsteroidBeltData beltData) if (planetData is AsteroidBeltData beltData)
{ {
beltData.Asteroids = beltData.Asteroids =
Enumerable.Range(0, (int) zoneSettings.AsteroidCount.Evaluate(beltData.Mass * orbitMap[planet].Distance)) Enumerable.Range(0, (int) zoneSettings.AsteroidCount.Evaluate(beltData.Mass * orbitMap[planet].Distance))
.Select(_ => new Asteroid .Select(_ => new Asteroid
{ {
@ -197,12 +173,12 @@ public static class ZoneGenerator
float primary = random.NextFloat(); float primary = random.NextFloat();
float right = frac(primary + zoneSettings.GasGiantBandColorSeparation); float right = frac(primary + zoneSettings.GasGiantBandColorSeparation);
float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation); float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation);
// Create n time keys from 0 to 1 // Create n time keys from 0 to 1
var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f); var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f);
var times = Enumerable.Range(0, bandCount) var times = Enumerable.Range(0, bandCount)
.Select(i => (float) i / (bandCount-1)); .Select(i => (float) i / (bandCount-1));
// Each band has a chance of being either the primary or one of the adjacent hues // Each band has a chance of being either the primary or one of the adjacent hues
// Saturation and Value are random with curves applied // Saturation and Value are random with curves applied
gas.Colors = times gas.Colors = times
@ -211,7 +187,7 @@ public static class ZoneGenerator
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()), zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()),
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time)) zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time))
.ToArray(); .ToArray();
gas.FirstOffsetDomainRotationSpeed = 0; gas.FirstOffsetDomainRotationSpeed = 0;
} }
gas.AlbedoRotationSpeed = -3; gas.AlbedoRotationSpeed = -3;
@ -219,7 +195,10 @@ public static class ZoneGenerator
gas.SecondOffsetRotationSpeed = 10; gas.SecondOffsetRotationSpeed = 10;
gas.SecondOffsetDomainRotationSpeed = -25; 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(); }).ToList();
var nearestFaction = galaxy.Factions.MinBy(f => galaxy.HomeZones[f].Distance[galaxyZone]); var nearestFaction = galaxy.Factions.MinBy(f => galaxy.HomeZones[f].Distance[galaxyZone]);
@ -253,30 +232,28 @@ public static class ZoneGenerator
{ {
var lagrangeOrbit = new OrbitData var lagrangeOrbit = new OrbitData
{ {
ID = Guid.NewGuid(),
Parent = baseOrbit.Parent, Parent = baseOrbit.Parent,
Distance = baseOrbit.Distance, Distance = baseOrbit.Distance,
Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f) Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
}; };
pack.Orbits.Add(lagrangeOrbit); pack.Orbits.Add(cache.Upsert(lagrangeOrbit));
return lagrangeOrbit; return lagrangeOrbit;
} }
void PlaceTurret(OrbitData orbit, LoadoutGenerator loadoutGenerator, int distanceMultiplier) void PlaceTurret(OrbitData orbit, LoadoutGenerator loadoutGenerator, int distanceMultiplier)
{ {
var phase = 20f * distanceMultiplier / orbit.Distance; var phase = 20f * distanceMultiplier / orbit.Distance;
var turretOrbit = new OrbitData var turretOrbit = new OrbitData
{ {
ID = Guid.NewGuid(),
Parent = orbit.Parent, Parent = orbit.Parent,
Distance = orbit.Distance, Distance = orbit.Distance,
Phase = orbit.Phase + phase Phase = orbit.Phase + phase
}; };
pack.Orbits.Add(turretOrbit); pack.Orbits.Add(cache.Upsert(turretOrbit));
var turret = loadoutGenerator.GenerateTurretLoadout(); var turret = loadoutGenerator.GenerateTurretLoadout();
if (turret == null) return; if (turret == null) return;
turret.Orbit = turretOrbit.ID; turret.Orbit = cache.RefOf(turretOrbit);
pack.Entities.Add(turret); 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!"); itemManager.Log($"Story station for {story.Faction?.Name} could not be generated; its story has nowhere to live!");
continue; continue;
} }
station.Orbit = lagrangeOrbit.ID; station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = story.Security; station.SecurityLevel = story.Security;
station.SecurityRadius = pack.Radius; station.SecurityRadius = pack.Radius;
station.Story = i; station.Story = i;
@ -318,7 +295,7 @@ public static class ZoneGenerator
var lagrangeOrbit = CreateLagrangeOrbit(orbit); var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout(); var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout();
if (station == null) continue; if (station == null) continue;
station.Orbit = lagrangeOrbit.ID; station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = security; station.SecurityLevel = security;
station.SecurityRadius = pack.Radius; station.SecurityRadius = pack.Radius;
pack.Entities.Add(station); pack.Entities.Add(station);
@ -336,7 +313,7 @@ public static class ZoneGenerator
return pack; return pack;
} }
// static float ResourceValue(ref Random random, ZoneGenerationSettings settings, SimpleCommodityData resource, float density) // static float ResourceValue(ref Random random, ZoneGenerationSettings settings, SimpleCommodityData resource, float density)
// { // {
// return random.NextPowerDistribution(resource.Minimum, resource.Maximum, resource.Exponent, // 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 // 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 // Probabilities which are fixed for the entire galaxy are in GlobalData, contained in the GameContext
var rosette = random.NextFloat() < settings.RosetteProbability; var rosette = random.NextFloat() < settings.RosetteProbability;
if (rosette) if (rosette)
{ {
// Create a rosette with a number of vertices between 2 and 9 inclusive // 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))); 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 // Create a small number of less massive "captured" planets orbiting past the rosette
root.ExpandSolar( root.ExpandSolar(
ref random, ref random,
count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)), count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)),
massMulMin: .6f, massMulMin: .6f,
massMulMax: .8f, massMulMax: .8f,
distMulMin: 1.25f, distMulMin: 1.25f,
distMulMax: 1.75f, distMulMax: 1.75f,
jupiterJump: 1, jupiterJump: 1,
massFraction: .1f); massFraction: .1f);
@ -381,9 +358,9 @@ public static class ZoneGenerator
// Give each child in the rosette its own mini solar system // Give each child in the rosette its own mini solar system
p.ExpandSolar( p.ExpandSolar(
ref random, ref random,
count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)), count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)),
massMulMin: 0.75f, massMulMin: 0.75f,
massMulMax: 2.5f, massMulMax: 2.5f,
distMulMin: 1 + m * .25f, distMulMin: 1 + m * .25f,
distMulMax: 1.05f + m * .5f, distMulMax: 1.05f + m * .5f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1, jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1,
@ -396,9 +373,9 @@ public static class ZoneGenerator
// Create a regular old boring solar system // Create a regular old boring solar system
root.ExpandSolar( root.ExpandSolar(
ref random, ref random,
count: random.NextInt(5, 15), count: random.NextInt(5, 15),
massMulMin: 0.75f, massMulMin: 0.75f,
massMulMax: 2.5f, massMulMax: 2.5f,
distMulMin: 1.1f, distMulMin: 1.1f,
distMulMax: 1.25f, distMulMax: 1.25f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1, 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 // Get all children that are above the satellite creation mass floor and not rosette members
var satelliteCandidates = rosette var satelliteCandidates = rosette
? root.AllPlanets().Where(p => ? root.AllPlanets().Where(p =>
p != root && p != root &&
p.Parent != root && p.Parent != root &&
p.Mass > settings.SatelliteCreationMassFloor && p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p)) !alreadyExpanded.Contains(p))
: root.AllPlanets().Where(p => : root.AllPlanets().Where(p =>
p != root && p != root &&
p.Mass > settings.SatelliteCreationMassFloor && p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p)); !alreadyExpanded.Contains(p));
@ -455,7 +432,7 @@ public static class ZoneGenerator
var beltCandidates = rosette 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.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); : root.AllPlanets().Where(p => p != root && p.Mass < settings.BeltMassCeiling && !binaries.Contains(p) && p.Children.Count == 0);
foreach(var planet in beltCandidates.Reverse()) foreach(var planet in beltCandidates.Reverse())
if (random.NextFloat() < settings.BeltProbability && !planet.Parent.Children.Any(p=>p.Belt)) if (random.NextFloat() < settings.BeltProbability && !planet.Parent.Children.Any(p=>p.Belt))
planet.Belt = true; planet.Belt = true;
@ -498,29 +475,29 @@ public class GeneratorPlanet
public void ExpandRosette(ref Random random, int vertices) public void ExpandRosette(ref Random random, int vertices)
{ {
//Debug.Log("Expanding Rosette"); //Debug.Log("Expanding Rosette");
// Rosette children replace the parent, parent orbital node is left empty // Rosette children replace the parent, parent orbital node is left empty
Empty = true; Empty = true;
// Masses in a rosette alternate, so every sequential pair has the same shared mass // Masses in a rosette alternate, so every sequential pair has the same shared mass
var sharedMass = Mass / vertices * 2; var sharedMass = Mass / vertices * 2;
// Proportion of the masses of sequential pairs is random, but only for even vertex counts // 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; var proportion = vertices % 2 == 0 ? random.NextFloat(.5f,.95f) : .5f;
// Place children at a fixed distance in the center of the range // Place children at a fixed distance in the center of the range
var dist = (ChildDistanceMinimum + ChildDistanceMaximum) / 2; var dist = (ChildDistanceMinimum + ChildDistanceMaximum) / 2;
// Position of first child // Position of first child
var p0 = new float2(0, dist); var p0 = new float2(0, dist);
// Position of second child // Position of second child
var p1 = OrbitData.Evaluate(1.0f / vertices) * dist; var p1 = OrbitData.Evaluate(1.0f / vertices) * dist;
// Maximum child distance is half the distance to the neighbor minus the neighbor's radius // 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 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; var p1ChildDist = (distance(p0, p1) * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f;
for (int i = 0; i < vertices; i++) for (int i = 0; i < vertices; i++)
{ {
var child = new GeneratorPlanet var child = new GeneratorPlanet
@ -547,14 +524,14 @@ public class GeneratorPlanet
// Expansion is impossible when child space is full // Expansion is impossible when child space is full
if (count == 0 || ChildDistanceMaximum < ChildDistanceMinimum) if (count == 0 || ChildDistanceMaximum < ChildDistanceMinimum)
return; return;
var masses = new float[count]; var masses = new float[count];
var distances = 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 // Initialize first mass and distance, actual number doesn't matter since total mass will be divided proportionally
float massTotal = distances[0] = masses[0] = 1; float massTotal = distances[0] = masses[0] = 1;
// Masses and distances multiply from one planet to the next // Masses and distances multiply from one planet to the next
// Mass typically increases exponentially as you go further out // Mass typically increases exponentially as you go further out
// jupiterJump is an additional mass multiplier applied after half of the planets // jupiterJump is an additional mass multiplier applied after half of the planets
@ -580,7 +557,7 @@ public class GeneratorPlanet
distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum, distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum,
(oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]); (oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]);
} }
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
// Only instantiate children above the mass floor // 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'"> 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')" <Error Condition="!Exists('$(CultLibRoot)\packages\cultmath\src\CultMath\CultMath.csproj')"
Text="CultLibRoot '$(CultLibRoot)' does not contain the required CultMath project." /> 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" <Exec Command="&quot;$(GitExecutable)&quot; -C &quot;$(CultLibRoot)&quot; rev-parse HEAD"
ConsoleToMSBuild="true" ConsoleToMSBuild="true"
StandardOutputImportance="Low"> StandardOutputImportance="Low">