]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Expeditions rework (#18960)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Tue, 19 Sep 2023 12:52:01 +0000 (22:52 +1000)
committerGitHub <noreply@github.com>
Tue, 19 Sep 2023 12:52:01 +0000 (22:52 +1000)
40 files changed:
Content.Client/Salvage/UI/SalvageExpeditionWindow.xaml.cs
Content.Server/Procedural/DungeonJob.PostGen.cs
Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs [deleted file]
Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs
Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs [deleted file]
Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs [deleted file]
Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs
Content.Server/Salvage/SalvageSystem.Expeditions.cs
Content.Server/Salvage/SalvageSystem.Runner.cs
Content.Server/Salvage/SalvageSystem.cs
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Content.Shared/CCVar/CCVars.cs
Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs [new file with mode: 0644]
Content.Shared/Procedural/Loot/SalvageLootPrototype.cs
Content.Shared/Procedural/SalvageDifficultyPrototype.cs [new file with mode: 0644]
Content.Shared/Random/IBudgetEntry.cs [new file with mode: 0644]
Content.Shared/Random/RandomSystem.cs [new file with mode: 0644]
Content.Shared/Salvage/Expeditions/Modifiers/SalvageAirMod.cs
Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs [moved from Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeMod.cs with 92% similarity]
Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonModPrototype.cs [moved from Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonMod.cs with 88% similarity]
Content.Shared/Salvage/Expeditions/Modifiers/SalvageLightMod.cs
Content.Shared/Salvage/Expeditions/Modifiers/SalvageTemperatureMod.cs
Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs [deleted file]
Content.Shared/Salvage/Expeditions/Modifiers/SalvageWeatherMod.cs
Content.Shared/Salvage/Expeditions/SalvageExpeditions.cs
Content.Shared/Salvage/Expeditions/SalvageFactionPrototype.cs
Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs [new file with mode: 0644]
Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs [deleted file]
Content.Shared/Salvage/SharedSalvageSystem.cs
Resources/ConfigPresets/Build/development.toml
Resources/Locale/en-US/procedural/expeditions.ftl
Resources/Prototypes/Entities/Mobs/NPCs/carp.yml
Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
Resources/Prototypes/Entities/Mobs/Player/dragon.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml
Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml
Resources/Prototypes/Procedural/salvage_difficulties.yml [new file with mode: 0644]
Resources/Prototypes/Procedural/salvage_factions.yml
Resources/Prototypes/Procedural/salvage_loot.yml
Resources/Prototypes/Procedural/salvage_mods.yml

index 150f4a295737429e3563938fbe39a096e1773bad..7875ac6ef042c95068ae029d8b7922097bf49276 100644 (file)
@@ -4,6 +4,7 @@ using Content.Client.Stylesheets;
 using Content.Client.UserInterface.Controls;
 using Content.Shared.CCVar;
 using Content.Shared.Parallax.Biomes;
+using Content.Shared.Procedural;
 using Content.Shared.Salvage;
 using Content.Shared.Salvage.Expeditions;
 using Content.Shared.Salvage.Expeditions.Modifiers;
@@ -53,8 +54,10 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
         for (var i = 0; i < state.Missions.Count; i++)
         {
             var missionParams = state.Missions[i];
-            var config = missionParams.MissionType;
-            var mission = _salvage.GetMission(missionParams.MissionType, missionParams.Difficulty, missionParams.Seed);
+            var difficultyId = "Moderate";
+            var difficultyProto = _prototype.Index<SalvageDifficultyPrototype>(difficultyId);
+            // TODO: Selectable difficulty soon.
+            var mission = _salvage.GetMission(difficultyProto, missionParams.Seed);
 
             // Mission title
             var missionStripe = new StripeBack()
@@ -64,7 +67,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
 
             missionStripe.AddChild(new Label()
             {
-                Text = Loc.GetString($"salvage-expedition-type-{config.ToString()}"),
+                Text = Loc.GetString($"salvage-expedition-type"),
                 HorizontalAlignment = HAlignment.Center,
                 Margin = new Thickness(0f, 5f, 0f, 5f),
             });
@@ -81,48 +84,25 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
                 Text = Loc.GetString("salvage-expedition-window-difficulty")
             });
 
-            Color difficultyColor;
-
-            switch (missionParams.Difficulty)
-            {
-                case DifficultyRating.Minimal:
-                    difficultyColor = Color.FromHex("#52B4E996");
-                    break;
-                case DifficultyRating.Minor:
-                    difficultyColor = Color.FromHex("#9FED5896");
-                    break;
-                case DifficultyRating.Moderate:
-                    difficultyColor = Color.FromHex("#EFB34196");
-                    break;
-                case DifficultyRating.Hazardous:
-                    difficultyColor = Color.FromHex("#DE3A3A96");
-                    break;
-                case DifficultyRating.Extreme:
-                    difficultyColor = Color.FromHex("#D381C996");
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException();
-            }
+            var difficultyColor = difficultyProto.Color;
 
             lBox.AddChild(new Label
             {
-                Text = Loc.GetString($"salvage-expedition-difficulty-{missionParams.Difficulty.ToString()}"),
+                Text = Loc.GetString("salvage-expedition-difficulty-Moderate"),
                 FontColorOverride = difficultyColor,
                 HorizontalAlignment = HAlignment.Left,
                 Margin = new Thickness(0f, 0f, 0f, 5f),
             });
 
-            // Details
-            var details = _salvage.GetMissionDescription(mission);
-
             lBox.AddChild(new Label
             {
-                Text = Loc.GetString("salvage-expedition-window-details")
+                Text = Loc.GetString("salvage-expedition-difficulty-players"),
+                HorizontalAlignment = HAlignment.Left,
             });
 
             lBox.AddChild(new Label
             {
-                Text = details,
+                Text = difficultyProto.RecommendedPlayers.ToString(),
                 FontColorOverride = StyleNano.NanoGold,
                 HorizontalAlignment = HAlignment.Left,
                 Margin = new Thickness(0f, 0f, 0f, 5f),
@@ -168,7 +148,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
 
             lBox.AddChild(new Label
             {
-                Text = Loc.GetString(_prototype.Index<SalvageBiomeMod>(biome).ID),
+                Text = Loc.GetString(_prototype.Index<SalvageBiomeModPrototype>(biome).ID),
                 FontColorOverride = StyleNano.NanoGold,
                 HorizontalAlignment = HAlignment.Left,
                 Margin = new Thickness(0f, 0f, 0f, 5f),
@@ -190,29 +170,6 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
                 Margin = new Thickness(0f, 0f, 0f, 5f),
             });
 
-            lBox.AddChild(new Label()
-            {
-                Text = Loc.GetString("salvage-expedition-window-rewards")
-            });
-
-            var rewards = new Dictionary<string, int>();
-            foreach (var reward in mission.Rewards)
-            {
-                var name = _prototype.Index<EntityPrototype>(reward).Name;
-                var count = rewards.GetOrNew(name);
-                count++;
-                rewards[name] = count;
-            }
-
-            // there will always be 3 or more rewards so no need for 0 check
-            lBox.AddChild(new Label()
-            {
-                Text = string.Join("\n", rewards.Select(o => "- " + o.Key + (o.Value > 1 ? $" x {o.Value}" : ""))).TrimEnd(),
-                FontColorOverride = StyleNano.ConcerningOrangeFore,
-                HorizontalAlignment = HAlignment.Left,
-                Margin = new Thickness(0f, 0f, 0f, 5f)
-            });
-
             // Claim
             var claimButton = new Button()
             {
@@ -289,7 +246,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
         else
         {
             var cooldown = _cooldown
-                ? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionFailedCooldown))
+                ? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown))
                 : TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown));
 
             NextOfferBar.Value = 1f - (float) (remaining / cooldown);
index 1bbcc438ef59ab21ccd917b4f2622acf54a3e928..4718de7c0c0e61502713ec79f3e401020bcc52ef 100644 (file)
@@ -83,17 +83,14 @@ public sealed partial class DungeonJob
         var lastDirection = new Dictionary<Vector2i, Direction>();
         costSoFar[start] = 0f;
         lastDirection[start] = Direction.Invalid;
-        var tagQuery = _entManager.GetEntityQuery<TagComponent>();
 
