Expeditions rework (#18960)

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

View File

@@ -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);

View 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();

View File

@@ -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();
}

View 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!;
}

View File

@@ -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();
}

View File

@@ -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();
}

View 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);
}

View 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.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
{
Index = component.NextIndex,
MissionType = config,
Seed = _random.Next(),
Difficulty = rating,
Difficulty = "Moderate",
};
component.Missions[component.NextIndex++] = mission;
break;
}
}
}
@@ -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);
}
}
}

View 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"));
}
}
}
}

View 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!;

View 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,18 +156,13 @@ 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<SalvageDungeonMod>(mission.Dungeon);
var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
var dungeonConfig = _prototypeManager.Index<DungeonConfigPrototype>(dungeonMod.Proto);
dungeon =
await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset,
_missionParams.Seed));
// Aborty
@@ -172,7 +172,6 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
}
expedition.DungeonLocation = dungeonOffset;
}
List<Vector2i> reservedTiles = new();
@@ -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,10 +199,104 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
await SpawnDungeonLoot(dungeon, missionBiome, lootProto, mapUid, grid, random, reservedTiles);
}
// Handle boss loot (when relevant).
// 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)
{
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 RandomSpawnsLoot randomLoot:
budgetEntries.Clear();
foreach (var entry in randomLoot.Entries)
{
budgetEntries.Add(entry);
}
probSum = budgetEntries.Sum(x => x.Prob);
while (lootBudget > 0f)
{
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();
}
}
return true;
}
private async Task SpawnDungeonLoot(Dungeon? dungeon, SalvageBiomeMod biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
private async Task SpawnRandomEntry(MapGridComponent grid, IBudgetEntry entry, Dungeon dungeon, Random random)
{
await SuspendIfOutOfTime();
var availableRooms = new ValueList<DungeonRoom>(dungeon.Rooms);
var availableTiles = new List<Vector2i>();
while (availableRooms.Count > 0)
{
availableTiles.Clear();
var roomIndex = random.Next(availableRooms.Count);
var room = availableRooms.RemoveSwap(roomIndex);
availableTiles.AddRange(room.Tiles);
while (availableTiles.Count > 0)
{
var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count));
if (!_anchorable.TileFree(grid, tile, (int) CollisionGroup.MachineLayer,
(int) CollisionGroup.MachineLayer))
{
continue;
}
_entManager.SpawnAtPosition(entry.Proto, grid.GridTileToLocal(tile));
return;
}
}
// oh noooooooooooo
}
private async Task SpawnDungeonLoot(Dungeon dungeon, SalvageBiomeModPrototype biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
{
for (var i = 0; i < loot.LootRules.Count; i++)
{
@@ -241,150 +324,4 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
}
}
}
#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"]);
}
}
}
private async Task SetupStructure(
SalvageMission mission,
Dungeon dungeon,
EntityUid gridUid,
MapGridComponent grid,
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);
var structureCount = _salvage.GetStructureCount(mission.Difficulty);
var shaggy = faction.Configs["DefenseStructure"];
var validSpawns = new List<Vector2i>();
// Spawn the objectives
for (var i = 0; i < structureCount; i++)
{
var structureRoom = availableRooms[random.Next(availableRooms.Count)];
validSpawns.Clear();
validSpawns.AddRange(structureRoom.Tiles);
random.Shuffle(validSpawns);
while (validSpawns.Count > 0)
{
var spawnTile = validSpawns[^1];
validSpawns.RemoveAt(validSpawns.Count - 1);
if (!_anchorable.TileFree(grid, spawnTile, (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;
}
}
}
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);
}
private async Task SpawnMobsRandomRooms(SalvageMission mission, Dungeon dungeon, SalvageFactionPrototype faction, MapGridComponent grid, Random random, float scale = 1f)
{
// 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++)
{
var roll = random.NextFloat() * groupSum;
var value = 0f;
foreach (var group in faction.MobGroups)
{
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)
{
var spawnTile = validSpawns[^1];
validSpawns.RemoveAt(validSpawns.Count - 1);
if (!_anchorable.TileFree(grid, spawnTile, (int) CollisionGroup.MachineLayer,
(int) CollisionGroup.MachineLayer))
{
continue;
}
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;
}
}
}
#endregion
}

View 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

View File

@@ -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;
}

View 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>

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ preload = false
[salvage]
expedition_cooldown = 30.0
expedition_failed_cooldown = 30.0
[shuttle]
grid_fill = false

View 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!

View File

@@ -164,7 +164,14 @@
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

View File

@@ -60,6 +60,9 @@
thresholds:
0: Alive
50: Dead
- type: SlowOnDamage
speedModifierThresholds:
25: 0.5
- type: Stamina
critThreshold: 200
- type: Bloodstream
@@ -129,7 +132,7 @@
- type: MobThresholds
thresholds:
0: Alive
75: Dead
100: Dead
- type: Stamina
critThreshold: 300
- type: SlowOnDamage
@@ -162,18 +165,16 @@
- 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:
@@ -202,20 +203,15 @@
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:
@@ -246,12 +242,9 @@
- 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:
@@ -259,8 +252,7 @@
Brute: 10
- type: SlowOnDamage
speedModifierThresholds:
150: 0.5
100: 0.7
50: 0.7
- type: Fixtures
fixtures:
fix1:
@@ -285,16 +277,13 @@
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:
@@ -332,12 +321,13 @@
- 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
@@ -351,9 +341,6 @@
availableModes:
- FullAuto
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
- type: SlowOnDamage
speedModifierThresholds:
50: 0.4
- type: Fixtures
fixtures:
fix1:

