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;
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()
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),
});
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),
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),
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()
{
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);
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();
+++ /dev/null
-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();
-}
{
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!;
}
+++ /dev/null
-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();
-}
+++ /dev/null
-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();
-}
+using Content.Shared.Procedural;
using Content.Shared.Salvage;
using Content.Shared.Salvage.Expeditions;
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);
}
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;
private const double SalvageJobTime = 0.002;
private float _cooldown;
- private float _failedCooldown;
private void InitializeExpeditions()
{
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)
private void ShutdownExpeditions()
{
_configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange);
- _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange);
}
private void SetCooldownChange(float obj)
_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();
// Finish mission
if (TryComp<SalvageExpeditionDataComponent>(component.Station, out var data))
{
- FinishExpedition(data, uid, component, null);
+ FinishExpedition(data, uid);
}
}
}
}
- 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;
}
}
SalvageJobTime,
EntityManager,
_timing,
+ _logManager,
_mapManager,
_prototypeManager,
_anchorable,
_biome,
_dungeon,
_metaData,
- this,
station,
missionParams,
cancelToken.Token);
{
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);
- }
- }
}
// 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;
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))
{
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);
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"));
- }
- }
}
}
[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!;
+using System.Collections;
using System.Linq;
using System.Numerics;
using System.Threading;
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;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Server.Salvage;
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)
_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);
// 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);
{
var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
lighting.AmbientLightColor = mission.Color.Value;
- _entManager.Dirty(lighting);
+ _entManager.Dirty(mapUid, lighting);
}
}
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));
// 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))
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)
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
}
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
--- /dev/null
+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;
+}
-using Content.Shared.Salvage;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.Loot;
/// </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>
--- /dev/null
+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;
+}
--- /dev/null
+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; }
+}
+
--- /dev/null
+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();
+ }
+}
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>
/// 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!;
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!;
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>
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;
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>
+++ /dev/null
-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;
-}
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>
}
[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>
/// </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>
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;
/// </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>
/// </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>
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.
--- /dev/null
+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;
+}
+++ /dev/null
-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();
-}
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;
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)
{
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
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,
-}
[salvage]
expedition_cooldown = 30.0
-expedition_failed_cooldown = 30.0
[shuttle]
grid_fill = false
*[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:
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!
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
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:
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
- 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
- type: MeleeWeapon
attackRate: 1.5
damage:
+ groups:
+ Brute: 10
types:
- Blunt: 5
- Piercing: 5
Structural: 10
- 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
--- /dev/null
+- type: salvageDifficulty
+ id: Moderate
+ lootBudget: 30
+ mobBudget: 25
+ modifierBudget: 2
+ color: "#52B4E996"
+ recommendedPlayers: 2
+
+ #9FED5896
+ #EFB34196
+ #DE3A3A96
+ #D381C996
- 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
-# 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
# 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