-        // TODO:
-        // Pick a random node to start
-        // Then, dijkstra out from it. Add like +10 if it's a wall or smth
-        // When we hit another cable then mark it as found and iterate cameFrom and add to the thingie.
         while (remaining.Count > 0)
         {
             if (frontier.Count == 0)
             {
-                frontier.Enqueue(remaining.First(), 0f);
+                var newStart = remaining.First();
+                frontier.Enqueue(newStart, 0f);
+                lastDirection[newStart] = Direction.Invalid;
             }
 
             var node = frontier.Dequeue();
diff --git a/Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs
deleted file mode 100644 (file)
index a3ec66f..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.Salvage;
-
-namespace Content.Server.Salvage.Expeditions.Structure;
-
-/// <summary>
-/// Tracks expedition data for <see cref="SalvageMissionType.Elimination"/>
-/// </summary>
-[RegisterComponent, Access(typeof(SalvageSystem), typeof(SpawnSalvageMissionJob))]
-public sealed partial class SalvageEliminationExpeditionComponent : Component
-{
-    /// <summary>
-    /// List of mobs that need to be killed for the mission to be complete.
-    /// </summary>
-    [DataField("megafauna")]
-    public List<EntityUid> Megafauna = new();
-}
index ee4b7c88cd6b0a12f4b02d300696d07649f6cff3..2bc00397bc9bf80edd6c95fac35feaa658d208f4 100644 (file)
@@ -49,16 +49,4 @@ public sealed partial class SalvageExpeditionComponent : SharedSalvageExpedition
     {
         Params = AudioParams.Default.WithVolume(-5),
     };
-
-    /// <summary>
-    /// The difficulty this mission had or, in the future, was selected.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField("difficulty")]
-    public DifficultyRating Difficulty;
-
-    /// <summary>
-    /// List of items to order on mission completion
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField("rewards", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
-    public List<string> Rewards = default!;
 }
diff --git a/Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs
deleted file mode 100644 (file)
index b9a1379..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.Salvage;
-
-namespace Content.Server.Salvage.Expeditions;
-
-/// <summary>
-/// Tracks expedition data for <see cref="SalvageMissionType.Mining"/>
-/// </summary>
-[RegisterComponent, Access(typeof(SalvageSystem))]
-public sealed partial class SalvageMiningExpeditionComponent : Component
-{
-    /// <summary>
-    /// Entities that were present on the shuttle and match the loot tax.
-    /// </summary>
-    [DataField("exemptEntities")]
-    public List<EntityUid> ExemptEntities = new();
-}
diff --git a/Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs
deleted file mode 100644 (file)
index 0dec1f8..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Shared.Salvage;
-
-namespace Content.Server.Salvage.Expeditions.Structure;
-
-/// <summary>
-/// Tracks expedition data for <see cref="SalvageMissionType.Structure"/>
-/// </summary>
-[RegisterComponent, Access(typeof(SalvageSystem), typeof(SpawnSalvageMissionJob))]
-public sealed partial class SalvageStructureExpeditionComponent : Component
-{
-    [DataField("structures")]
-    public List<EntityUid> Structures = new();
-}
index c814e9c36b7c1c1b31c7359f30d078f4b2032451..d4241b2baf25a4385f70ec53099afa8f1bdfc27f 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Procedural;
 using Content.Shared.Salvage;
 using Content.Shared.Salvage.Expeditions;
 
@@ -18,7 +19,7 @@ public sealed partial class SalvageSystem
         SpawnMission(missionparams, station.Value);
 
         data.ActiveMission = args.Index;
-        var mission = GetMission(missionparams.MissionType, missionparams.Difficulty, missionparams.Seed);
+        var mission = GetMission(_prototypeManager.Index<SalvageDifficultyPrototype>(missionparams.Difficulty), missionparams.Seed);
         data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1);
         UpdateConsoles(data);
     }
index a1ac37062b2d0389f783f4b92f31b7a10470c3b2..6021cb62360af067dc4337bcd0599b9d6b706bc6 100644 (file)
@@ -8,6 +8,7 @@ using Robust.Shared.CPUJob.JobQueues;
 using Robust.Shared.CPUJob.JobQueues.Queues;
 using System.Linq;
 using System.Threading;
+using Content.Shared.Procedural;
 using Content.Shared.Salvage.Expeditions;
 using Robust.Shared.GameStates;
 
@@ -26,7 +27,6 @@ public sealed partial class SalvageSystem
     private const double SalvageJobTime = 0.002;
 
     private float _cooldown;
-    private float _failedCooldown;
 
     private void InitializeExpeditions()
     {
@@ -43,9 +43,7 @@ public sealed partial class SalvageSystem
         SubscribeLocalEvent<SalvageStructureComponent, ExaminedEvent>(OnStructureExamine);
 
         _cooldown = _configurationManager.GetCVar(CCVars.SalvageExpeditionCooldown);
-        _failedCooldown = _configurationManager.GetCVar(CCVars.SalvageExpeditionFailedCooldown);
         _configurationManager.OnValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange);
-        _configurationManager.OnValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange);
     }
 
     private void OnExpeditionGetState(EntityUid uid, SalvageExpeditionComponent component, ref ComponentGetState args)
@@ -59,7 +57,6 @@ public sealed partial class SalvageSystem
     private void ShutdownExpeditions()
     {
         _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange);
-        _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange);
     }
 
     private void SetCooldownChange(float obj)
@@ -77,20 +74,6 @@ public sealed partial class SalvageSystem
         _cooldown = obj;
     }
 
-    private void SetFailedCooldownChange(float obj)
-    {
-        var diff = obj - _failedCooldown;
-
-        var query = AllEntityQuery<SalvageExpeditionDataComponent>();
-
-        while (query.MoveNext(out var comp))
-        {
-            comp.NextOffer += TimeSpan.FromSeconds(diff);
-        }
-
-        _failedCooldown = obj;
-    }
-
     private void OnExpeditionShutdown(EntityUid uid, SalvageExpeditionComponent component, ComponentShutdown args)
     {
         component.Stream?.Stop();
@@ -110,7 +93,7 @@ public sealed partial class SalvageSystem
         // Finish mission
         if (TryComp<SalvageExpeditionDataComponent>(component.Station, out var data))
         {
-            FinishExpedition(data, uid, component, null);
+            FinishExpedition(data, uid);
         }
     }
 
@@ -152,109 +135,29 @@ public sealed partial class SalvageSystem
         }
     }
 
-    private void FinishExpedition(SalvageExpeditionDataComponent component, EntityUid uid, SalvageExpeditionComponent expedition, EntityUid? shuttle)
+    private void FinishExpedition(SalvageExpeditionDataComponent component, EntityUid uid)
     {
-        // Finish mission cleanup.
-        switch (expedition.MissionParams.MissionType)
-        {
-            // Handles the mining taxation.
-            case SalvageMissionType.Mining:
-                expedition.Completed = true;
-
-                if (shuttle != null && TryComp<SalvageMiningExpeditionComponent>(uid, out var mining))
-                {
-                    var xformQuery = GetEntityQuery<TransformComponent>();
-                    var entities = new List<EntityUid>();
-                    MiningTax(entities, shuttle.Value, mining, xformQuery);
-
-                    var tax = GetMiningTax(expedition.MissionParams.Difficulty);
-                    _random.Shuffle(entities);
-
-                    // TODO: urgh this pr is already taking so long I'll do this later
-                    for (var i = 0; i < Math.Ceiling(entities.Count * tax); i++)
-                    {
-                        // QueueDel(entities[i]);
-                    }
-                }
-
-                break;
-        }
-
-        // Handle payout after expedition has finished
-        if (expedition.Completed)
-        {
-            Log.Debug($"Completed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
-            component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown);
-            Announce(uid, Loc.GetString("salvage-expedition-mission-completed"));
-            GiveRewards(expedition);
-        }
-        else
-        {
-            Log.Debug($"Failed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
-            component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_failedCooldown);
-            Announce(uid, Loc.GetString("salvage-expedition-mission-failed"));
-        }
-
+        component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown);
+        Announce(uid, Loc.GetString("salvage-expedition-mission-completed"));
         component.ActiveMission = 0;
         component.Cooldown = true;
         UpdateConsoles(component);
     }
 
-    /// <summary>
-    /// Deducts ore tax for mining.
-    /// </summary>
-    private void MiningTax(List<EntityUid> entities, EntityUid entity, SalvageMiningExpeditionComponent mining, EntityQuery<TransformComponent> xformQuery)
-    {
-        if (!mining.ExemptEntities.Contains(entity))
-        {
-            entities.Add(entity);
-        }
-
-        var xform = xformQuery.GetComponent(entity);
-        var children = xform.ChildEnumerator;
-
-        while (children.MoveNext(out var child))
-        {
-            MiningTax(entities, child.Value, mining, xformQuery);
-        }
-    }
-
     private void GenerateMissions(SalvageExpeditionDataComponent component)
     {
         component.Missions.Clear();
-        var configs = Enum.GetValues<SalvageMissionType>().ToList();
-
-        // Temporarily removed coz it SUCKS
-        configs.Remove(SalvageMissionType.Mining);
-
-        // this doesn't support having more missions than types of ratings
-        // but the previous system didn't do that either.
-        var allDifficulties = Enum.GetValues<DifficultyRating>();
-        _random.Shuffle(allDifficulties);
-        var difficulties = allDifficulties.Take(MissionLimit).ToList();
-        difficulties.Sort();
-
-        if (configs.Count == 0)
-            return;
 
         for (var i = 0; i < MissionLimit; i++)
         {
-            _random.Shuffle(configs);
-            var rating = difficulties[i];
-
-            foreach (var config in configs)
+            var mission = new SalvageMissionParams
             {
-                var mission = new SalvageMissionParams
-                {
-                    Index = component.NextIndex,
-                    MissionType = config,
-                    Seed = _random.Next(),
-                    Difficulty = rating,
-                };
-
-                component.Missions[component.NextIndex++] = mission;
-                break;
-            }
+                Index = component.NextIndex,
+                Seed = _random.Next(),
+                Difficulty = "Moderate",
+            };
+
+            component.Missions[component.NextIndex++] = mission;
         }
     }
 
@@ -271,13 +174,13 @@ public sealed partial class SalvageSystem
             SalvageJobTime,
             EntityManager,
             _timing,
+            _logManager,
             _mapManager,
             _prototypeManager,
             _anchorable,
             _biome,
             _dungeon,
             _metaData,
