| | | 1 | | using System.Text.Json.Serialization; |
| | | 2 | | |
| | | 3 | | namespace RaidLoop.Core; |
| | | 4 | | |
| | | 5 | | public enum ItemType |
| | | 6 | | { |
| | | 7 | | Weapon, |
| | | 8 | | Armor, |
| | | 9 | | Backpack, |
| | | 10 | | Consumable, |
| | | 11 | | Sellable, |
| | | 12 | | Material |
| | | 13 | | } |
| | | 14 | | |
| | | 15 | | public enum OpeningContactState |
| | | 16 | | { |
| | | 17 | | MutualContact, |
| | | 18 | | PlayerAmbush, |
| | | 19 | | EnemyAmbush |
| | | 20 | | } |
| | | 21 | | |
| | | 22 | | public enum OpeningSide |
| | | 23 | | { |
| | | 24 | | None, |
| | | 25 | | Player, |
| | | 26 | | Enemy |
| | | 27 | | } |
| | | 28 | | |
| | 4 | 29 | | public sealed record OpeningPhaseContext( |
| | 8 | 30 | | OpeningContactState ContactState, |
| | 2 | 31 | | int PlayerInitiative, |
| | 2 | 32 | | int EnemyInitiative, |
| | 0 | 33 | | int TimeOfDayVisibilityModifier = 0, |
| | 0 | 34 | | int EnvironmentAwarenessModifier = 0, |
| | 0 | 35 | | int PlayerGearAwarenessModifier = 0, |
| | 4 | 36 | | int EnemyLocalizationModifier = 0); |
| | | 37 | | |
| | | 38 | | [JsonConverter(typeof(ItemJsonConverter))] |
| | | 39 | | public sealed record Item( |
| | | 40 | | string Name, |
| | | 41 | | ItemType Type, |
| | | 42 | | int Weight, |
| | | 43 | | int Value = 1, |
| | | 44 | | int Slots = 1, |
| | | 45 | | Rarity Rarity = Rarity.Common, |
| | | 46 | | DisplayRarity DisplayRarity = DisplayRarity.Common) |
| | | 47 | | { |
| | | 48 | | [JsonPropertyName("itemDefId")] |
| | | 49 | | public int ItemDefId |
| | | 50 | | { |
| | | 51 | | get |
| | | 52 | | { |
| | | 53 | | if (_itemDefId > 0) |
| | | 54 | | { |
| | | 55 | | return _itemDefId; |
| | | 56 | | } |
| | | 57 | | |
| | | 58 | | if (!string.IsNullOrWhiteSpace(_key) && ItemCatalog.TryGetItemDefIdByKey(_key!, out var resolvedFromKey)) |
| | | 59 | | { |
| | | 60 | | return resolvedFromKey; |
| | | 61 | | } |
| | | 62 | | |
| | | 63 | | return 0; |
| | | 64 | | } |
| | | 65 | | init => _itemDefId = value; |
| | | 66 | | } |
| | | 67 | | |
| | | 68 | | [JsonPropertyName("itemKey")] |
| | | 69 | | public string Key |
| | | 70 | | { |
| | | 71 | | get |
| | | 72 | | { |
| | | 73 | | if (!string.IsNullOrWhiteSpace(_key)) |
| | | 74 | | { |
| | | 75 | | return _key!; |
| | | 76 | | } |
| | | 77 | | |
| | | 78 | | if (_itemDefId > 0 && ItemCatalog.TryGetByItemDefId(_itemDefId, out var resolvedById) && resolvedById is not |
| | | 79 | | { |
| | | 80 | | return resolvedById.Key; |
| | | 81 | | } |
| | | 82 | | |
| | | 83 | | return string.Empty; |
| | | 84 | | } |
| | | 85 | | init => _key = value ?? string.Empty; |
| | | 86 | | } |
| | | 87 | | |
| | | 88 | | private string? _key; |
| | | 89 | | private int _itemDefId; |
| | | 90 | | } |
| | | 91 | | |
| | | 92 | | public sealed class GameState |
| | | 93 | | { |
| | | 94 | | public List<Item> Stash { get; } = []; |
| | | 95 | | |
| | | 96 | | public GameState(IEnumerable<Item> initialStash) |
| | | 97 | | { |
| | | 98 | | Stash.AddRange(initialStash); |
| | | 99 | | } |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | public sealed class RaidState |
| | | 103 | | { |
| | | 104 | | public int Health { get; set; } |
| | | 105 | | public int BackpackCapacity { get; set; } |
| | | 106 | | public int MaxEncumbrance |
| | | 107 | | { |
| | | 108 | | get => Inventory.MaxEncumbrance; |
| | | 109 | | set => Inventory.MaxEncumbrance = value; |
| | | 110 | | } |
| | | 111 | | public List<Item> BroughtItems { get; } = []; |
| | | 112 | | public RaidInventory Inventory { get; } |
| | | 113 | | public List<Item> RaidLoot => Inventory.CarriedItems; |
| | | 114 | | public bool IsDead => Health <= 0; |
| | | 115 | | |
| | | 116 | | public RaidState(int health, int backpackCapacity, List<Item> broughtItems, List<Item> raidLoot) |
| | | 117 | | { |
| | | 118 | | Health = health; |
| | | 119 | | BackpackCapacity = backpackCapacity; |
| | | 120 | | BroughtItems = broughtItems; |
| | | 121 | | Inventory = RaidInventory.FromItems(broughtItems, raidLoot, backpackCapacity); |
| | | 122 | | } |
| | | 123 | | |
| | | 124 | | public RaidState(int health, RaidInventory inventory) |
| | | 125 | | { |
| | | 126 | | Health = health; |
| | | 127 | | Inventory = inventory; |
| | | 128 | | BackpackCapacity = inventory.BackpackCapacity; |
| | | 129 | | BroughtItems = inventory.GetExtractableItems().ToList(); |
| | | 130 | | } |
| | | 131 | | } |
| | | 132 | | |
| | | 133 | | public sealed class RaidInventory |
| | | 134 | | { |
| | | 135 | | public Item? EquippedWeapon { get; set; } |
| | | 136 | | public Item? EquippedArmor { get; set; } |
| | | 137 | | public Item? EquippedBackpack { get; set; } |
| | | 138 | | public List<Item> CarriedItems { get; } = []; |
| | | 139 | | public List<Item> DiscoveredLoot { get; } = []; |
| | | 140 | | public int MedkitCount { get; set; } |
| | | 141 | | public int BackpackCapacity { get; set; } |
| | | 142 | | public int MaxEncumbrance { get; set; } = CombatBalance.GetMaxEncumbranceFromStrength(PlayerStatRules.MinimumScore); |
| | | 143 | | |
| | | 144 | | public IEnumerable<Item> GetExtractableItems() |
| | | 145 | | { |
| | | 146 | | if (EquippedWeapon is not null) |
| | | 147 | | { |
| | | 148 | | yield return EquippedWeapon; |
| | | 149 | | } |
| | | 150 | | |
| | | 151 | | if (EquippedArmor is not null) |
| | | 152 | | { |
| | | 153 | | yield return EquippedArmor; |
| | | 154 | | } |
| | | 155 | | |
| | | 156 | | if (EquippedBackpack is not null) |
| | | 157 | | { |
| | | 158 | | yield return EquippedBackpack; |
| | | 159 | | } |
| | | 160 | | |
| | | 161 | | foreach (var item in CarriedItems) |
| | | 162 | | { |
| | | 163 | | yield return item; |
| | | 164 | | } |
| | | 165 | | |
| | | 166 | | for (var i = 0; i < MedkitCount; i++) |
| | | 167 | | { |
| | | 168 | | yield return ItemCatalog.Create("Medkit"); |
| | | 169 | | } |
| | | 170 | | } |
| | | 171 | | |
| | | 172 | | public static RaidInventory FromItems(List<Item> broughtItems, List<Item> carriedItems, int backpackCapacity) |
| | | 173 | | { |
| | | 174 | | var inventory = new RaidInventory |
| | | 175 | | { |
| | | 176 | | EquippedWeapon = broughtItems.FirstOrDefault(x => x.Type == ItemType.Weapon), |
| | | 177 | | EquippedArmor = broughtItems.FirstOrDefault(x => x.Type == ItemType.Armor), |
| | | 178 | | EquippedBackpack = broughtItems.FirstOrDefault(x => x.Type == ItemType.Backpack), |
| | | 179 | | BackpackCapacity = backpackCapacity |
| | | 180 | | }; |
| | | 181 | | |
| | | 182 | | foreach (var item in broughtItems.Where(x => x.Type is not (ItemType.Weapon or ItemType.Armor or ItemType.Backpa |
| | | 183 | | { |
| | | 184 | | if (CombatBalance.IsMedkit(item)) |
| | | 185 | | { |
| | | 186 | | inventory.MedkitCount++; |
| | | 187 | | continue; |
| | | 188 | | } |
| | | 189 | | |
| | | 190 | | inventory.CarriedItems.Add(item); |
| | | 191 | | } |
| | | 192 | | |
| | | 193 | | foreach (var item in carriedItems) |
| | | 194 | | { |
| | | 195 | | if (CombatBalance.IsMedkit(item)) |
| | | 196 | | { |
| | | 197 | | inventory.MedkitCount++; |
| | | 198 | | continue; |
| | | 199 | | } |
| | | 200 | | |
| | | 201 | | inventory.CarriedItems.Add(item); |
| | | 202 | | } |
| | | 203 | | |
| | | 204 | | return inventory; |
| | | 205 | | } |
| | | 206 | | } |
| | | 207 | | |
| | | 208 | | public sealed record OpeningPhaseResult( |
| | | 209 | | OpeningContactState ContactState, |
| | | 210 | | OpeningSide SurpriseSide, |
| | | 211 | | OpeningSide InitiativeWinner, |
| | | 212 | | int OpeningActionsRemaining, |
| | | 213 | | bool SurprisePersistenceEligible); |