View File

@@ -144,21 +144,22 @@
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

View File

@@ -13,16 +13,16 @@
- 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

View File

@@ -44,6 +44,7 @@
- type: entity
id: Thruster
name: thruster
parent: [ BaseThruster, ConstructibleMachine ]
components:
- type: Thruster
@@ -64,6 +65,15 @@
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
@@ -91,6 +101,7 @@
- type: entity
id: Gyroscope
name: gyroscope
parent: [ BaseThruster, ConstructibleMachine ]
components:
- type: Thruster
@@ -133,6 +144,15 @@
- 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

View File

@@ -0,0 +1,12 @@
- type: salvageDifficulty
id: Moderate
lootBudget: 30
mobBudget: 25
modifierBudget: 2
color: "#52B4E996"
recommendedPlayers: 2
#9FED5896
#EFB34196
#DE3A3A96
#D381C996

View 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
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
- entries:
- id: MobXenoRouny
amount: 1
prob: 0.001
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

View 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
@@ -14,7 +131,6 @@
- type: salvageLoot
id: OreQuartz
desc: Veins of quartz
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -27,7 +143,6 @@
# - Medium value
- type: salvageLoot
id: OreGold
desc: Veins of gold ore
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -39,7 +154,6 @@
- type: salvageLoot
id: OreSilver
desc: Veins of silver ore
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -52,7 +166,6 @@
# - High value
- type: salvageLoot
id: OrePlasma
desc: Veins of plasma ore
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -64,7 +177,6 @@
- type: salvageLoot
id: OreUranium
desc: Veins of uranium ore
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -76,7 +188,6 @@
- type: salvageLoot
id: OreBananium
desc: Veins of bananium ore
guaranteed: true
loots:
- !type:BiomeMarkerLoot
@@ -88,7 +199,6 @@
- type: salvageLoot
id: OreArtifactFragment
desc: artifact fragment-embedded rock
guaranteed: true
loots:
- !type:BiomeMarkerLoot

View File

@@ -4,45 +4,31 @@
parent: FTLPoint
# Biome mods -> at least 1 required
- type: salvageBiomeMod
id: Caves
biome: Caves
- type: salvageBiomeMod
id: Grasslands
biome: Grasslands
- type: salvageBiomeMod
id: Snow
cost: 1
biome: Snow
- type: salvageBiomeMod
id: Lava
cost: 2
biome: Lava
- type: salvageBiomeMod
id: Snow
biome: Snow
- type: salvageBiomeMod
id: Caves
cost: 1
biome: Caves
#- type: salvageBiomeMod
# id: Space
# cost: 1
# 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
@@ -65,62 +51,85 @@
- 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
# Misc mods
- type: salvageMod
id: LongDistance
desc: Long distance
# Dungeons
# For now just simple 1-dungeon setups
- type: salvageDungeonMod
id: Experiment
proto: Experiment
temperature: 323.15 # 50C
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- Lava
- type: salvageDungeonMod
id: LavaBrig
proto: LavaBrig
- type: salvageTemperatureMod
id: Burning
desc: High temperature
cost: 2
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
- type: salvageTemperatureMod
id: Cold
cost: 1
temperature: 275.15 # 2C
biomes:
- Caves
#- LowDesert
- Grasslands
- Snow
- type: salvageTemperatureMod
id: Tundra
desc: Low temperature
cost: 2
temperature: 263.15 # -40C
biomes:
- 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
- type: salvageAirMod
id: Breathable
cost: 0
gases:
- 21.824779 # oxygen
- 82.10312 # nitrogen
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- type: salvageAirMod
id: Sleepy
@@ -196,64 +205,29 @@
- Caves
- Lava
# Temperatures
# Weather mods -> not required
#- type: salvageWeatherMod
# id: SnowfallHeavy
# weather: SnowfallHeavy
# cost: 1
#
#- type: salvageWeatherMod
# id: Rain
# weather: Rain
- type: salvageTemperatureMod
id: RoomTemp
# Dungeons
# For now just simple 1-dungeon setups
- type: salvageDungeonMod
id: Experiment
proto: Experiment
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- type: salvageTemperatureMod
id: Hot
temperature: 323.15 # 50C
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
- type: salvageDungeonMod
id: LavaBrig
proto: LavaBrig
biomes:
- Lava
- type: salvageTemperatureMod
id: Cold
temperature: 275.15 # 2C
biomes:
- Caves
#- LowDesert
- Grasslands
- Snow
- type: salvageTemperatureMod
id: Tundra
desc: Low temperature
cost: 2
temperature: 263.15 # -40C
biomes:
- Caves
- Snow
- type: salvageTemperatureMod
id: Frozen
desc: Extreme cold
cost: 3
temperature: 123.15 # -150C
biomes:
- Snow