-            this,
             station,
             missionParams,
             cancelToken.Token);
@@ -290,19 +193,4 @@ public sealed partial class SalvageSystem
     {
         args.PushMarkup(Loc.GetString("salvage-expedition-structure-examine"));
     }
-
-    private void GiveRewards(SalvageExpeditionComponent comp)
-    {
-        // send it to cargo, no rewards otherwise.
-        if (!TryComp<StationCargoOrderDatabaseComponent>(comp.Station, out var cargoDb))
-            return;
-
-        foreach (var reward in comp.Rewards)
-        {
-            var sender = Loc.GetString("cargo-gift-default-sender");
-            var desc = Loc.GetString("salvage-expedition-reward-description");
-            var dest = Loc.GetString("cargo-gift-default-dest");
-            _cargo.AddAndApproveOrder(comp.Station, reward, 0, 1, sender, desc, dest, cargoDb);
-        }
-    }
 }
index ce65ead92837a8c14151fdb3471777f5f33d59f6..0863362131cc510ccb845f07a921f25e5e2ae129 100644 (file)
@@ -43,7 +43,7 @@ public sealed partial class SalvageSystem
         // TODO: This is terrible but need bluespace harnesses or something.
         var query = EntityQueryEnumerator<HumanoidAppearanceComponent, MobStateComponent, TransformComponent>();
 
-        while (query.MoveNext(out var uid, out var _, out var mobState, out var mobXform))
+        while (query.MoveNext(out var uid, out _, out var mobState, out var mobXform))
         {
             if (mobXform.MapUid != xform.MapUid)
                 continue;
@@ -109,22 +109,11 @@ public sealed partial class SalvageSystem
             Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir())));
 
         component.Stage = ExpeditionStage.Running;
-        Dirty(component);
+        Dirty(args.MapUid, component);
     }
 
     private void OnFTLStarted(ref FTLStartedEvent ev)
     {
-        // Started a mining mission so work out exempt entities
-        if (TryComp<SalvageMiningExpeditionComponent>(
-                _mapManager.GetMapEntityId(ev.TargetCoordinates.ToMap(EntityManager, _transform).MapId),
-                out var mining))
-        {
-            var ents = new List<EntityUid>();
-            var xformQuery = GetEntityQuery<TransformComponent>();
-            MiningTax(ents, ev.Entity, mining, xformQuery);
-            mining.ExemptEntities = ents;
-        }
-
         if (!TryComp<SalvageExpeditionComponent>(ev.FromMapUid, out var expedition) ||
             !TryComp<SalvageExpeditionDataComponent>(expedition.Station, out var station))
         {
@@ -169,7 +158,7 @@ public sealed partial class SalvageSystem
                 Dirty(uid, comp);
                 Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(2).Minutes)));
             }
-            else if (comp.Stage < ExpeditionStage.Countdown && remaining < TimeSpan.FromMinutes(5))
+            else if (comp.Stage < ExpeditionStage.Countdown && remaining < TimeSpan.FromMinutes(4))
             {
                 comp.Stage = ExpeditionStage.Countdown;
                 Dirty(uid, comp);
@@ -210,72 +199,5 @@ public sealed partial class SalvageSystem
                 QueueDel(uid);
             }
         }
-
-        // Mining missions: NOOP since it's handled after ftling
-
-        // Structure missions
-        var structureQuery = EntityQueryEnumerator<SalvageStructureExpeditionComponent, SalvageExpeditionComponent>();
-
-        while (structureQuery.MoveNext(out var uid, out var structure, out var comp))
-        {
-            if (comp.Completed)
-                continue;
-
-            var structureAnnounce = false;
-
-            for (var i = 0; i < structure.Structures.Count; i++)
-            {
-                var objective = structure.Structures[i];
-
-                if (Deleted(objective))
-                {
-                    structure.Structures.RemoveSwap(i);
-                    structureAnnounce = true;
-                }
-            }
-
-            if (structureAnnounce)
-            {
-                Announce(uid, Loc.GetString("salvage-expedition-structure-remaining", ("count", structure.Structures.Count)));
-            }
-
-            if (structure.Structures.Count == 0)
-            {
-                comp.Completed = true;
-                Announce(uid, Loc.GetString("salvage-expedition-completed"));
-            }
-        }
-
-        // Elimination missions
-        var eliminationQuery = EntityQueryEnumerator<SalvageEliminationExpeditionComponent, SalvageExpeditionComponent>();
-        while (eliminationQuery.MoveNext(out var uid, out var elimination, out var comp))
-        {
-            if (comp.Completed)
-                continue;
-
-            var announce = false;
-
-            for (var i = 0; i < elimination.Megafauna.Count; i++)
-            {
-                var mob = elimination.Megafauna[i];
-
-                if (Deleted(mob) || _mobState.IsDead(mob))
-                {
-                    elimination.Megafauna.RemoveSwap(i);
-                    announce = true;
-                }
-            }
-
-            if (announce)
-            {
-                Announce(uid, Loc.GetString("salvage-expedition-megafauna-remaining", ("count", elimination.Megafauna.Count)));
-            }
-
-            if (elimination.Megafauna.Count == 0)
-            {
-                comp.Completed = true;
-                Announce(uid, Loc.GetString("salvage-expedition-completed"));
-            }
-        }
     }
 }
index 1065f7426e6995880ce8f83ae53c1b61b6e28b56..0da6207289846136b45399eddcd36a8ca11dafea 100644 (file)
@@ -37,12 +37,12 @@ namespace Content.Server.Salvage
         [Dependency] private readonly IChatManager _chat = default!;
         [Dependency] private readonly IConfigurationManager _configurationManager = default!;
         [Dependency] private readonly IGameTiming _timing = default!;
+        [Dependency] private readonly ILogManager _logManager = default!;
         [Dependency] private readonly IMapManager _mapManager = default!;
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly AnchorableSystem _anchorable = default!;
         [Dependency] private readonly BiomeSystem _biome = default!;
-        [Dependency] private readonly CargoSystem _cargo = default!;
         [Dependency] private readonly DungeonSystem _dungeon = default!;
         [Dependency] private readonly MapLoaderSystem _map = default!;
         [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
index c6b71b40955cf0993ac786ccce779e7e643725ea..f38114cc4401f3972959d89343b76024f95221af 100644 (file)
@@ -1,3 +1,4 @@
+using System.Collections;
 using System.Linq;
 using System.Numerics;
 using System.Threading;
@@ -19,6 +20,7 @@ using Content.Shared.Parallax.Biomes;
 using Content.Shared.Physics;
 using Content.Shared.Procedural;
 using Content.Shared.Procedural.Loot;
+using Content.Shared.Random;
 using Content.Shared.Salvage;
 using Content.Shared.Salvage.Expeditions;
 using Content.Shared.Salvage.Expeditions.Modifiers;
@@ -29,6 +31,7 @@ using Robust.Shared.Map.Components;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Salvage;
 
@@ -42,22 +45,23 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
     private readonly BiomeSystem _biome;
     private readonly DungeonSystem _dungeon;
     private readonly MetaDataSystem _metaData;
-    private readonly SalvageSystem _salvage;
 
     public readonly EntityUid Station;
     private readonly SalvageMissionParams _missionParams;
 
+    private readonly ISawmill _sawmill;
+
     public SpawnSalvageMissionJob(
         double maxTime,
         IEntityManager entManager,
         IGameTiming timing,
+        ILogManager logManager,
         IMapManager mapManager,
         IPrototypeManager protoManager,
         AnchorableSystem anchorable,
         BiomeSystem biome,
         DungeonSystem dungeon,
         MetaDataSystem metaData,
-        SalvageSystem salvage,
         EntityUid station,
         SalvageMissionParams missionParams,
         CancellationToken cancellation = default) : base(maxTime, cancellation)
@@ -70,15 +74,17 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
         _biome = biome;
         _dungeon = dungeon;
         _metaData = metaData;
-        _salvage = salvage;
         Station = station;
         _missionParams = missionParams;
+        _sawmill = logManager.GetSawmill("salvage_job");
+#if !DEBUG
+        _sawmill.Level = LogLevel.Info;
+#endif
     }
 
     protected override async Task<bool> Process()
     {
-        Logger.DebugS("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}");
-        var config = _missionParams.MissionType;
+        _sawmill.Debug("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}");
         var mapId = _mapManager.CreateMap();
         var mapUid = _mapManager.GetMapEntityId(mapId);
         _mapManager.AddUninitializedMap(mapId);
@@ -88,16 +94,17 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
 
         // Setup mission configs
         // As we go through the config the rating will deplete so we'll go for most important to least important.
+        var difficultyId = "Moderate";
+        var difficultyProto = _prototypeManager.Index<SalvageDifficultyPrototype>(difficultyId);
 
         var mission = _entManager.System<SharedSalvageSystem>()
-            .GetMission(_missionParams.MissionType, _missionParams.Difficulty, _missionParams.Seed);
+            .GetMission(difficultyProto, _missionParams.Seed);
 
-        var missionBiome = _prototypeManager.Index<SalvageBiomeMod>(mission.Biome);
-        BiomeComponent? biome = null;
+        var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
 
         if (missionBiome.BiomePrototype != null)
         {
-            biome = _entManager.AddComponent<BiomeComponent>(mapUid);
+            var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
             var biomeSystem = _entManager.System<BiomeSystem>();
             biomeSystem.SetTemplate(biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
             biomeSystem.SetSeed(biome, mission.Seed);
@@ -125,7 +132,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             {
                 var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
                 lighting.AmbientLightColor = mission.Color.Value;
-                _entManager.Dirty(lighting);
+                _entManager.Dirty(mapUid, lighting);
             }
         }
 
@@ -137,8 +144,6 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
         expedition.Station = Station;
         expedition.EndTime = _timing.CurTime + mission.Duration;
         expedition.MissionParams = _missionParams;
-        expedition.Difficulty = _missionParams.Difficulty;
-        expedition.Rewards = mission.Rewards;
 
         // Don't want consoles to have the incorrect name until refreshed.
         var ftlUid = _entManager.CreateEntityUninitialized("FTLPoint", new EntityCoordinates(mapUid, grid.TileSizeHalfVector));
@@ -151,29 +156,23 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
         // We'll use the dungeon rotation as the spawn angle
         var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed);
 
-        Dungeon dungeon = default!;
-
-        if (config != SalvageMissionType.Mining)
+        var maxDungeonOffset = minDungeonOffset + 12;
+        var dungeonOffsetDistance = minDungeonOffset + (maxDungeonOffset - minDungeonOffset) * random.NextFloat();
+        var dungeonOffset = new Vector2(0f, dungeonOffsetDistance);
+        dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
+        var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
+        var dungeonConfig = _prototypeManager.Index<DungeonConfigPrototype>(dungeonMod.Proto);
+        var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
+            _missionParams.Seed));
+
+        // Aborty
+        if (dungeon.Rooms.Count == 0)
         {
-            var maxDungeonOffset = minDungeonOffset + 12;
-            var dungeonOffsetDistance = minDungeonOffset + (maxDungeonOffset - minDungeonOffset) * random.NextFloat();
-            var dungeonOffset = new Vector2(0f, dungeonOffsetDistance);
-            dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
-            var dungeonMod = _prototypeManager.Index<SalvageDungeonMod>(mission.Dungeon);
-            var dungeonConfig = _prototypeManager.Index<DungeonConfigPrototype>(dungeonMod.Proto);
-            dungeon =
-                await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
-                    _missionParams.Seed));
-
-            // Aborty
-            if (dungeon.Rooms.Count == 0)
-            {
-                return false;
-            }
-
-            expedition.DungeonLocation = dungeonOffset;
+            return false;
         }
 
