Salvage expeditions (#12745)
This commit is contained in:
@@ -1,77 +1,235 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
using Content.Shared.Procedural.Rewards;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Salvage.Expeditions.Structure;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
public abstract class SharedSalvageSystem : EntitySystem
|
||||
{
|
||||
public static readonly TimeSpan MissionCooldown = TimeSpan.FromMinutes(5);
|
||||
public static readonly TimeSpan MissionFailedCooldown = TimeSpan.FromMinutes(10);
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public static float GetDifficultyModifier(DifficultyRating difficulty)
|
||||
public static readonly TimeSpan MissionCooldown = TimeSpan.FromMinutes(5);
|
||||
public static readonly TimeSpan MissionFailedCooldown = TimeSpan.FromMinutes(15);
|
||||
|
||||
#region Descriptions
|
||||
|
||||
public string GetMissionDescription(SalvageMission mission)
|
||||
{
|
||||
// These should reflect how many salvage staff are expected to be required for the mission.
|
||||
switch (difficulty)
|
||||
// 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));
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public float GetMiningTax(DifficultyRating baseRating)
|
||||
{
|
||||
return 0.6f + (int) baseRating * 0.05f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of structures to destroy.
|
||||
/// </summary>
|
||||
public int GetStructureCount(DifficultyRating baseRating)
|
||||
{
|
||||
return 1 + (int) baseRating * 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public int GetDifficulty(DifficultyRating rating)
|
||||
{
|
||||
switch (rating)
|
||||
{
|
||||
case DifficultyRating.None:
|
||||
return 1f;
|
||||
return 1;
|
||||
case DifficultyRating.Minor:
|
||||
return 1.5f;
|
||||
return 2;
|
||||
case DifficultyRating.Moderate:
|
||||
return 3f;
|
||||
return 4;
|
||||
case DifficultyRating.Hazardous:
|
||||
return 6f;
|
||||
return 6;
|
||||
case DifficultyRating.Extreme:
|
||||
return 10f;
|
||||
return 8;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null);
|
||||
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;
|
||||
}
|
||||
|
||||
public static string GetFTLName(DatasetPrototype dataset, int seed)
|
||||
{
|
||||
var random = new System.Random(seed);
|
||||
return $"{dataset.Values[random.Next(dataset.Values.Count)]}-{random.Next(10, 100)}-{(char) (65 + random.Next(26))}";
|
||||
}
|
||||
|
||||
public static string GetFaction(List<string> factions, int seed)
|
||||
public SalvageMission GetMission(SalvageMissionType config, DifficultyRating difficulty, int seed)
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 1);
|
||||
return factions[adjustedSeed.Next(factions.Count)];
|
||||
// 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 rand = new System.Random(seed);
|
||||
var faction = GetMod<SalvageFactionPrototype>(rand, ref rating);
|
||||
var biome = GetMod<SalvageBiomeMod>(rand, ref rating);
|
||||
var dungeon = GetDungeon(biome.ID, rand, ref rating);
|
||||
var mods = new List<string>();
|
||||
|
||||
SalvageLightMod? light = null;
|
||||
|
||||
if (biome.BiomePrototype != null)
|
||||
{
|
||||
light = GetLight(biome.ID, rand, ref rating);
|
||||
mods.Add(light.Description);
|
||||
}
|
||||
|
||||
var time = GetMod<SalvageTimeMod>(rand, ref rating);
|
||||
// Round the duration to nearest 15 seconds.
|
||||
var exactDuration = time.MinDuration + (time.MaxDuration - time.MinDuration) * rand.NextFloat();
|
||||
exactDuration = MathF.Round(exactDuration / 15f) * 15f;
|
||||
var duration = TimeSpan.FromSeconds(exactDuration);
|
||||
|
||||
if (time.ID != "StandardTime")
|
||||
{
|
||||
mods.Add(time.Description);
|
||||
}
|
||||
|
||||
var loots = GetLoot(config, _proto.EnumeratePrototypes<SalvageLootPrototype>().ToList(), GetDifficulty(difficulty), seed);
|
||||
return new SalvageMission(seed, difficulty, dungeon.ID, faction.ID, config, biome.ID, light?.Color, duration, loots, mods);
|
||||
}
|
||||
|
||||
public static IEnumerable<SalvageLootPrototype> GetLoot(List<string> loots, int seed, IPrototypeManager protoManager)
|
||||
public SalvageDungeonMod GetDungeon(string biome, System.Random rand, ref float rating)
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<SalvageDungeonMod>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.BiomeMods?.Contains(biome) == false ||
|
||||
mod.Cost > rating)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rating -= (int) mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public SalvageLightMod GetLight(string biome, System.Random rand, ref float rating)
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<SalvageLightMod>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.Biomes?.Contains(biome) == false || mod.Cost > rating)
|
||||
continue;
|
||||
|
||||
rating -= mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public T GetMod<T>(System.Random rand, ref float rating) where T : class, IPrototype, ISalvageMod
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<T>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.Cost > rating)
|
||||
continue;
|
||||
|
||||
rating -= mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetLoot(SalvageMissionType mission, List<SalvageLootPrototype> loots, int count, int seed)
|
||||
{
|
||||
var results = new Dictionary<string, int>();
|
||||
var adjustedSeed = new System.Random(seed + 2);
|
||||
|
||||
for (var i = 0; i < loots.Count; i++)
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var loot = loots[i];
|
||||
var a = protoManager.Index<WeightedRandomPrototype>(loot);
|
||||
var lootConfig = a.Pick(adjustedSeed);
|
||||
yield return protoManager.Index<SalvageLootPrototype>(lootConfig);
|
||||
adjustedSeed.Shuffle(loots);
|
||||
|
||||
foreach (var loot in loots)
|
||||
{
|
||||
if (loot.Blacklist.Contains(mission))
|
||||
continue;
|
||||
|
||||
var weh = results.GetOrNew(loot.ID);
|
||||
weh++;
|
||||
results[loot.ID] = weh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static ISalvageReward GetReward(WeightedRandomPrototype proto, int seed, IPrototypeManager protoManager)
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 3);
|
||||
var rewardProto = proto.Pick(adjustedSeed);
|
||||
return protoManager.Index<SalvageRewardPrototype>(rewardProto).Reward;
|
||||
}
|
||||
|
||||
#region Structure
|
||||
|
||||
public static int GetStructureCount(SalvageStructure structure, int seed)
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 4);
|
||||
return adjustedSeed.Next(structure.MinStructures, structure.MaxStructures + 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[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,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DifficultyRating : byte
|
||||
{
|
||||
None,
|
||||
Minor,
|
||||
Moderate,
|
||||
Hazardous,
|
||||
Extreme,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user