diff --git a/README.md b/README.md index a399a72..6fd42f7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,46 @@ # Subsystem Subsystem is a mod loader for _Homeworld: Deserts of Kharak_. It aims to have a safe, auditable and data-oriented design. + +## Disclaimer + +Subsystem is currently in alpha. It may be extremely unstable, and it's not likely to provide useful debugging information. Use at your own risk, and please try to dig into logs on your own before reporting crashes or other odd behavior. + +## Installation + +Place `Subsystem.dll` and `BBI.Unity.Game.dll` in the `Deserts of Kharak/Data/Managed/` folder. + +`BBI.Unity.Game.dll` already exists and should be overwritten. You can make a backup of this file if you'd like. + +## Usage + +Create a JSON file like this: + +```json +{ + "Entities": { + "G_Baserunner_MP": { + "UnitAttributes": { + "MaxHealth": 3500, + "Resource1Cost": 225, + "ProductionTime": 17.5 + } + }, + "G_Carrier_MP": { + "UnitAttributes": { + "Armour": 15 + } + } + } +} +``` + +Save the file and place it at `Deserts of Kharak/Data/patch.json`. The filename and location must match exactly. + +Note that the keys (`Entities`, `UnitAttributes`, etc) are case-sensitive. + +The stats are reloaded at the beginning of every game, + +## Multiplayer Notes + +To use this in multiplayer, all players in the game **must** have the same version of Subsystem *and* the same `patch.json` file contents. If any player has different data, the game is liable to crash with a synchronization error. diff --git a/Subsystem.sln b/Subsystem.sln new file mode 100644 index 0000000..7243e7d --- /dev/null +++ b/Subsystem.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Subsystem", "Subsystem\Subsystem.csproj", "{4A4A0500-7372-4497-981B-00E1FD8F83F8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4A4A0500-7372-4497-981B-00E1FD8F83F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4A0500-7372-4497-981B-00E1FD8F83F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4A0500-7372-4497-981B-00E1FD8F83F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4A0500-7372-4497-981B-00E1FD8F83F8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FB298686-837E-4981-901E-8A233E1187C4} + EndGlobalSection +EndGlobal diff --git a/Subsystem/AttributeLoader.cs b/Subsystem/AttributeLoader.cs new file mode 100644 index 0000000..0022a63 --- /dev/null +++ b/Subsystem/AttributeLoader.cs @@ -0,0 +1,79 @@ +using BBI.Core.Data; +using BBI.Core.Utility.FixedPoint; +using BBI.Game.Data; +using LitJson; +using System.IO; +using UnityEngine; + +namespace Subsystem +{ + public class AttributeLoader + { + public static void LoadAttributes(EntityTypeCollection entityTypeCollection) + { + var jsonPath = Path.Combine(Application.dataPath, "patch.json"); + var json = File.ReadAllText(jsonPath); + + var attributesPatch = JsonMapper.ToObject(json); + + ApplyAttributesPatch(entityTypeCollection, attributesPatch); + } + + public static void ApplyAttributesPatch(EntityTypeCollection entityTypeCollection, AttributesPatch attributesPatch) + { + foreach (var kvp in attributesPatch.Entities) + { + var entityTypeName = kvp.Key; + var entityTypePatch = kvp.Value; + + var entityType = entityTypeCollection.GetEntityType(entityTypeName); + + var unitAttributes = entityType.Get(); + var unitAttributesWrapper = new UnitAttributesWrapper(unitAttributes); + + ApplyUnitAttributesPatch(entityTypePatch.UnitAttributes, unitAttributesWrapper); + + entityType.Replace(unitAttributes, unitAttributesWrapper); + } + } + + public static void ApplyUnitAttributesPatch(UnitAttributesPatch unitAttributesPatch, UnitAttributesWrapper unitAttributesWrapper) + { + if (unitAttributesPatch.MaxHealth.HasValue) { unitAttributesWrapper.MaxHealth = unitAttributesPatch.MaxHealth.Value; } + if (unitAttributesPatch.Armour.HasValue) { unitAttributesWrapper.Armour = unitAttributesPatch.Armour.Value; } + if (unitAttributesPatch.DamageReceivedMultiplier.HasValue) { unitAttributesWrapper.DamageReceivedMultiplier = Fixed64.UnsafeFromDouble(unitAttributesPatch.DamageReceivedMultiplier.Value); } + if (unitAttributesPatch.AccuracyReceivedMultiplier.HasValue) { unitAttributesWrapper.AccuracyReceivedMultiplier = Fixed64.UnsafeFromDouble(unitAttributesPatch.AccuracyReceivedMultiplier.Value); } + if (unitAttributesPatch.PopCapCost.HasValue) { unitAttributesWrapper.PopCapCost = unitAttributesPatch.PopCapCost.Value; } + if (unitAttributesPatch.ExperienceValue.HasValue) { unitAttributesWrapper.ExperienceValue = unitAttributesPatch.ExperienceValue.Value; } + if (unitAttributesPatch.ProductionTime.HasValue) { unitAttributesWrapper.ProductionTime = Fixed64.UnsafeFromDouble(unitAttributesPatch.ProductionTime.Value); } + if (unitAttributesPatch.AggroRange.HasValue) { unitAttributesWrapper.AggroRange = Fixed64.UnsafeFromDouble(unitAttributesPatch.AggroRange.Value); } + if (unitAttributesPatch.LeashRange.HasValue) { unitAttributesWrapper.LeashRange = Fixed64.UnsafeFromDouble(unitAttributesPatch.LeashRange.Value); } + if (unitAttributesPatch.AlertRange.HasValue) { unitAttributesWrapper.AlertRange = Fixed64.UnsafeFromDouble(unitAttributesPatch.AlertRange.Value); } + if (unitAttributesPatch.RepairPickupRange.HasValue) { unitAttributesWrapper.RepairPickupRange = Fixed64.UnsafeFromDouble(unitAttributesPatch.RepairPickupRange.Value); } + if (unitAttributesPatch.LeadPriority.HasValue) { unitAttributesWrapper.LeadPriority = unitAttributesPatch.LeadPriority.Value; } + if (unitAttributesPatch.Selectable.HasValue) { unitAttributesWrapper.Selectable = unitAttributesPatch.Selectable.Value; } + if (unitAttributesPatch.Controllable.HasValue) { unitAttributesWrapper.Controllable = unitAttributesPatch.Controllable.Value; } + if (unitAttributesPatch.Targetable.HasValue) { unitAttributesWrapper.Targetable = unitAttributesPatch.Targetable.Value; } + if (unitAttributesPatch.NonAutoTargetable.HasValue) { unitAttributesWrapper.NonAutoTargetable = unitAttributesPatch.NonAutoTargetable.Value; } + if (unitAttributesPatch.RetireTargetable.HasValue) { unitAttributesWrapper.RetireTargetable = unitAttributesPatch.RetireTargetable.Value; } + if (unitAttributesPatch.HackedReturnTargetable.HasValue) { unitAttributesWrapper.HackedReturnTargetable = unitAttributesPatch.HackedReturnTargetable.Value; } + if (unitAttributesPatch.HackableProperties.HasValue) { unitAttributesWrapper.HackableProperties = unitAttributesPatch.HackableProperties.Value; } + if (unitAttributesPatch.ExcludeFromUnitStats.HasValue) { unitAttributesWrapper.ExcludeFromUnitStats = unitAttributesPatch.ExcludeFromUnitStats.Value; } + if (unitAttributesPatch.BlocksLOF.HasValue) { unitAttributesWrapper.BlocksLOF = unitAttributesPatch.BlocksLOF.Value; } + if (unitAttributesPatch.WorldHeightOffset.HasValue) { unitAttributesWrapper.WorldHeightOffset = Fixed64.UnsafeFromDouble(unitAttributesPatch.WorldHeightOffset.Value); } + if (unitAttributesPatch.DoNotPersist.HasValue) { unitAttributesWrapper.DoNotPersist = unitAttributesPatch.DoNotPersist.Value; } + if (unitAttributesPatch.LevelBound.HasValue) { unitAttributesWrapper.LevelBound = unitAttributesPatch.LevelBound.Value; } + if (unitAttributesPatch.StartsInHangar.HasValue) { unitAttributesWrapper.StartsInHangar = unitAttributesPatch.StartsInHangar.Value; } + if (unitAttributesPatch.SensorRadius.HasValue) { unitAttributesWrapper.SensorRadius = Fixed64.UnsafeFromDouble(unitAttributesPatch.SensorRadius.Value); } + if (unitAttributesPatch.ContactRadius.HasValue) { unitAttributesWrapper.ContactRadius = Fixed64.UnsafeFromDouble(unitAttributesPatch.ContactRadius.Value); } + if (unitAttributesPatch.NumProductionQueues.HasValue) { unitAttributesWrapper.NumProductionQueues = unitAttributesPatch.NumProductionQueues.Value; } + if (unitAttributesPatch.ProductionQueueDepth.HasValue) { unitAttributesWrapper.ProductionQueueDepth = unitAttributesPatch.ProductionQueueDepth.Value; } + if (unitAttributesPatch.ShowProductionQueues.HasValue) { unitAttributesWrapper.ShowProductionQueues = unitAttributesPatch.ShowProductionQueues.Value; } + if (unitAttributesPatch.NoTextNotifications.HasValue) { unitAttributesWrapper.NoTextNotifications = unitAttributesPatch.NoTextNotifications.Value; } + if (unitAttributesPatch.FireRateDisplay.HasValue) { unitAttributesWrapper.FireRateDisplay = unitAttributesPatch.FireRateDisplay.Value; } + if (unitAttributesPatch.PriorityAsTarget.HasValue) { unitAttributesWrapper.PriorityAsTarget = Fixed64.UnsafeFromDouble(unitAttributesPatch.PriorityAsTarget.Value); } + if (unitAttributesPatch.Resource1Cost.HasValue) { unitAttributesWrapper.Resource1Cost = unitAttributesPatch.Resource1Cost.Value; } + if (unitAttributesPatch.Resource2Cost.HasValue) { unitAttributesWrapper.Resource2Cost = unitAttributesPatch.Resource2Cost.Value; } + } + } +} diff --git a/Subsystem/AttributesPatch.cs b/Subsystem/AttributesPatch.cs new file mode 100644 index 0000000..0a2c8f7 --- /dev/null +++ b/Subsystem/AttributesPatch.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Subsystem +{ + public class AttributesPatch + { + public Dictionary Entities { get; set; } = new Dictionary(); + } +} diff --git a/Subsystem/EntityTypePatch.cs b/Subsystem/EntityTypePatch.cs new file mode 100644 index 0000000..8f81a52 --- /dev/null +++ b/Subsystem/EntityTypePatch.cs @@ -0,0 +1,7 @@ +namespace Subsystem +{ + public class EntityTypePatch + { + public UnitAttributesPatch UnitAttributes { get; set; } = new UnitAttributesPatch(); + } +} diff --git a/Subsystem/Properties/AssemblyInfo.cs b/Subsystem/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..24372e0 --- /dev/null +++ b/Subsystem/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Subsystem")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Majiir Paktu")] +[assembly: AssemblyProduct("Subsystem")] +[assembly: AssemblyCopyright("Copyright © Majiir Paktu 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4a4a0500-7372-4497-981b-00e1fd8f83f8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersion("0.1.0")] diff --git a/Subsystem/Subsystem.csproj b/Subsystem/Subsystem.csproj new file mode 100644 index 0000000..6b75aeb --- /dev/null +++ b/Subsystem/Subsystem.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {4A4A0500-7372-4497-981B-00E1FD8F83F8} + Library + Properties + Subsystem + Subsystem + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Subsystem/UnitAttributesPatch.cs b/Subsystem/UnitAttributesPatch.cs new file mode 100644 index 0000000..77fe612 --- /dev/null +++ b/Subsystem/UnitAttributesPatch.cs @@ -0,0 +1,48 @@ +using BBI.Game.Data; + +namespace Subsystem +{ + public class UnitAttributesPatch + { + public int? MaxHealth { get; set; } + public int? Armour { get; set; } + + public double? DamageReceivedMultiplier { get; set; } + public double? AccuracyReceivedMultiplier { get; set; } + public int? PopCapCost { get; set; } + public int? ExperienceValue { get; set; } + public double? ProductionTime { get; set; } + public double? AggroRange { get; set; } + public double? LeashRange { get; set; } + public double? AlertRange { get; set; } + public double? RepairPickupRange { get; set; } + + public int? LeadPriority { get; set; } + public bool? Selectable { get; set; } + public bool? Controllable { get; set; } + public bool? Targetable { get; set; } + public bool? NonAutoTargetable { get; set; } + public bool? RetireTargetable { get; set; } + public bool? HackedReturnTargetable { get; set; } + public HackableProperties? HackableProperties { get; set; } + public bool? ExcludeFromUnitStats { get; set; } + public bool? BlocksLOF { get; set; } + public double? WorldHeightOffset { get; set; } + public bool? DoNotPersist { get; set; } + public bool? LevelBound { get; set; } + public bool? StartsInHangar { get; set; } + public double? SensorRadius { get; set; } + public double? ContactRadius { get; set; } + public int? NumProductionQueues { get; set; } + public int? ProductionQueueDepth { get; set; } + public bool? ShowProductionQueues { get; set; } + public bool? NoTextNotifications { get; set; } + + public int? FireRateDisplay { get; set; } + + public double? PriorityAsTarget { get; set; } + + public int? Resource1Cost { get; set; } + public int? Resource2Cost { get; set; } + } +} diff --git a/Subsystem/UnitAttributesWrapper.cs b/Subsystem/UnitAttributesWrapper.cs new file mode 100644 index 0000000..7977e96 --- /dev/null +++ b/Subsystem/UnitAttributesWrapper.cs @@ -0,0 +1,149 @@ +using BBI.Core; +using BBI.Core.Utility.FixedPoint; +using BBI.Game.Data; +using System.Collections.Generic; + +namespace Subsystem +{ + public class UnitAttributesWrapper : NamedObjectBase, UnitAttributes + { + public UnitAttributesWrapper(UnitAttributes other) : base(other.Name) + { + Class = other.Class; + SelectionFlags = other.SelectionFlags; + NavMeshAttributes = other.NavMeshAttributes; + MaxHealth = other.MaxHealth; + Armour = other.Armour; + DamageReceivedMultiplier = other.DamageReceivedMultiplier; + AccuracyReceivedMultiplier = other.AccuracyReceivedMultiplier; + PopCapCost = other.PopCapCost; + ExperienceValue = other.ExperienceValue; + ProductionTime = other.ProductionTime; + AggroRange = other.AggroRange; + LeashRange = other.LeashRange; + AlertRange = other.AlertRange; + RepairPickupRange = other.RepairPickupRange; + UnitPositionReaggroConditions = other.UnitPositionReaggroConditions; + LeashPositionReaggroConditions = other.LeashPositionReaggroConditions; + LeadPriority = other.LeadPriority; + Selectable = other.Selectable; + Controllable = other.Controllable; + Targetable = other.Targetable; + NonAutoTargetable = other.NonAutoTargetable; + RetireTargetable = other.RetireTargetable; + HackedReturnTargetable = other.HackedReturnTargetable; + HackableProperties = other.HackableProperties; + ExcludeFromUnitStats = other.ExcludeFromUnitStats; + BlocksLOF = other.BlocksLOF; + WorldHeightOffset = other.WorldHeightOffset; + DoNotPersist = other.DoNotPersist; + LevelBound = other.LevelBound; + StartsInHangar = other.StartsInHangar; + SensorRadius = other.SensorRadius; + ContactRadius = other.ContactRadius; + NumProductionQueues = other.NumProductionQueues; + ProductionQueueDepth = other.ProductionQueueDepth; + ShowProductionQueues = other.ShowProductionQueues; + NoTextNotifications = other.NoTextNotifications; + NotificationFlags = other.NotificationFlags; + FireRateDisplay = other.FireRateDisplay; + WeaponLoadout = other.WeaponLoadout; + PriorityAsTarget = other.PriorityAsTarget; + ThreatData = other.ThreatData; + ThreatCounters = other.ThreatCounters; + ThreatCounteredBys = other.ThreatCounteredBys; + Resource1Cost = other.Resource1Cost; + Resource2Cost = other.Resource2Cost; + } + + public UnitClass Class { get; set; } + + public UnitSelectionFlags SelectionFlags { get; set; } + + public NavMeshAttributes NavMeshAttributes { get; set; } + + public int MaxHealth { get; set; } + + public int Armour { get; set; } + + public Fixed64 DamageReceivedMultiplier { get; set; } + + public Fixed64 AccuracyReceivedMultiplier { get; set; } + + public int PopCapCost { get; set; } + + public int ExperienceValue { get; set; } + + public Fixed64 ProductionTime { get; set; } + + public Fixed64 AggroRange { get; set; } + + public Fixed64 LeashRange { get; set; } + + public Fixed64 AlertRange { get; set; } + + public Fixed64 RepairPickupRange { get; set; } + + public UnitPositionReaggroConditions UnitPositionReaggroConditions { get; set; } + + public LeashPositionReaggroConditions LeashPositionReaggroConditions { get; set; } + + public int LeadPriority { get; set; } + + public bool Selectable { get; set; } + + public bool Controllable { get; set; } + + public bool Targetable { get; set; } + + public bool NonAutoTargetable { get; set; } + + public bool RetireTargetable { get; set; } + + public bool HackedReturnTargetable { get; set; } + + public HackableProperties HackableProperties { get; set; } + + public bool ExcludeFromUnitStats { get; set; } + + public bool BlocksLOF { get; set; } + + public Fixed64 WorldHeightOffset { get; set; } + + public bool DoNotPersist { get; set; } + + public bool LevelBound { get; set; } + + public bool StartsInHangar { get; set; } + + public Fixed64 SensorRadius { get; set; } + + public Fixed64 ContactRadius { get; set; } + + public int NumProductionQueues { get; set; } + + public int ProductionQueueDepth { get; set; } + + public bool ShowProductionQueues { get; set; } + + public bool NoTextNotifications { get; set; } + + public UnitNotificationFlags NotificationFlags { get; set; } + + public int FireRateDisplay { get; set; } + + public WeaponBinding[] WeaponLoadout { get; set; } + + public Fixed64 PriorityAsTarget { get; set; } + + public ThreatData ThreatData { get; set; } + + public IEnumerable ThreatCounters { get; set; } + + public IEnumerable ThreatCounteredBys { get; set; } + + public int Resource1Cost { get; set; } + + public int Resource2Cost { get; set; } + } +}