+        expedition.DungeonLocation = dungeonOffset;
+
         List<Vector2i> reservedTiles = new();
 
         foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false))
@@ -184,24 +183,14 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             reservedTiles.Add(tile.GridIndices);
         }
 
-        // Mission setup
-        switch (config)
-        {
-            case SalvageMissionType.Mining:
-                await SetupMining(mission, mapUid);
-                break;
-            case SalvageMissionType.Destruction:
-                await SetupStructure(mission, dungeon, mapUid, grid, random);
-                break;
-            case SalvageMissionType.Elimination:
-                await SetupElimination(mission, dungeon, mapUid, grid, random);
-                break;
-            default:
-                throw new NotImplementedException();
-        }
+        var budgetEntries = new List<IBudgetEntry>();
+
+        /*
+         * GUARANTEED LOOT
+         */
 
-        // Handle loot
         // We'll always add this loot if possible
+        // mainly used for ore layers.
         foreach (var lootProto in _prototypeManager.EnumeratePrototypes<SalvageLootPrototype>())
         {
             if (!lootProto.Guaranteed)
@@ -210,181 +199,129 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             await SpawnDungeonLoot(dungeon, missionBiome, lootProto, mapUid, grid, random, reservedTiles);
         }
 
-        return true;
-    }
+        // Handle boss loot (when relevant).
 
-    private async Task SpawnDungeonLoot(Dungeon? dungeon, SalvageBiomeMod biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
-    {
-        for (var i = 0; i < loot.LootRules.Count; i++)
+        // Handle mob loot.
+
+        // Handle remaining loot
+
+        /*
+         * MOB SPAWNS
+         */
+
+        var mobBudget = difficultyProto.MobBudget;
+        var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
+        var randomSystem = _entManager.System<RandomSystem>();
+
+        foreach (var entry in faction.MobGroups)
         {
-            var rule = loot.LootRules[i];
+            budgetEntries.Add(entry);
+        }
+
+        var probSum = budgetEntries.Sum(x => x.Prob);
 
+        while (mobBudget > 0f)
+        {
+            var entry = randomSystem.GetBudgetEntry(ref mobBudget, ref probSum, budgetEntries, random);
+            if (entry == null)
+                break;
+
+            await SpawnRandomEntry(grid, entry, dungeon, random);
+        }
+
+        var allLoot = _prototypeManager.Index<SalvageLootPrototype>(SharedSalvageSystem.ExpeditionsLootProto);
+        var lootBudget = difficultyProto.LootBudget;
+
+        foreach (var rule in allLoot.LootRules)
+        {
             switch (rule)
             {
-                case BiomeMarkerLoot biomeLoot:
+                case RandomSpawnsLoot randomLoot:
+                    budgetEntries.Clear();
+
+                    foreach (var entry in randomLoot.Entries)
                     {
-                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome) &&
-                            biomeLoot.Prototype.TryGetValue(biomeMod.ID, out var mod))
-                        {
-                            _biome.AddMarkerLayer(biome, mod);
-                        }
+                        budgetEntries.Add(entry);
                     }
-                    break;
-                case BiomeTemplateLoot biomeLoot:
+
+                    probSum = budgetEntries.Sum(x => x.Prob);
+
+                    while (lootBudget > 0f)
                     {
-                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
-                        {
-                            _biome.AddTemplate(biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
-                        }
+                        var entry = randomSystem.GetBudgetEntry(ref lootBudget, ref probSum, budgetEntries, random);
+                        if (entry == null)
+                            break;
+
+                        _sawmill.Debug($"Spawning dungeon loot {entry.Proto}");
+                        await SpawnRandomEntry(grid, entry, dungeon, random);
                     }
                     break;
+                default:
+                    throw new NotImplementedException();
             }
         }
-    }
-
-    #region Mission Specific
-
-    private async Task SetupMining(
-        SalvageMission mission,
-        EntityUid gridUid)
-    {
-        var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
 
-        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
-        {
-            // TODO: Better
-            for (var i = 0; i < _salvage.GetDifficulty(mission.Difficulty); i++)
-            {
-                _biome.AddMarkerLayer(biome, faction.Configs["Mining"]);
-            }
-        }
+        return true;
     }
 
-    private async Task SetupStructure(
-        SalvageMission mission,
-        Dungeon dungeon,
-        EntityUid gridUid,
-        MapGridComponent grid,
-        Random random)
+    private async Task SpawnRandomEntry(MapGridComponent grid, IBudgetEntry entry, Dungeon dungeon, Random random)
     {
-        var structureComp = _entManager.EnsureComponent<SalvageStructureExpeditionComponent>(gridUid);
-        var availableRooms = dungeon.Rooms.ToList();
-        var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
-        await SpawnMobsRandomRooms(mission, dungeon, faction, grid, random);
+        await SuspendIfOutOfTime();
 
-        var structureCount = _salvage.GetStructureCount(mission.Difficulty);
-        var shaggy = faction.Configs["DefenseStructure"];
-        var validSpawns = new List<Vector2i>();
+        var availableRooms = new ValueList<DungeonRoom>(dungeon.Rooms);
+        var availableTiles = new List<Vector2i>();
 
-        // Spawn the objectives
-        for (var i = 0; i < structureCount; i++)
+        while (availableRooms.Count > 0)
         {
-            var structureRoom = availableRooms[random.Next(availableRooms.Count)];
-            validSpawns.Clear();
-            validSpawns.AddRange(structureRoom.Tiles);
-            random.Shuffle(validSpawns);
+            availableTiles.Clear();
+            var roomIndex = random.Next(availableRooms.Count);
+            var room = availableRooms.RemoveSwap(roomIndex);
+            availableTiles.AddRange(room.Tiles);
 
-            while (validSpawns.Count > 0)
+            while (availableTiles.Count > 0)
             {
-                var spawnTile = validSpawns[^1];
-                validSpawns.RemoveAt(validSpawns.Count - 1);
+                var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count));
 
-                if (!_anchorable.TileFree(grid, spawnTile, (int) CollisionGroup.MachineLayer,
+                if (!_anchorable.TileFree(grid, tile, (int) CollisionGroup.MachineLayer,
                         (int) CollisionGroup.MachineLayer))
                 {
                     continue;
                 }
 
-                var spawnPosition = grid.GridTileToLocal(spawnTile);
-                var uid = _entManager.SpawnEntity(shaggy, spawnPosition);
-                _entManager.AddComponent<SalvageStructureComponent>(uid);
-                structureComp.Structures.Add(uid);
-                break;
+                _entManager.SpawnAtPosition(entry.Proto, grid.GridTileToLocal(tile));
+                return;
             }
         }
-    }
-
-    private async Task SetupElimination(
-        SalvageMission mission,
-        Dungeon dungeon,
-        EntityUid gridUid,
-        MapGridComponent grid,
-        Random random)
-    {
-        // spawn megafauna in a random place
-        var roomIndex = random.Next(dungeon.Rooms.Count);
-        var room = dungeon.Rooms[roomIndex];
-        var tile = room.Tiles.ElementAt(random.Next(room.Tiles.Count));
-        var position = grid.GridTileToLocal(tile);
 
-        var faction = _prototypeManager.Index<SalvageFactionPrototype>(mission.Faction);
-        var prototype = faction.Configs["Megafauna"];
-        var uid = _entManager.SpawnEntity(prototype, position);
-        // not removing ghost role since its 1 megafauna, expect that you won't be able to cheese it.
-        var eliminationComp = _entManager.EnsureComponent<SalvageEliminationExpeditionComponent>(gridUid);
-        eliminationComp.Megafauna.Add(uid);
-
-        // spawn less mobs than usual since there's megafauna to deal with too
-        await SpawnMobsRandomRooms(mission, dungeon, faction, grid, random, 0.5f);
+        // oh noooooooooooo
     }
 
