Expeditions rework (#18960)

This commit is contained in:
metalgearsloth
2023-09-19 22:52:01 +10:00
committed by GitHub
parent 86fa8ae180
commit 036b9ef74f
40 changed files with 774 additions and 1097 deletions

View File

@@ -24,7 +24,7 @@ public sealed class SalvageAirMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f;
/// <inheritdoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null;
/// <summary>

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Salvage.Expeditions.Modifiers;
/// Affects the biome to be used for salvage.
/// </summary>
[Prototype("salvageBiomeMod")]
public sealed class SalvageBiomeMod : IPrototype, ISalvageMod
public sealed class SalvageBiomeModPrototype : IPrototype, ISalvageMod
{
[IdDataField] public string ID { get; } = default!;

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Salvage.Expeditions.Modifiers;
[Prototype("salvageDungeonMod")]
public sealed class SalvageDungeonMod : IPrototype, IBiomeSpecificMod
public sealed class SalvageDungeonModPrototype : IPrototype, IBiomeSpecificMod
{
[IdDataField] public string ID { get; } = default!;
@@ -17,7 +17,7 @@ public sealed class SalvageDungeonMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f;
/// <inheridoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null;
/// <summary>

View File

@@ -15,7 +15,7 @@ public sealed class SalvageLightMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f;
/// <inheritdoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null;
[DataField("color", required: true)] public Color? Color;

View File

@@ -16,7 +16,7 @@ public sealed class SalvageTemperatureMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f;
/// <inheritdoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null;
/// <summary>

View File

@@ -1,23 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Salvage.Expeditions.Modifiers;
[Prototype("salvageTimeMod")]
public sealed class SalvageTimeMod : IPrototype, ISalvageMod
{
[IdDataField] public string ID { get; } = default!;
[DataField("desc")] public string Description { get; private set; } = string.Empty;
/// <summary>
/// Cost for difficulty modifiers.
/// </summary>
[DataField("cost")]
public float Cost { get; private set; }
[DataField("minDuration")]
public int MinDuration = 630;
[DataField("maxDuration")]
public int MaxDuration = 570;
}

View File

@@ -17,7 +17,7 @@ public sealed class SalvageWeatherMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f;
/// <inheritdoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null;
/// <summary>

View File

@@ -72,28 +72,14 @@ public sealed partial class SalvageExpeditionDataComponent : Component
}
[Serializable, NetSerializable]
public sealed record SalvageMissionParams : IComparable<SalvageMissionParams>
public sealed record SalvageMissionParams
{
[ViewVariables]
public ushort Index;
[ViewVariables(VVAccess.ReadWrite)]
public SalvageMissionType MissionType;
[ViewVariables(VVAccess.ReadWrite)] public int Seed;
/// <summary>
/// Base difficulty for this mission.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public DifficultyRating Difficulty;
public int CompareTo(SalvageMissionParams? other)
{
if (other == null)
return -1;
return Difficulty.CompareTo(other.Difficulty);
}
public string Difficulty = string.Empty;
}
/// <summary>
@@ -102,16 +88,13 @@ public sealed record SalvageMissionParams : IComparable<SalvageMissionParams>
/// </summary>
public sealed record SalvageMission(
int Seed,
DifficultyRating Difficulty,
string Dungeon,
string Faction,
SalvageMissionType Mission,
string Biome,
string Air,
float Temperature,
Color? Color,
TimeSpan Duration,
List<string> Rewards,
List<string> Modifiers)
{
/// <summary>
@@ -120,12 +103,7 @@ public sealed record SalvageMission(
public readonly int Seed = Seed;
/// <summary>
/// Difficulty rating.
/// </summary>
public DifficultyRating Difficulty = Difficulty;
/// <summary>
/// <see cref="SalvageDungeonMod"/> to be used.
/// <see cref="SalvageDungeonModPrototype"/> to be used.
/// </summary>
public readonly string Dungeon = Dungeon;
@@ -134,11 +112,6 @@ public sealed record SalvageMission(
/// </summary>
public readonly string Faction = Faction;
/// <summary>
/// Underlying mission params that generated this.
/// </summary>
public readonly SalvageMissionType Mission = Mission;
/// <summary>
/// Biome to be used for the mission.
/// </summary>
@@ -164,11 +137,6 @@ public sealed record SalvageMission(
/// </summary>
public TimeSpan Duration = Duration;
/// <summary>
/// The list of items to order on mission completion.
/// </summary>
public List<string> Rewards = Rewards;
/// <summary>
/// Modifiers (outside of the above) applied to the mission.
/// </summary>

View File

@@ -5,20 +5,14 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Salvage.Expeditions;
[Prototype("salvageFaction")]
public sealed class SalvageFactionPrototype : IPrototype, ISalvageMod
public sealed class SalvageFactionPrototype : IPrototype
{
[IdDataField] public string ID { get; } = default!;
[DataField("desc")] public string Description { get; private set; } = string.Empty;
/// <summary>
/// Cost for difficulty modifiers.
/// </summary>
[DataField("cost")]
public float Cost { get; private set; } = 0f;
[ViewVariables(VVAccess.ReadWrite), DataField("groups", required: true)]
public List<SalvageMobGroup> MobGroups = default!;
[ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)]
public List<SalvageMobEntry> MobGroups = new();
/// <summary>
/// Miscellaneous data for factions.

View File

@@ -0,0 +1,24 @@
using Content.Shared.Random;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Salvage.Expeditions;
[DataDefinition]
public partial record struct SalvageMobEntry() : IBudgetEntry
{
/// <summary>
/// Cost for this mob in a budget.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("cost")]
public float Cost { get; set; } = 1f;
/// <summary>
/// Probability to spawn this mob. Summed with everything else for the faction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("prob")]
public float Prob { get; set; } = 1f;
[ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Proto { get; set; } = string.Empty;
}

View File

@@ -1,18 +0,0 @@
using Content.Shared.Storage;
namespace Content.Shared.Salvage.Expeditions;
[DataDefinition]
public partial record struct SalvageMobGroup()
{
// A mob may be cheap but rare or expensive but frequent.
/// <summary>
/// Probability to spawn this group. Summed with everything else for the faction.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("prob")]
public float Prob = 1f;
[ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)]
public List<EntitySpawnEntry> Entries = new();
}

View File

@@ -1,9 +1,13 @@
using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.Procedural;
using Content.Shared.Procedural.Loot;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
@@ -13,73 +17,14 @@ namespace Content.Shared.Salvage;
public abstract class SharedSalvageSystem : EntitySystem
{
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] protected readonly IConfigurationManager CfgManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
#region Descriptions
public string GetMissionDescription(SalvageMission mission)
{
// Hardcoded in coooooz it's dynamic based on difficulty and I'm lazy.
switch (mission.Mission)
{
case SalvageMissionType.Mining:
// Taxation: , ("tax", $"{GetMiningTax(mission.Difficulty) * 100f:0}")
return Loc.GetString("salvage-expedition-desc-mining");
case SalvageMissionType.Destruction:
var proto = _proto.Index<SalvageFactionPrototype>(mission.Faction).Configs["DefenseStructure"];
return Loc.GetString("salvage-expedition-desc-structure",
("count", GetStructureCount(mission.Difficulty)),
("structure", _loc.GetEntityData(proto).Name));
case SalvageMissionType.Elimination:
return Loc.GetString("salvage-expedition-desc-elimination");
default:
throw new NotImplementedException();
}
}
public float GetMiningTax(DifficultyRating baseRating)
{
return 0.6f + (int) baseRating * 0.05f;
}
/// <summary>
/// Gets the amount of structures to destroy.
/// Main loot table for salvage expeditions.
/// </summary>
public int GetStructureCount(DifficultyRating baseRating)
{
return 1 + (int) baseRating * 2;
}
#endregion
public int GetDifficulty(DifficultyRating rating)
{
switch (rating)
{
case DifficultyRating.Minimal:
return 1;
case DifficultyRating.Minor:
return 2;
case DifficultyRating.Moderate:
return 4;
case DifficultyRating.Hazardous:
return 8;
case DifficultyRating.Extreme:
return 16;
default:
throw new ArgumentOutOfRangeException(nameof(rating), rating, null);
}
}
/// <summary>
/// How many groups of mobs to spawn for a mission.
/// </summary>
public float GetSpawnCount(DifficultyRating difficulty)
{
return (int) difficulty * 2;
}
[ValidatePrototypeId<SalvageLootPrototype>]
public const string ExpeditionsLootProto = "SalvageLoot";
public static string GetFTLName(DatasetPrototype dataset, int seed)
{
@@ -87,51 +32,45 @@ public abstract class SharedSalvageSystem : EntitySystem
return $"{dataset.Values[random.Next(dataset.Values.Count)]}-{random.Next(10, 100)}-{(char) (65 + random.Next(26))}";
}
public SalvageMission GetMission(SalvageMissionType config, DifficultyRating difficulty, int seed)
public SalvageMission GetMission(SalvageDifficultyPrototype difficulty, int seed)
{
// This is on shared to ensure the client display for missions and what the server generates are consistent
var rating = (float) GetDifficulty(difficulty);
// Don't want easy missions to have any negative modifiers but also want
// easy to be a 1 for difficulty.
rating -= 1f;
var modifierBudget = difficulty.ModifierBudget;
var rand = new System.Random(seed);
var faction = GetMod<SalvageFactionPrototype>(rand, ref rating);
var biome = GetMod<SalvageBiomeMod>(rand, ref rating);
var dungeon = GetBiomeMod<SalvageDungeonMod>(biome.ID, rand, ref rating);
// Run budget in order of priority
// - Biome
// - Lighting
// - Atmos
var biome = GetMod<SalvageBiomeModPrototype>(rand, ref modifierBudget);
var light = GetBiomeMod<SalvageLightMod>(biome.ID, rand, ref modifierBudget);
var temp = GetBiomeMod<SalvageTemperatureMod>(biome.ID, rand, ref modifierBudget);
var air = GetBiomeMod<SalvageAirMod>(biome.ID, rand, ref modifierBudget);
var dungeon = GetBiomeMod<SalvageDungeonModPrototype>(biome.ID, rand, ref modifierBudget);
var factionProtos = _proto.EnumeratePrototypes<SalvageFactionPrototype>().ToList();
var faction = factionProtos[rand.Next(factionProtos.Count)];
var mods = new List<string>();
var air = GetBiomeMod<SalvageAirMod>(biome.ID, rand, ref rating);
if (air.Description != string.Empty)
{
mods.Add(air.Description);
}
// only show the description if there is an atmosphere since wont matter otherwise
var temp = GetBiomeMod<SalvageTemperatureMod>(biome.ID, rand, ref rating);
if (temp.Description != string.Empty && !air.Space)
{
mods.Add(temp.Description);
}
var light = GetBiomeMod<SalvageLightMod>(biome.ID, rand, ref rating);
if (light.Description != string.Empty)
{
mods.Add(light.Description);
}
var time = GetMod<SalvageTimeMod>(rand, ref rating);
// Round the duration to nearest 15 seconds.
var exactDuration = MathHelper.Lerp(time.MinDuration, time.MaxDuration, rand.NextFloat());
exactDuration = MathF.Round(exactDuration / 15f) * 15f;
var duration = TimeSpan.FromSeconds(exactDuration);
var duration = TimeSpan.FromSeconds(CfgManager.GetCVar(CCVars.SalvageExpeditionDuration));
if (time.Description != string.Empty)
{
mods.Add(time.Description);
}
var rewards = GetRewards(difficulty, rand);
return new SalvageMission(seed, difficulty, dungeon.ID, faction.ID, config, biome.ID, air.ID, temp.Temperature, light.Color, duration, rewards, mods);
return new SalvageMission(seed, dungeon.ID, faction.ID, biome.ID, air.ID, temp.Temperature, light.Color, duration, mods);
}
public T GetBiomeMod<T>(string biome, System.Random rand, ref float rating) where T : class, IPrototype, IBiomeSpecificMod
@@ -171,72 +110,5 @@ public abstract class SharedSalvageSystem : EntitySystem
throw new InvalidOperationException();
}
private List<string> GetRewards(DifficultyRating difficulty, System.Random rand)
{
var rewards = new List<string>(3);
var ids = RewardsForDifficulty(difficulty);
foreach (var id in ids)
{
// pick a random reward to give
var weights = _proto.Index<WeightedRandomEntityPrototype>(id);
rewards.Add(weights.Pick(rand));
}
return rewards;
}
/// <summary>
/// Get a list of WeightedRandomEntityPrototype IDs with the rewards for a certain difficulty.
/// </summary>
private string[] RewardsForDifficulty(DifficultyRating rating)
{
var common = "SalvageRewardCommon";
var rare = "SalvageRewardRare";
var epic = "SalvageRewardEpic";
switch (rating)
{
case DifficultyRating.Minimal:
return new string[] { common, common, common };
case DifficultyRating.Minor:
return new string[] { common, common, rare };
case DifficultyRating.Moderate:
return new string[] { common, rare, rare };
case DifficultyRating.Hazardous:
return new string[] { rare, rare, rare, epic };
case DifficultyRating.Extreme:
return new string[] { rare, rare, epic, epic, epic };
default:
throw new NotImplementedException();
}
}
}
[Serializable, NetSerializable]
public enum SalvageMissionType : byte
{
/// <summary>
/// No dungeon, just ore loot and random mob spawns.
/// </summary>
Mining,
/// <summary>
/// Destroy the specified structures in a dungeon.
/// </summary>
Destruction,
/// <summary>
/// Kill a large creature in a dungeon.
/// </summary>
Elimination,
}
[Serializable, NetSerializable]
public enum DifficultyRating : byte
{
Minimal,
Minor,
Moderate,
Hazardous,
Extreme,
}