demo2
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Demo2.Domain",
|
||||
"rootNamespace": "Demo2.Domain",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60f0384ea03a1314da9f3fc30edf2bc5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,150 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
public interface IDemo2Mod
|
||||
{
|
||||
string Id { get; }
|
||||
string Version { get; }
|
||||
void Register(Demo2ContentRegistry registry);
|
||||
}
|
||||
|
||||
public sealed class Demo2ModManifest
|
||||
{
|
||||
public string Id = string.Empty;
|
||||
public string Version = string.Empty;
|
||||
public string ContentSha256 = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class Demo2ContentRegistry
|
||||
{
|
||||
private readonly Dictionary<string, Demo2RuleDefinition> _rules = new(StringComparer.Ordinal);
|
||||
private readonly Dictionary<int, Demo2RecipeDefinition> _recipes = new();
|
||||
private readonly Dictionary<Demo2MachineKind, Demo2MachineDefinition> _machines = new();
|
||||
private readonly List<Demo2WaveDefinition> _waves = new();
|
||||
|
||||
public IReadOnlyDictionary<string, Demo2RuleDefinition> Rules => _rules;
|
||||
public IReadOnlyDictionary<int, Demo2RecipeDefinition> Recipes => _recipes;
|
||||
public IReadOnlyDictionary<Demo2MachineKind, Demo2MachineDefinition> Machines => _machines;
|
||||
public IReadOnlyList<Demo2WaveDefinition> Waves => _waves;
|
||||
|
||||
public void AddRule(Demo2RuleDefinition definition) => _rules.Add(definition.Id, definition);
|
||||
public void AddRecipe(Demo2RecipeDefinition definition) => _recipes.Add(definition.Id, definition);
|
||||
public void AddMachine(Demo2MachineDefinition definition) => _machines.Add(definition.Kind, definition);
|
||||
public void AddWave(Demo2WaveDefinition definition) => _waves.Add(definition);
|
||||
|
||||
public string ComputeContentHash()
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
foreach (var rule in _rules.Values.OrderBy(value => value.Id, StringComparer.Ordinal))
|
||||
{
|
||||
text.Append(rule.Id).Append('|').Append(rule.Name).Append('|').Append((int)rule.Category);
|
||||
foreach (var instruction in rule.Instructions)
|
||||
text.Append(':').Append((int)instruction.Opcode).Append(',').Append((int)instruction.Condition).Append(',').Append(instruction.Operand);
|
||||
text.AppendLine();
|
||||
}
|
||||
foreach (var recipe in _recipes.Values.OrderBy(value => value.Id))
|
||||
text.Append(recipe.Id).Append('|').Append(recipe.Name).Append('|')
|
||||
.Append((int)recipe.InputA).Append(',').Append(recipe.InputCountA).Append('|')
|
||||
.Append((int)recipe.InputB).Append(',').Append(recipe.InputCountB).Append('|')
|
||||
.Append((int)recipe.Output).Append(',').Append(recipe.OutputCount).Append('|').Append(recipe.ProcessTicks).AppendLine();
|
||||
foreach (var machine in _machines.Values.OrderBy(value => (int)value.Kind))
|
||||
text.Append("machine|").Append((int)machine.Kind).Append('|').Append(machine.Name).Append('|')
|
||||
.Append(machine.Cost).Append('|').Append(machine.RecipeId).Append('|').Append(machine.Capacity).AppendLine();
|
||||
foreach (var wave in _waves.OrderBy(value => value.Cycle))
|
||||
text.Append("wave|").Append(wave.Cycle).Append('|').Append(wave.TotalHealth).Append('|').Append(wave.Trait).AppendLine();
|
||||
using var sha = SHA256.Create();
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString()));
|
||||
var hex = new StringBuilder(bytes.Length * 2);
|
||||
for (var i = 0; i < bytes.Length; i++) hex.Append(bytes[i].ToString("x2"));
|
||||
return hex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Demo2BuiltInContent
|
||||
{
|
||||
public static Demo2ContentRegistry Create()
|
||||
{
|
||||
var registry = new Demo2ContentRegistry();
|
||||
AddMachinesAndRecipes(registry);
|
||||
AddWaves(registry);
|
||||
foreach (var rule in BuildRules()) registry.AddRule(rule);
|
||||
return registry;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<Demo2RuleDefinition> BuildRules() => new[]
|
||||
{
|
||||
Rule("production.triple-echo", "三连复写", "连续完成三次相同配方后复制下一件产物。", Demo2RuleCategory.Production, Demo2RuleOpcode.DuplicateNextOutput, 1, Demo2RuleCondition.ThreeSameRecipes),
|
||||
Rule("production.hot-ingot", "灼热锭", "无故障时,每周期增加 120 载荷。", Demo2RuleCategory.Production, Demo2RuleOpcode.AddPayload, 120, Demo2RuleCondition.NoFaults),
|
||||
Rule("production.precision-gears", "精密齿轮", "首批齿轮使载荷提高 25%。", Demo2RuleCategory.Production, Demo2RuleOpcode.MultiplyPayload, 1250, Demo2RuleCondition.FirstBatch),
|
||||
Rule("production.dense-ammo", "高密弹药", "结算载荷固定增加 180。", Demo2RuleCategory.Production, Demo2RuleOpcode.AddPayload, 180),
|
||||
Rule("logistics.twin-stream", "双流共振", "铁矿与燃料相邻流动时,倍率增加 0.35。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddMultiplier, 350, Demo2RuleCondition.IronFuelAdjacent),
|
||||
Rule("logistics.clear-belt", "净空运输", "传送带占用低于 80% 时,倍率提高 20%。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.MultiplyMultiplier, 1200, Demo2RuleCondition.BeltBelowEightyPercent),
|
||||
Rule("logistics.split-balance", "均衡分流", "稳定分流为载荷增加 90。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddPayload, 90),
|
||||
Rule("logistics.long-haul", "长距惯性", "低占用且无故障时,倍率增加 0.2。", Demo2RuleCategory.Logistics, Demo2RuleOpcode.AddMultiplier, 200, Demo2RuleCondition.BeltBelowEightyPercent | Demo2RuleCondition.NoFaults),
|
||||
Rule("machine.overclock", "红区超频", "载荷提高 50%,并增加 1 次故障。", Demo2RuleCategory.MachineState, new[]
|
||||
{
|
||||
Instruction(Demo2RuleOpcode.MultiplyPayload, 1500), Instruction(Demo2RuleOpcode.AddFaults, 1)
|
||||
}),
|
||||
Rule("machine.preventive-maintenance", "预防维护", "存在故障时修复 1 次并增加 60 载荷。", Demo2RuleCategory.MachineState, new[]
|
||||
{
|
||||
Instruction(Demo2RuleOpcode.RepairFaults, 1, Demo2RuleCondition.HasFaults), Instruction(Demo2RuleOpcode.AddPayload, 60, Demo2RuleCondition.HasFaults)
|
||||
}),
|
||||
Rule("machine.night-shift", "夜班协议", "无故障时倍率增加 0.25。", Demo2RuleCategory.MachineState, Demo2RuleOpcode.AddMultiplier, 250, Demo2RuleCondition.NoFaults),
|
||||
Rule("machine.thermal-bank", "余热银行", "剩余能量每点转化 3 载荷。", Demo2RuleCategory.MachineState, Demo2RuleOpcode.ConvertEnergyToPayload, 3, Demo2RuleCondition.HasExcessEnergy),
|
||||
Rule("cycle.energy-settlement", "能量清算", "结算时剩余能量每点转化 5 载荷。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.ConvertEnergyToPayload, 5, Demo2RuleCondition.HasExcessEnergy),
|
||||
Rule("cycle.excess-credit", "过量授信", "每 20 点超额火力兑换 1 资金。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.AddMoneyFromExcess, 20),
|
||||
Rule("cycle.first-batch", "首批红利", "首批完成时倍率增加 0.3。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.AddMultiplier, 300, Demo2RuleCondition.FirstBatch),
|
||||
Rule("cycle.reserve-capacitor", "储备电容", "剩余能量使倍率提高 15%。", Demo2RuleCategory.CycleSettlement, Demo2RuleOpcode.MultiplyMultiplier, 1150, Demo2RuleCondition.HasExcessEnergy),
|
||||
Rule("risk.glass-core", "玻璃核心", "核心仅剩 1 点耐久时,倍率提高 65%。", Demo2RuleCategory.RiskExchange, Demo2RuleOpcode.AddMultiplier, 650, Demo2RuleCondition.CoreAtRisk),
|
||||
Rule("risk.debt-leverage", "负债杠杆", "立即增加 260 载荷,但故障计数增加 2。", Demo2RuleCategory.RiskExchange, new[]
|
||||
{
|
||||
Instruction(Demo2RuleOpcode.AddPayload, 260), Instruction(Demo2RuleOpcode.AddFaults, 2)
|
||||
})
|
||||
};
|
||||
|
||||
private static void AddMachinesAndRecipes(Demo2ContentRegistry registry)
|
||||
{
|
||||
registry.AddRecipe(new Demo2RecipeDefinition { Id = 1, Name = "采集铁矿", Output = Demo2MaterialKind.IronOre, OutputCount = 1, ProcessTicks = 20 });
|
||||
registry.AddRecipe(new Demo2RecipeDefinition { Id = 2, Name = "燃料电解", Output = Demo2MaterialKind.Fuel, OutputCount = 1, ProcessTicks = 30 });
|
||||
registry.AddRecipe(new Demo2RecipeDefinition { Id = 3, Name = "铁锭熔炼", InputA = Demo2MaterialKind.IronOre, InputB = Demo2MaterialKind.Fuel, Output = Demo2MaterialKind.Ingot, InputCountA = 2, InputCountB = 1, OutputCount = 1, ProcessTicks = 40 });
|
||||
registry.AddRecipe(new Demo2RecipeDefinition { Id = 4, Name = "齿轮装配", InputA = Demo2MaterialKind.Ingot, Output = Demo2MaterialKind.Gear, InputCountA = 2, OutputCount = 1, ProcessTicks = 50 });
|
||||
registry.AddRecipe(new Demo2RecipeDefinition { Id = 5, Name = "弹药压制", InputA = Demo2MaterialKind.Gear, InputB = Demo2MaterialKind.Energy, Output = Demo2MaterialKind.Ammo, InputCountA = 1, InputCountB = 1, OutputCount = 4, ProcessTicks = 35 });
|
||||
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Miner, Name = "采集器", Cost = 40, RecipeId = 1, Capacity = 8 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Conveyor, Name = "传送带", Cost = 5, Capacity = 4 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Splitter, Name = "分流器", Cost = 15, Capacity = 8 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Smelter, Name = "熔炼机", Cost = 80, RecipeId = 3, Capacity = 12 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Assembler, Name = "组装机", Cost = 110, RecipeId = 4, Capacity = 12 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Loader, Name = "弹药装填器", Cost = 70, RecipeId = 5, Capacity = 24 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Turret, Name = "自动炮塔", Cost = 150, Capacity = 200 });
|
||||
registry.AddMachine(new Demo2MachineDefinition { Kind = Demo2MachineKind.Generator, Name = "能量站", Cost = 60, RecipeId = 2, Capacity = 16 });
|
||||
}
|
||||
|
||||
private static void AddWaves(Demo2ContentRegistry registry)
|
||||
{
|
||||
var health = 500L;
|
||||
var traits = new[] { "标准装甲", "分散队列", "电磁护盾", "高速突击", "核心压制" };
|
||||
for (var cycle = 1; cycle <= Demo2Protocol.CycleCount; cycle++)
|
||||
{
|
||||
registry.AddWave(new Demo2WaveDefinition { Cycle = cycle, TotalHealth = health, Trait = traits[cycle - 1] });
|
||||
health = SaturatingMath.MultiplyScaled(health, 1800);
|
||||
}
|
||||
}
|
||||
|
||||
private static Demo2RuleDefinition Rule(string id, string name, string description, Demo2RuleCategory category, Demo2RuleOpcode opcode, long operand, Demo2RuleCondition condition = Demo2RuleCondition.None)
|
||||
=> Rule(id, name, description, category, new[] { Instruction(opcode, operand, condition) });
|
||||
|
||||
private static Demo2RuleDefinition Rule(string id, string name, string description, Demo2RuleCategory category, IEnumerable<Demo2RuleInstructionData> instructions)
|
||||
=> new() { Id = id, Name = name, Description = description, Category = category, Instructions = instructions.ToList() };
|
||||
|
||||
private static Demo2RuleInstructionData Instruction(Demo2RuleOpcode opcode, long operand, Demo2RuleCondition condition = Demo2RuleCondition.None)
|
||||
=> new() { Opcode = opcode, Operand = operand, Condition = condition };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 879b2677a87331d48a64a750e7d46537
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,188 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
public enum Demo2Phase : byte { Title, Lobby, Build, Production, Defense, Reward, Victory, Defeat }
|
||||
public enum Demo2TransportMode : byte { Tcp, Kcp }
|
||||
public enum Demo2MachineKind : byte { Miner, Conveyor, Splitter, Smelter, Assembler, Loader, Turret, Generator, NightShiftSmelter = 100 }
|
||||
public enum Demo2MaterialKind : byte { IronOre, Fuel, Ingot, Gear, Ammo, Energy }
|
||||
public enum Demo2RuleCategory : byte { Production, Logistics, MachineState, CycleSettlement, RiskExchange }
|
||||
public enum Demo2RuleOpcode : byte
|
||||
{
|
||||
AddPayload,
|
||||
MultiplyPayload,
|
||||
AddMultiplier,
|
||||
MultiplyMultiplier,
|
||||
ConvertEnergyToPayload,
|
||||
AddMoneyFromExcess,
|
||||
DuplicateNextOutput,
|
||||
AddFaults,
|
||||
RepairFaults
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Demo2RuleCondition : ushort
|
||||
{
|
||||
None = 0,
|
||||
ThreeSameRecipes = 1 << 0,
|
||||
IronFuelAdjacent = 1 << 1,
|
||||
BeltBelowEightyPercent = 1 << 2,
|
||||
NoFaults = 1 << 3,
|
||||
HasExcessEnergy = 1 << 4,
|
||||
FirstBatch = 1 << 5,
|
||||
CoreAtRisk = 1 << 6,
|
||||
HasFaults = 1 << 7
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2RuleInstructionData
|
||||
{
|
||||
public Demo2RuleOpcode Opcode;
|
||||
public Demo2RuleCondition Condition;
|
||||
public long Operand;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2RuleDefinition
|
||||
{
|
||||
public string Id = string.Empty;
|
||||
public string Name = string.Empty;
|
||||
public string Description = string.Empty;
|
||||
public Demo2RuleCategory Category;
|
||||
public List<Demo2RuleInstructionData> Instructions = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2RecipeDefinition
|
||||
{
|
||||
public int Id;
|
||||
public string Name = string.Empty;
|
||||
public Demo2MaterialKind InputA;
|
||||
public Demo2MaterialKind InputB;
|
||||
public Demo2MaterialKind Output;
|
||||
public int InputCountA;
|
||||
public int InputCountB;
|
||||
public int OutputCount;
|
||||
public int ProcessTicks;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2MachineDefinition
|
||||
{
|
||||
public Demo2MachineKind Kind;
|
||||
public string Name = string.Empty;
|
||||
public int Cost;
|
||||
public int RecipeId;
|
||||
public int Capacity;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2WaveDefinition
|
||||
{
|
||||
public int Cycle;
|
||||
public long TotalHealth;
|
||||
public string Trait = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2BuildCommand
|
||||
{
|
||||
public long Sequence;
|
||||
public string IdempotencyToken = string.Empty;
|
||||
public string PlayerId = string.Empty;
|
||||
public Demo2MachineKind Kind;
|
||||
public int X;
|
||||
public int Y;
|
||||
public byte Rotation;
|
||||
public int RecipeId;
|
||||
public bool Remove;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2CommandResult
|
||||
{
|
||||
public bool Accepted;
|
||||
public bool Duplicate;
|
||||
public string ErrorCode = string.Empty;
|
||||
public int Refund;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2SnapshotEntity
|
||||
{
|
||||
public int StableId;
|
||||
public Demo2MachineKind MachineKind;
|
||||
public Demo2MaterialKind MaterialKind;
|
||||
public int X;
|
||||
public int Y;
|
||||
public byte Rotation;
|
||||
public int RecipeId;
|
||||
public int Progress;
|
||||
public int Quantity;
|
||||
public int Faults;
|
||||
public int PendingOutputs;
|
||||
public long TurretAmmo;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2Snapshot
|
||||
{
|
||||
public int SimulationVersion = Demo2Protocol.SimulationVersion;
|
||||
public long Tick;
|
||||
public ulong Seed;
|
||||
public Demo2Phase Phase;
|
||||
public int Cycle;
|
||||
public int CoreHealth;
|
||||
public long Money;
|
||||
public long Payload;
|
||||
public long MultiplierMilli = SaturatingMath.Scale;
|
||||
public long Energy;
|
||||
public long ExcessFirepower;
|
||||
public int RuleAppliedCycle;
|
||||
public int SpeedMultiplier = 1;
|
||||
public long Extracted;
|
||||
public long Transported;
|
||||
public long Processed;
|
||||
public long AmmoProduced;
|
||||
public int CompletedSameRecipeStreak;
|
||||
public int LastRecipeId;
|
||||
public int Faults;
|
||||
public int BeltOccupancy;
|
||||
public int BeltCapacity;
|
||||
public byte IronFuelAdjacent;
|
||||
public byte FirstBatch;
|
||||
public int DuplicateNextOutputs;
|
||||
public ulong StateHash;
|
||||
public List<string> RuleIds = new();
|
||||
public List<Demo2SnapshotEntity> Entities = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2SaveData
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
public int Version = CurrentVersion;
|
||||
public int Cycle = 1;
|
||||
public int CoreHealth = 3;
|
||||
public long Money = 500;
|
||||
public ulong Seed = 20260817;
|
||||
public string BlueprintJson = string.Empty;
|
||||
public List<string> RuleIds = new();
|
||||
public List<string> UnlockIds = new();
|
||||
public string SnapshotJson = string.Empty;
|
||||
public string SnapshotHash = string.Empty;
|
||||
}
|
||||
|
||||
public static class Demo2Protocol
|
||||
{
|
||||
public const string GameVersion = "1.0.0";
|
||||
public const int SimulationVersion = 1;
|
||||
public const int TickRate = 20;
|
||||
public const int GridWidth = 32;
|
||||
public const int GridHeight = 20;
|
||||
public const int CycleCount = 5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06add429ba83dfd4091aade08e445455
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class Demo2NetworkEnvelope
|
||||
{
|
||||
public string Type = string.Empty;
|
||||
public long Sequence;
|
||||
public string PayloadJson = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2Handshake
|
||||
{
|
||||
public string GameVersion = Demo2Protocol.GameVersion;
|
||||
public int SimulationVersion = Demo2Protocol.SimulationVersion;
|
||||
public string PlayerId = string.Empty;
|
||||
public string ReconnectToken = string.Empty;
|
||||
public List<Demo2ModManifest> Mods = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2HandshakeResult
|
||||
{
|
||||
public bool Accepted;
|
||||
public string ErrorCode = string.Empty;
|
||||
public string Difference = string.Empty;
|
||||
public string PlayerId = string.Empty;
|
||||
public string ReconnectToken = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2HashBroadcast
|
||||
{
|
||||
public long Tick;
|
||||
public ulong StateHash;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2LobbyPlayer
|
||||
{
|
||||
public string PlayerId = string.Empty;
|
||||
public string DisplayName = string.Empty;
|
||||
public bool Ready;
|
||||
public bool Connected;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Demo2LobbyState
|
||||
{
|
||||
public bool IsHost;
|
||||
public Demo2TransportMode Transport;
|
||||
public List<Demo2LobbyPlayer> Players = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9755a060f8361054d9b41a7e9e3a70e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
public struct Demo2RuleContext
|
||||
{
|
||||
public long Payload;
|
||||
public long MultiplierMilli;
|
||||
public long Energy;
|
||||
public long Money;
|
||||
public long ExcessFirepower;
|
||||
public int Faults;
|
||||
public bool ThreeSameRecipes;
|
||||
public bool IronFuelAdjacent;
|
||||
public bool BeltBelowEightyPercent;
|
||||
public bool FirstBatch;
|
||||
public bool CoreAtRisk;
|
||||
public int DuplicatedOutputs;
|
||||
|
||||
public long FinalFirepower => SaturatingMath.MultiplyScaled(Payload, MultiplierMilli);
|
||||
}
|
||||
|
||||
public static class Demo2RuleEngine
|
||||
{
|
||||
public static Demo2RuleContext Evaluate(Demo2RuleContext context, IReadOnlyList<Demo2RuleDefinition> orderedRules)
|
||||
{
|
||||
context.MultiplierMilli = context.MultiplierMilli <= 0 ? SaturatingMath.Scale : context.MultiplierMilli;
|
||||
for (var ruleIndex = 0; ruleIndex < orderedRules.Count; ruleIndex++)
|
||||
{
|
||||
var instructions = orderedRules[ruleIndex].Instructions;
|
||||
for (var instructionIndex = 0; instructionIndex < instructions.Count; instructionIndex++)
|
||||
{
|
||||
var instruction = instructions[instructionIndex];
|
||||
if (!Matches(instruction.Condition, context)) continue;
|
||||
Apply(ref context, instruction);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public static bool Matches(Demo2RuleCondition condition, in Demo2RuleContext context)
|
||||
{
|
||||
if ((condition & Demo2RuleCondition.ThreeSameRecipes) != 0 && !context.ThreeSameRecipes) return false;
|
||||
if ((condition & Demo2RuleCondition.IronFuelAdjacent) != 0 && !context.IronFuelAdjacent) return false;
|
||||
if ((condition & Demo2RuleCondition.BeltBelowEightyPercent) != 0 && !context.BeltBelowEightyPercent) return false;
|
||||
if ((condition & Demo2RuleCondition.NoFaults) != 0 && context.Faults != 0) return false;
|
||||
if ((condition & Demo2RuleCondition.HasExcessEnergy) != 0 && context.Energy <= 0) return false;
|
||||
if ((condition & Demo2RuleCondition.FirstBatch) != 0 && !context.FirstBatch) return false;
|
||||
if ((condition & Demo2RuleCondition.CoreAtRisk) != 0 && !context.CoreAtRisk) return false;
|
||||
if ((condition & Demo2RuleCondition.HasFaults) != 0 && context.Faults <= 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Apply(ref Demo2RuleContext context, Demo2RuleInstructionData instruction)
|
||||
{
|
||||
switch (instruction.Opcode)
|
||||
{
|
||||
case Demo2RuleOpcode.AddPayload:
|
||||
context.Payload = SaturatingMath.Add(context.Payload, instruction.Operand);
|
||||
break;
|
||||
case Demo2RuleOpcode.MultiplyPayload:
|
||||
context.Payload = SaturatingMath.MultiplyScaled(context.Payload, instruction.Operand);
|
||||
break;
|
||||
case Demo2RuleOpcode.AddMultiplier:
|
||||
context.MultiplierMilli = SaturatingMath.Add(context.MultiplierMilli, instruction.Operand);
|
||||
break;
|
||||
case Demo2RuleOpcode.MultiplyMultiplier:
|
||||
context.MultiplierMilli = SaturatingMath.MultiplyScaled(context.MultiplierMilli, instruction.Operand);
|
||||
break;
|
||||
case Demo2RuleOpcode.ConvertEnergyToPayload:
|
||||
context.Payload = SaturatingMath.Add(context.Payload, SaturatingMath.Multiply(context.Energy, instruction.Operand));
|
||||
context.Energy = 0;
|
||||
break;
|
||||
case Demo2RuleOpcode.AddMoneyFromExcess:
|
||||
context.Money = SaturatingMath.Add(context.Money, context.ExcessFirepower / Math.Max(1, instruction.Operand));
|
||||
break;
|
||||
case Demo2RuleOpcode.DuplicateNextOutput:
|
||||
context.DuplicatedOutputs = SaturatingMath.Add(context.DuplicatedOutputs, (int)instruction.Operand) > int.MaxValue
|
||||
? int.MaxValue
|
||||
: context.DuplicatedOutputs + (int)instruction.Operand;
|
||||
break;
|
||||
case Demo2RuleOpcode.AddFaults:
|
||||
context.Faults = Math.Max(0, context.Faults + (int)instruction.Operand);
|
||||
break;
|
||||
case Demo2RuleOpcode.RepairFaults:
|
||||
context.Faults = Math.Max(0, context.Faults - (int)instruction.Operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27fbfb173d4cab643831a132177109b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
public struct DeterministicRandom
|
||||
{
|
||||
private ulong _state;
|
||||
private ulong _increment;
|
||||
|
||||
public DeterministicRandom(ulong seed, ulong stream = 54)
|
||||
{
|
||||
_state = 0;
|
||||
_increment = (stream << 1) | 1;
|
||||
NextUInt();
|
||||
_state += seed;
|
||||
NextUInt();
|
||||
}
|
||||
|
||||
public uint NextUInt()
|
||||
{
|
||||
var oldState = _state;
|
||||
_state = unchecked(oldState * 6364136223846793005UL + _increment);
|
||||
var xorShifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
|
||||
var rotation = (int)(oldState >> 59);
|
||||
return (xorShifted >> rotation) | (xorShifted << ((-rotation) & 31));
|
||||
}
|
||||
|
||||
public int NextInt(int minInclusive, int maxExclusive)
|
||||
{
|
||||
if (maxExclusive <= minInclusive) return minInclusive;
|
||||
return minInclusive + (int)(NextUInt() % (uint)(maxExclusive - minInclusive));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4946a70f5600c044b85dcc82d4d33bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
namespace Demo2.Domain
|
||||
{
|
||||
public static class SaturatingMath
|
||||
{
|
||||
public const long Scale = 1000;
|
||||
|
||||
public static long Add(long left, long right)
|
||||
{
|
||||
if (right > 0 && left > long.MaxValue - right) return long.MaxValue;
|
||||
if (right < 0 && left < long.MinValue - right) return long.MinValue;
|
||||
return left + right;
|
||||
}
|
||||
|
||||
public static long Multiply(long left, long right)
|
||||
{
|
||||
if (left == 0 || right == 0) return 0;
|
||||
if (left == long.MinValue && right == -1) return long.MaxValue;
|
||||
if (right == long.MinValue && left == -1) return long.MaxValue;
|
||||
var negative = (left < 0) ^ (right < 0);
|
||||
var a = AbsUnsigned(left);
|
||||
var b = AbsUnsigned(right);
|
||||
if (a > (ulong)long.MaxValue / b) return negative ? long.MinValue : long.MaxValue;
|
||||
var value = (long)(a * b);
|
||||
return negative ? -value : value;
|
||||
}
|
||||
|
||||
public static long MultiplyScaled(long value, long multiplierMilli)
|
||||
{
|
||||
if (value == 0 || multiplierMilli == 0) return 0;
|
||||
var product = Multiply(value, multiplierMilli);
|
||||
if (product == long.MaxValue || product == long.MinValue) return product;
|
||||
return product / Scale;
|
||||
}
|
||||
|
||||
public static string Format(long value)
|
||||
{
|
||||
var abs = value == long.MinValue ? long.MaxValue : Math.Abs(value);
|
||||
if (abs < 1_000_000_000) return value.ToString("N0");
|
||||
var exponent = (int)Math.Floor(Math.Log10(abs));
|
||||
var mantissa = value / Math.Pow(10, exponent);
|
||||
return mantissa.ToString("0.##") + "e" + exponent;
|
||||
}
|
||||
|
||||
private static ulong AbsUnsigned(long value) => value < 0 ? (ulong)(-(value + 1)) + 1UL : (ulong)value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c00ba3036e8f4814f898673c9363d11c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user