-    private async Task SpawnMobsRandomRooms(SalvageMission mission, Dungeon dungeon, SalvageFactionPrototype faction, MapGridComponent grid, Random random, float scale = 1f)
+    private async Task SpawnDungeonLoot(Dungeon dungeon, SalvageBiomeModPrototype biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
     {
-        // scale affects how many groups are spawned, not the size of the groups themselves
-        var groupSpawns = _salvage.GetSpawnCount(mission.Difficulty) * scale;
-        var groupSum = faction.MobGroups.Sum(o => o.Prob);
-        var validSpawns = new List<Vector2i>();
-
-        for (var i = 0; i < groupSpawns; i++)
+        for (var i = 0; i < loot.LootRules.Count; i++)
         {
-            var roll = random.NextFloat() * groupSum;
-            var value = 0f;
+            var rule = loot.LootRules[i];
 
-            foreach (var group in faction.MobGroups)
+            switch (rule)
             {
-                value += group.Prob;
-
-                if (value < roll)
-                    continue;
-
-                var mobGroupIndex = random.Next(faction.MobGroups.Count);
-                var mobGroup = faction.MobGroups[mobGroupIndex];
-
-                var spawnRoomIndex = random.Next(dungeon.Rooms.Count);
-                var spawnRoom = dungeon.Rooms[spawnRoomIndex];
-                validSpawns.Clear();
-                validSpawns.AddRange(spawnRoom.Tiles);
-                random.Shuffle(validSpawns);
-
-                foreach (var entry in EntitySpawnCollection.GetSpawns(mobGroup.Entries, random))
-                {
-                    while (validSpawns.Count > 0)
+                case BiomeMarkerLoot biomeLoot:
                     {
-                        var spawnTile = validSpawns[^1];
-                        validSpawns.RemoveAt(validSpawns.Count - 1);
-
-                        if (!_anchorable.TileFree(grid, spawnTile, (int) CollisionGroup.MachineLayer,
-                                (int) CollisionGroup.MachineLayer))
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome) &&
+                            biomeLoot.Prototype.TryGetValue(biomeMod.ID, out var mod))
                         {
-                            continue;
+                            _biome.AddMarkerLayer(biome, mod);
                         }
-
-                        var spawnPosition = grid.GridTileToLocal(spawnTile);
-
-                        var uid = _entManager.CreateEntityUninitialized(entry, spawnPosition);
-                        _entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
-                        _entManager.RemoveComponent<GhostRoleComponent>(uid);
-                        _entManager.InitializeAndStartEntity(uid);
-
-                        break;
                     }
-                }
-
-                await SuspendIfOutOfTime();
-                break;
+                    break;
+                case BiomeTemplateLoot biomeLoot:
+                    {
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
+                        {
+                            _biome.AddTemplate(biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
+                        }
+                    }
+                    break;
             }
         }
     }
-
-    #endregion
 }
index a0cde055bad2f2f8ff8d1f1cff8899f0a89bb341..ccc8b6d51dc6a4dc8db3b1c8f9562cc7c7601867 100644 (file)
@@ -1500,13 +1500,16 @@ namespace Content.Shared.CCVar
             SalvageForced = CVarDef.Create("salvage.forced", "", CVar.SERVERONLY);
 
         /// <summary>
-        /// Cooldown for successful missions.
+        /// Duration for missions
         /// </summary>
         public static readonly CVarDef<float>
-            SalvageExpeditionCooldown = CVarDef.Create("salvage.expedition_cooldown", 300f, CVar.REPLICATED);
+            SalvageExpeditionDuration = CVarDef.Create("salvage.expedition_duration", 420f, CVar.REPLICATED);
 
+        /// <summary>
+        /// Cooldown for missions.
+        /// </summary>
         public static readonly CVarDef<float>
-            SalvageExpeditionFailedCooldown = CVarDef.Create("salvage.expedition_failed_cooldown", 900f, CVar.REPLICATED);
+            SalvageExpeditionCooldown = CVarDef.Create("salvage.expedition_cooldown", 780f, CVar.REPLICATED);
 
         /*
          * Flavor
diff --git a/Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs b/Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs
new file mode 100644 (file)
index 0000000..a9b71e7
--- /dev/null
@@ -0,0 +1,33 @@
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Procedural.Loot;
+
+/// <summary>
+/// Randomly places loot in free areas inside the dungeon.
+/// </summary>
+public sealed partial class RandomSpawnsLoot : IDungeonLoot
+{
+    [ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)]
+    public List<RandomSpawnLootEntry> Entries = new();
+}
+
+[DataDefinition]
+public partial record struct RandomSpawnLootEntry : IBudgetEntry
+{
+    [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+    public string Proto { get; set; } = string.Empty;
+
+    /// <summary>
+    /// Cost for this loot to spawn.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("cost")]
+    public float Cost { get; set; } = 1f;
+
+    /// <summary>
+    /// Unit probability for this entry. Weighted against the entire table.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("prob")]
+    public float Prob { get; set; } = 1f;
+}
index f5f8ab3fb2c0e9105d09f6d0e367b4fe9dfe556d..ba684dfe435b227acc850505eee77849df6051e0 100644 (file)
@@ -1,6 +1,4 @@
-using Content.Shared.Salvage;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
 
 namespace Content.Shared.Procedural.Loot;
 
@@ -17,14 +15,6 @@ public sealed class SalvageLootPrototype : IPrototype
     /// </summary>
     [DataField("guaranteed")] public bool Guaranteed;
 
-    [DataField("desc")] public string Description = string.Empty;
-
-    /// <summary>
-    /// Mission types this loot is not allowed to spawn for
-    /// </summary>
-    [DataField("blacklist")]
-    public List<SalvageMissionType> Blacklist = new();
-
     /// <summary>
     /// All of the loot rules
     /// </summary>
diff --git a/Content.Shared/Procedural/SalvageDifficultyPrototype.cs b/Content.Shared/Procedural/SalvageDifficultyPrototype.cs
new file mode 100644 (file)
index 0000000..335bebd
--- /dev/null
@@ -0,0 +1,36 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Procedural;
+
+[Prototype("salvageDifficulty")]
+public sealed class SalvageDifficultyPrototype : IPrototype
+{
+    [IdDataField] public string ID { get; } = string.Empty;
+
+    /// <summary>
+    /// Color to be used in UI.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("color")]
+    public Color Color = Color.White;
+
+    /// <summary>
+    /// How much loot this difficulty is allowed to spawn.
+    /// </summary>
+    [DataField("lootBudget", required : true)]
+    public float LootBudget;
+
+    /// <summary>
+    /// How many mobs this difficulty is allowed to spawn.
+    /// </summary>
+    [DataField("mobBudget", required : true)]
+    public float MobBudget;
+
+    /// <summary>
+    /// Budget allowed for mission modifiers like no light, etc.
+    /// </summary>
+    [DataField("modifierBudget")]
+    public float ModifierBudget;
+
+    [DataField("recommendedPlayers", required: true)]
+    public int RecommendedPlayers;
+}
diff --git a/Content.Shared/Random/IBudgetEntry.cs b/Content.Shared/Random/IBudgetEntry.cs
new file mode 100644 (file)
index 0000000..22dc69e
--- /dev/null
@@ -0,0 +1,20 @@
+namespace Content.Shared.Random;
+
+/// <summary>
+/// Budgeted random spawn entry.
+/// </summary>
+public interface IBudgetEntry : IProbEntry
+{
+    float Cost { get; set; }
+
+    string Proto { get; set; }
+}
+
+/// <summary>
+/// Random entry that has a prob. See <see cref="RandomSystem"/>
+/// </summary>
+public interface IProbEntry
+{
+    float Prob { get; set; }
+}
+
diff --git a/Content.Shared/Random/RandomSystem.cs b/Content.Shared/Random/RandomSystem.cs
new file mode 100644 (file)
index 0000000..78297e1
--- /dev/null
@@ -0,0 +1,58 @@
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Random;
+
+public sealed class RandomSystem : EntitySystem
+{
+    public IBudgetEntry? GetBudgetEntry(ref float budget, ref float probSum, IList<IBudgetEntry> entries, System.Random random)
+    {
+        DebugTools.Assert(budget > 0f);
+
+        if (entries.Count == 0)
+            return null;
+
+        // - Pick an entry
+        // - Remove the cost from budget
+        // - If our remaining budget is under maxCost then start pruning unavailable entries.
+        random.Shuffle(entries);
+        var budgetEntry = (IBudgetEntry) GetProbEntry(entries, probSum, random);
+
+        budget -= budgetEntry.Cost;
+
+        // Prune invalid entries.
+        for (var i = 0; i < entries.Count; i++)
+        {
+            var entry = entries[i];
+
+            if (entry.Cost < budget)
+                continue;
+
+            entries.RemoveSwap(i);
+            i--;
+            probSum -= entry.Prob;
+        }
+
+        return budgetEntry;
+    }
+
+    /// <summary>
+    /// Gets a random entry based on each entry having a different probability.
+    /// </summary>
+    public IProbEntry GetProbEntry(IEnumerable<IProbEntry> entries, float probSum, System.Random random)
+    {
+        var value = random.NextFloat() * probSum;
+
+        foreach (var entry in entries)
+        {
+            value -= entry.Prob;
+
+            if (value < 0f)
+            {
+                return entry;
+            }
+        }
+
+        throw new InvalidOperationException();
+    }
+}
index aee79fdf027d19ff9fdd5960b04f57d9ff3fd6aa..717c21947bc3cc67fd75da02ae2340f6d112f2a4 100644 (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>
similarity index 92%
rename from Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeMod.cs
rename to Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs
index fc6ce7e9de9eaaa9fcbf9f66ff5038830fe70f44..fe806f2cd3c6ee5922599ef21fe181ac8e0982c1 100644 (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!;
 
similarity index 88%
rename from Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonMod.cs
rename to Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonModPrototype.cs
index 335a127081def1256fd9b4ea13a5e9a66778baf3..f86f7cfd3b68d8f54a7faa05fe8700d3ef9a7c8b 100644 (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>
index e738c98a3a7bc42b30ac9bbfe02ede2daaab77eb..cfdc6a2b76246c44fc3d95e6d64d125a109a6861 100644 (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;
index c52b2010e4e991831fbcb25d312bdb4962770496..bc3d5eb85124dc35b86f03f81266fa084dec8363 100644 (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>
diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs
deleted file mode 100644 (file)
index 6cc3507..0000000
+++ /dev/null
@@ -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;
-}
index caa89afeb4a3fce4778ed7e2bf55f429a3f39832..89fc84c4168f41161892b15ffb33f04935329914 100644 (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>
index 075149c1cb862deef55c8e568fc593037d9aee2d..41f44f672beebd9c44f0e40819b952bc74d49bcb 100644 (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>
index cead06243198cbe7f60d411a316793b93f3357a4..9de6d5221b9f47aca7400a1140159e116e3bd4d8 100644 (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.
diff --git a/Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs b/Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs
new file mode 100644 (file)
index 0000000..fcfd865
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs b/Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs
deleted file mode 100644 (file)
index df1917a..0000000
+++ /dev/null
@@ -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();
-}
index 5c1bf20f21766a869af976803a5dc8adf8263b04..918b18f8dfd4ecbdbafb961d899468238e69fb20 100644 (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);
-
-        if (time.Description != string.Empty)
-        {
-            mods.Add(time.Description);
-        }
+        var duration = TimeSpan.FromSeconds(CfgManager.GetCVar(CCVars.SalvageExpeditionDuration));
 
-        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,
-}
index fd3c075f7d63365efd21e2021c1bf23f1a3390c2..82833fe2d51e3af35ea5f87dedeeeb9a862e09f5 100644 (file)
@@ -14,7 +14,6 @@ preload = false
 
 [salvage]
 expedition_cooldown = 30.0
-expedition_failed_cooldown = 30.0
 
 [shuttle]
 grid_fill = false
index a0192fabd32fd07f9ea3f3baa660bdb2305bb52f..f309da1bda3e3053de85e8811ef22055910e75b8 100644 (file)
@@ -4,8 +4,7 @@ salvage-expedition-structure-remaining = {$count ->
     *[other] {$count} structures remaining.
 }
 
-salvage-expedition-megafauna-remaining = {$count} megafauna remaining.
-
+salvage-expedition-type = Mission
 salvage-expedition-window-title = Salvage expeditions
 salvage-expedition-window-difficulty = Difficulty:
 salvage-expedition-window-details = Details:
@@ -13,31 +12,17 @@ salvage-expedition-window-hostiles = Hostiles:
 salvage-expedition-window-duration = Duration:
 salvage-expedition-window-biome = Biome:
 salvage-expedition-window-modifiers = Modifiers:
-salvage-expedition-window-rewards = Rewards:
 salvage-expedition-window-claimed = Claimed
 salvage-expedition-window-claim = Claim
 
 salvage-expedition-window-next = Next offer
 
-# Expedition descriptions
-salvage-expedition-desc-mining = Collect resources inside the area.
-#  You will be taxed {$tax}% of the resources collected.
-salvage-expedition-desc-structure = {$count ->
-    [one] Destroy {$count} {$structure} inside the area.
-    *[other] Destroy {$count} {$structure}s inside the area.
-}
-salvage-expedition-desc-elimination = Kill a large and dangerous creature inside the area.
-
-salvage-expedition-type-Mining = Mining
-salvage-expedition-type-Destruction = Destruction
-salvage-expedition-type-Elimination = Elimination
-
-salvage-expedition-difficulty-Minimal = Minimal
-salvage-expedition-difficulty-Minor = Minor
 salvage-expedition-difficulty-Moderate = Moderate
 salvage-expedition-difficulty-Hazardous = Hazardous
 salvage-expedition-difficulty-Extreme = Extreme
 
+salvage-expedition-difficulty-players = Recommended salvagers:
+
 # Runner
 salvage-expedition-not-all-present = Not all salvagers are aboard the shuttle!
 
index 2d490ddb1c840abdf7d4396af9f9ab14a1476b31..5b32601033dd63ed58fb2abf44052db33b64923f 100644 (file)
   parent: MobCarp
   suffix: Dungeon
   components:
+  - type: MobThresholds
+    thresholds:
+      0: Alive
+      50: Dead
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      25: 0.7
   - type: MeleeWeapon
     damage:
       types:
-        Slash: 5
+        Slash: 6
index 3eaee32998e9d4cf9f4f31ce99e4df4ba169dedd..1c81d57e69ee424b7a37a55232ffd4ca80a331a1 100644 (file)
@@ -60,6 +60,9 @@
     thresholds:
       0: Alive
       50: Dead
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      25: 0.5
   - type: Stamina
     critThreshold: 200
   - type: Bloodstream
   - type: MobThresholds
     thresholds:
       0: Alive
-      75: Dead
+      100: Dead
   - type: Stamina
     critThreshold: 300
   - type: SlowOnDamage
   - type: MobThresholds
     thresholds:
       0: Alive
-      150: Dead
+      80: Dead
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      40: 0.7
   - type: MeleeWeapon
     damage:
       groups:
-        Brute: 5
+        Brute: 6
   - type: MovementSpeedModifier
-    baseWalkSpeed: 2.0
-    baseSprintSpeed: 2.5
-  - type: SlowOnDamage
-    speedModifierThresholds:
-      100: 0.4
-      50: 0.7
+    baseSprintSpeed: 4
   - type: Fixtures
     fixtures:
       fix1:
     thresholds:
       0: Alive
       300: Dead
-  - type: Stamina
-    critThreshold: 1500
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      150: 0.7
   - type: MovementSpeedModifier
-    baseWalkSpeed: 2.8
-    baseSprintSpeed: 3.8
   - type: MeleeWeapon
     hidden: true
     damage:
      groups:
-       Brute: 20
-  - type: SlowOnDamage
-    speedModifierThresholds:
-      250: 0.4
-      200: 0.7
+       Brute: 12
   - type: Fixtures
     fixtures:
       fix1:
   - type: MobThresholds
     thresholds:
       0: Alive
-      200: Dead
-  - type: Stamina
-    critThreshold: 550
+      100: Dead
   - type: MovementSpeedModifier
-    baseWalkSpeed: 2.3
-    baseSprintSpeed: 4.2
+    baseSprintSpeed: 4
   - type: MeleeWeapon
     hidden: true
     damage:
        Brute: 10
   - type: SlowOnDamage
     speedModifierThresholds:
-      150: 0.5
-      100: 0.7
+      50: 0.7
   - type: Fixtures
     fixtures:
       fix1:
     layers:
     - map: ["enum.DamageStateVisualLayers.Base"]
       state: running
-  - type: Stamina
-    critThreshold: 250
   - type: MovementSpeedModifier
-    baseWalkSpeed: 2.7
     baseSprintSpeed: 6.0
   - type: MeleeWeapon
     hidden: true
     damage:
      groups:
-       Brute: 3
+       Brute: 5
   - type: Fixtures
     fixtures:
       fix1:
   - type: MobThresholds
     thresholds:
       0: Alive
-      75: Dead
+      50: Dead
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      25: 0.7
   - type: HTN
     rootTask:
       task: SimpleRangedHostileCompound
-  - type: Stamina
-    critThreshold: 300
   - type: RechargeBasicEntityAmmo
     rechargeCooldown: 0.75
   - type: BasicEntityAmmoProvider
     availableModes:
       - FullAuto
     soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
-  - type: SlowOnDamage
-    speedModifierThresholds:
-      50: 0.4
   - type: Fixtures
     fixtures:
       fix1:
index 73600767722cf4b88b666516a0d2cc3d96383b7a..398d101021739a002141d9a3ecfbb8dbfea1089d 100644 (file)
   components:
   - type: GhostRole
     description: ghost-role-information-space-dragon-dungeon-description
-  # less tanky, no crit
+  - type: SlowOnDamage
+    speedModifierThresholds:
+      100: 0.7
   - type: MobThresholds
     thresholds:
       0: Alive
-      300: Dead
+      200: Dead
   # less meat spawned since it's a lot easier to kill
   - type: Butcherable
     spawned:
     - id: FoodMeatDragon
       amount: 1
-  # half damage, spread evenly
   - type: MeleeWeapon
     damage:
       groups:
-        Brute: 15
+        Brute: 12
 
 - type: entity
   id: ActionSpawnRift
index f51c3ada6a8a3a0974f037dad15fdc07f2839ccb..513e691395f7a654e54ff00b196042fc4d8183e1 100644 (file)
   - type: ItemCooldown
   - type: MeleeWeapon
     damage:
+      groups:
+        Brute: 5
       types:
-        Blunt: 2.5
-        Piercing: 2.5
         Structural: 10
   - type: Wieldable
   - type: IncreaseDamageOnWield
     damage:
+      groups:
+        Brute: 10
       types:
-        Blunt: 5
-        Piercing: 5
         Structural: 10
   - type: Item
     size: 80
@@ -44,7 +44,7 @@
   - type: MeleeWeapon
     attackRate: 1.5
     damage:
+      groups:
+        Brute: 10
       types:
-        Blunt: 5
-        Piercing: 5
         Structural: 10
index d3560b8b680228e50f069961d9f9d25e093fc9f4..73b78875396c579c32ef95a57513a2c1b2ac7e62 100644 (file)
@@ -44,6 +44,7 @@
 
 - type: entity
   id: Thruster
+  name: thruster
   parent: [ BaseThruster, ConstructibleMachine ]
   components:
   - type: Thruster
       visible: false
       offset: 0, 1
 
+- type: entity
+  id: ThrusterUnanchored
+  parent: Thruster
+  components:
+    - type: Transform
+      anchored: false
+    - type: Physics
+      bodyType: Dynamic
+
 - type: entity
   id: DebugThruster
   parent: BaseThruster
 
 - type: entity
   id: Gyroscope
+  name: gyroscope
   parent: [ BaseThruster, ConstructibleMachine ]
   components:
   - type: Thruster
   - type: StaticPrice
     price: 2000
 
+- type: entity
+  id: GyroscopeUnanchored
+  parent: Gyroscope
+  components:
+    - type: Transform
+      anchored: false
+    - type: Physics
+      bodyType: Dynamic
+
 - type: entity
   id: DebugGyroscope
   parent: BaseThruster
diff --git a/Resources/Prototypes/Procedural/salvage_difficulties.yml b/Resources/Prototypes/Procedural/salvage_difficulties.yml
new file mode 100644 (file)
index 0000000..caef822
--- /dev/null
@@ -0,0 +1,12 @@
+- type: salvageDifficulty
+  id: Moderate
+  lootBudget: 30
+  mobBudget: 25
+  modifierBudget: 2
+  color: "#52B4E996"
+  recommendedPlayers: 2
+
+    #9FED5896
+    #EFB34196
+    #DE3A3A96
+    #D381C996
index f0aba69908e7b0200f4d5b83ece43440da84b64b..1097ed90497be5436f83eafda09b66f9d355b263 100644 (file)
@@ -1,61 +1,41 @@
 - type: salvageFaction
   id: Xenos
-  groups:
-  - entries:
-    - id: MobXeno
-      amount: 2
-      maxAmount: 3
-    - id: MobXenoDrone
-      amount: 1
-  - entries:
-    - id: MobXenoPraetorian
-      amount: 1
-      maxAmount: 2
-    prob: 0.5
-  - entries:
-    - id: MobXenoDrone
-      amount: 0
-      maxAmount: 2
-    prob: 0.25
-  - entries:
-    - id: WeaponTurretXeno
-      amount: 3
-    prob: 0.25
-  - entries:
-      - id: MobXenoSpitter
-        amount: 2
-    prob: 0.25
-  - entries:
-    - id: MobXenoRavager
-      amount: 1
-    prob: 0.1
-  - entries:
-    - id: MobXenoRouny
-      amount: 1
-    prob: 0.001
+  entries:
+    - proto: MobXeno
+    - proto: MobXenoDrone
+      cost: 2
+    - proto: MobXenoPraetorian
+      cost: 5
+      prob: 0.1
+    - proto: MobXenoQueen
+      cost: 10
+      prob: 0.02
+    - proto: MobXenoRavager
+      cost: 5
+    - proto: MobXenoRouny
+      cost: 3
+      prob: 0.02
+    - proto: MobXenoSpitter
+    - proto: WeaponTurretXeno
+      prob: 0.1
   configs:
     DefenseStructure: XenoWardingTower
-    Mining: Xenos
     Megafauna: MobXenoQueen
 
 - type: salvageFaction
   id: Carps
-  groups:
-  - entries:
-    - id: MobCarpDungeon
-      amount: 1
-      maxAmount: 4
-  - entries:
-    - id: MobCarpMagic
-      amount: 1
-      maxAmount: 3
-    prob: 0.5
-  - entries:
-    - id: MobCarpHolo
-      amount: 1
-      maxAmount: 2
-    prob: 0.25
+  entries:
+    - proto: MobCarpDungeon
+    # These do too much damage for salvage, need nerfs
+    #- proto: MobCarpHolo
+    #  cost: 5
+    #  prob: 0.1
+    #- proto: MobCarpMagic
+    #  cost: 5
+    #  prob: 0.1
+    - proto: MobDragonDungeon
+      cost: 10
+      prob: 0.02
   configs:
     DefenseStructure: CarpStatue
-    Mining: Carps
     Megafauna: MobDragonDungeon
index 06da9f7167432f0eed70c1906419ff38e98ccd04..54870943b2f0fce413203618c6dfe201d62beb15 100644 (file)
@@ -1,8 +1,125 @@
-# Ores
+# Loot table
+# Main loot table for random spawns
+- type: salvageLoot
+  id: SalvageLoot
+  loots:
+    - !type:RandomSpawnsLoot
+      entries:
+        - proto: AdvMopItem
+          prob: 0.5
+        - proto: AmmoTechFabCircuitboard
+          cost: 2
+        - proto: AutolatheMachineCircuitboard
+          cost: 2
+        - proto: BiomassReclaimerMachineCircuitboard
+          cost: 2
+        - proto: BluespaceBeaker
+          cost: 2
+        - proto: CyborgEndoskeleton
+          cost: 3
+          prob: 0.5
+        - proto: ChemDispenserMachineCircuitboard
+          cost: 2
+        - proto: CircuitImprinter
+          cost: 2
+        - proto: CloningConsoleComputerCircuitboard
+          cost: 2
+        - proto: CloningPodMachineCircuitboard
+          cost: 2
+        - proto: CognizineChemistryBottle
+        - proto: CratePartsT3
+          cost: 2
+          prob: 0.5
+        - proto: CratePartsT3T4
+          cost: 5
+          prob: 0.5
+        - proto: CratePartsT4
+          cost: 5
+          prob: 0.5
+        - proto: CrateSalvageEquipment
+          cost: 3
+          prob: 0.5
+        - proto: GasRecycler
+          cost: 2
+        - proto: GeneratorRTG
+          cost: 5
+        - proto: GravityGeneratorMini
+          cost: 2
+        - proto: GyroscopeUnanchored
+          cost: 2
+          prob: 0.1
+        - proto: MedicalScannerMachineCircuitboard
+          cost: 2
+        - proto: NuclearBombKeg
+          cost: 5
+        - proto: OmnizineChemistryBottle
+          prob: 0.5
+        - proto: PortableGeneratorPacman
+          cost: 2
+        - proto: PortableGeneratorSuperPacman
+          cost: 3
+        - proto: PowerCellAntiqueProto
+          cost: 5
+          prob: 0.5
+        - proto: ProtolatheMachineCircuitboard
+        - proto: RandomArtifactSpawner
+          cost: 2
+        - proto: RandomCargoCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomCommandCorpseSpawner
+          cost: 5
+          prob: 0.5
+        - proto: RandomEngineerCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomMedicCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomScienceCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomSecurityCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomServiceCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: ResearchAndDevelopmentServerMachineCircuitboard
+          cost: 5
+          prob: 0.5
+        - proto: ResearchDisk10000
+          prob: 0.5
+        - proto: ResearchDisk5000
+          prob: 0.5
+        - proto: RipleyHarness
+          cost: 3
+          prob: 0.5
+        - proto: RPED
+        - proto: SpaceCash1000
+        - proto: SpaceCash10000
+          cost: 10
+        - proto: SpaceCash2500
+          cost: 3
+        - proto: SpaceCash5000
+          cost: 5
+        - proto: TechnologyDiskRare
+          cost: 5
+          prob: 0.5
+        - proto: ThrusterUnanchored
+        - proto: WaterTankHighCapacity
+        - proto: WeldingFuelTankHighCapacity
+          cost: 3
+
+# Mob loot table
+
+
+# Boss loot table
+
+# Ores - these are guaranteed
 # - Low value
 - type: salvageLoot
   id: OreTin
-  desc: Veins of steel
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 
 - type: salvageLoot
   id: OreQuartz
-  desc: Veins of quartz
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 # - Medium value
 - type: salvageLoot
   id: OreGold
-  desc: Veins of gold ore
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 
 - type: salvageLoot
   id: OreSilver
-  desc: Veins of silver ore
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 # - High value
 - type: salvageLoot
   id: OrePlasma
-  desc: Veins of plasma ore
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 
 - type: salvageLoot
   id: OreUranium
-  desc: Veins of uranium ore
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 
 - type: salvageLoot
   id: OreBananium
-  desc: Veins of bananium ore
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
 
 - type: salvageLoot
   id: OreArtifactFragment
-  desc: artifact fragment-embedded rock
   guaranteed: true
   loots:
     - !type:BiomeMarkerLoot
index 7ff2918c13d5635cdea0b691f47c3d8d1eac9d7f..8d46dff59a028cb8cc9fe414657425f4e708a91e 100644 (file)
@@ -5,22 +5,22 @@
 
 # Biome mods -> at least 1 required
 - type: salvageBiomeMod
-  id: Grasslands
-  biome: Grasslands
+  id: Caves
+  biome: Caves
 
 - type: salvageBiomeMod
-  id: Lava
-  cost: 2
-  biome: Lava
+  id: Grasslands
+  biome: Grasslands
 
 - type: salvageBiomeMod
   id: Snow
+  cost: 1
   biome: Snow
 
 - type: salvageBiomeMod
-  id: Caves
-  cost: 1
-  biome: Caves
+  id: Lava
+  cost: 2
+  biome: Lava
 
 #- type: salvageBiomeMod
 #  id: Space
 #  weather: false
 #  biome: null
 
-# Temperature mods -> not required
-# Also whitelist it
-
-# Weather mods -> not required
-- type: salvageWeatherMod
-  id: SnowfallHeavy
-  weather: SnowfallHeavy
-  cost: 1
-
-- type: salvageWeatherMod
-  id: Rain
-  weather: Rain
-
 # Light mods -> required
-# At some stage with sub-biomes this will probably be moved onto the biome itself
 - type: salvageLightMod
   id: Daylight
   desc: Daylight
   color: "#D8B059"
   biomes:
-  - Grasslands
+    - Grasslands
 
 - type: salvageLightMod
   id: Lavalight
   desc: Daylight
   color: "#A34931"
   biomes:
-  - Lava
+    - Lava
 
 - type: salvageLightMod
   id: Evening
 - type: salvageLightMod
   id: Night
   desc: Night time
-  cost: 2
+  cost: 1
   color: null
 
-# Time mods -> at least 1 required
-- type: salvageTimeMod
-  id: StandardTime
+# Temperatures
+- type: salvageTemperatureMod
+  id: RoomTemp
+  cost: 0
 
-- type: salvageTimeMod
-  id: RushTime
-  desc: Rush
-  minDuration: 420
-  maxDuration: 465
+- type: salvageTemperatureMod
+  id: Hot
   cost: 1
+  temperature: 323.15 # 50C
+  biomes:
+    - Caves
+    #- LowDesert
+    - Grasslands
+    - Lava
+
+- type: salvageTemperatureMod
+  id: Burning
+  desc: High temperature
+  cost: 2
+  temperature: 423.15 # 200C
+  biomes:
+    - Caves
+    #- LowDesert
+    - Lava
 
-# Misc mods
-- type: salvageMod
-  id: LongDistance
-  desc: Long distance
+- type: salvageTemperatureMod
+  id: Melting
+  desc: Extreme heat
+  cost: 4
+  temperature: 1273.15 # 1000C hot hot hot
+  biomes:
+    - Lava
 
-# Dungeons
-#  For now just simple 1-dungeon setups
-- type: salvageDungeonMod
-  id: Experiment
-  proto: Experiment
+- type: salvageTemperatureMod
+  id: Cold
+  cost: 1
+  temperature: 275.15 # 2C
   biomes:
     - Caves
     #- LowDesert
-    - Snow
     - Grasslands
+    - Snow
 
-- type: salvageDungeonMod
-  id: LavaBrig
-  proto: LavaBrig
+- type: salvageTemperatureMod
+  id: Tundra
+  desc: Low temperature
+  cost: 2
+  temperature: 263.15 # -40C
   biomes:
-    - Lava
+    - Caves
+    - Snow
+
+- type: salvageTemperatureMod
+  id: Frozen
+  desc: Extreme cold
+  cost: 4
+  temperature: 123.15 # -150C
+  biomes:
+    - Snow
 
 # Air mixtures
 - type: salvageAirMod
   id: Space
   desc: No atmosphere
   space: true
-  cost: 1
+  cost: 2
   biomes:
-  - Caves
-  - Lava
+    - Caves
+    - Lava
 
 - type: salvageAirMod
   id: Breathable
+  cost: 0
   gases:
-  - 21.824779 # oxygen
-  - 82.10312 # nitrogen
-  biomes:
-  - Caves
-  #- LowDesert
-  - Snow
-  - Grasslands
+    - 21.824779 # oxygen
+    - 82.10312 # nitrogen
 
 - type: salvageAirMod
   id: Sleepy
   cost: 1
   desc: Dangerous atmosphere
   gases:
-  - 21.824779 # oxygen
-  - 72.10312 # nitrogen
-  - 0
-  - 0
-  - 0
-  - 0
-  - 0
-  - 10 # nitrous oxide
+    - 21.824779 # oxygen
+    - 72.10312 # nitrogen
+    - 0
+    - 0
+    - 0
+    - 0
+    - 0
+    - 10 # nitrous oxide
   biomes:
-  - Caves
-  #- LowDesert
-  - Snow
-  - Grasslands
-  - Lava
+    - Caves
+    #- LowDesert
+    - Snow
+    - Grasslands
+    - Lava
 
 - type: salvageAirMod
   id: Poisoned
   cost: 2
   desc: Dangerous atmosphere
   gases:
-  - 21.824779 # oxygen
-  - 77.10312 # nitrogen
-  - 10 # carbon dioxide
+    - 21.824779 # oxygen
+    - 77.10312 # nitrogen
+    - 10 # carbon dioxide
   biomes:
-  - Caves
-  #- LowDesert
-  - Snow
-  - Grasslands
-  - Lava
+    - Caves
+    #- LowDesert
+    - Snow
+    - Grasslands
+    - Lava
 
 - type: salvageAirMod
   id: Poison
   cost: 3
   desc: Toxic atmosphere
   gases:
-  - 21.824779 # oxygen
-  - 0
-  - 82.10312 # carbon dioxide
+    - 21.824779 # oxygen
+    - 0
+    - 82.10312 # carbon dioxide
   biomes:
-  - Caves
-  - Snow
-  - Lava
+    - Caves
+    - Snow
+    - Lava
 
 - type: salvageAirMod
   id: Plasma
   cost: 4
   desc: Toxic atmosphere
   gases:
-  - 0
-  - 0
-  - 0
-  - 103.927899 # plasma
+    - 0
+    - 0
+    - 0
+    - 103.927899 # plasma
   biomes:
-  - Caves
-  - Lava
+    - Caves
+    - Lava
 
 - type: salvageAirMod
   id: Burnable
   cost: 5
   desc: Volatile atmosphere
   gases:
-  - 21.824779 # oxygen
-  - 0
-  - 0
-  - 82.10312 # plasma
-  biomes:
-  - Caves
-  - Lava
-
-# Temperatures
-
-- type: salvageTemperatureMod
-  id: RoomTemp
-  biomes:
-  - Caves
-  #- LowDesert
-  - Grasslands
-
-- type: salvageTemperatureMod
-  id: Hot
-  temperature: 323.15 # 50C
+    - 21.824779 # oxygen
+    - 0
+    - 0
+    - 82.10312 # plasma
   biomes:
-  - Caves
-  #- LowDesert
-  - Grasslands
-  - Lava
-
-- type: salvageTemperatureMod
-  id: Burning
-  desc: High temperature
-  cost: 1
-  temperature: 423.15 # 200C
-  biomes:
-  - Caves
-  #- LowDesert
-  - Lava
-
-- type: salvageTemperatureMod
-  id: Melting
-  desc: Extreme heat
-  cost: 4
-  temperature: 1273.15 # 1000C hot hot hot
-  biomes:
-  - Lava
+    - Caves
+    - Lava
 
-- type: salvageTemperatureMod
-  id: Cold
-  temperature: 275.15 # 2C
-  biomes:
-  - Caves
-  #- LowDesert
-  - Grasslands
-  - Snow
+# Weather mods -> not required
+#- type: salvageWeatherMod
+#  id: SnowfallHeavy
+#  weather: SnowfallHeavy
+#  cost: 1
+#
+#- type: salvageWeatherMod
+#  id: Rain
+#  weather: Rain
 
-- type: salvageTemperatureMod
-  id: Tundra
-  desc: Low temperature
-  cost: 2
-  temperature: 263.15 # -40C
+# Dungeons
+#  For now just simple 1-dungeon setups
+- type: salvageDungeonMod
+  id: Experiment
+  proto: Experiment
   biomes:
-  - Caves
-  - Snow
+    - Caves
+    #- LowDesert
+    - Snow
+    - Grasslands
 
-- type: salvageTemperatureMod
-  id: Frozen
-  desc: Extreme cold
-  cost: 3
-  temperature: 123.15 # -150C
+- type: salvageDungeonMod
+  id: LavaBrig
+  proto: LavaBrig
   biomes:
-  - Snow
+    - Lava