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.Client.UserInterface.Controls;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Parallax.Biomes; using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural;
using Content.Shared.Salvage; using Content.Shared.Salvage;
using Content.Shared.Salvage.Expeditions; using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers; 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++) for (var i = 0; i < state.Missions.Count; i++)
{ {
var missionParams = state.Missions[i]; var missionParams = state.Missions[i];
var config = missionParams.MissionType; var difficultyId = "Moderate";
var mission = _salvage.GetMission(missionParams.MissionType, missionParams.Difficulty, missionParams.Seed); var difficultyProto = _prototype.Index<SalvageDifficultyPrototype>(difficultyId);
// TODO: Selectable difficulty soon.
var mission = _salvage.GetMission(difficultyProto, missionParams.Seed);
// Mission title // Mission title
var missionStripe = new StripeBack() var missionStripe = new StripeBack()
@@ -64,7 +67,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
missionStripe.AddChild(new Label() missionStripe.AddChild(new Label()
{ {
Text = Loc.GetString($"salvage-expedition-type-{config.ToString()}"), Text = Loc.GetString($"salvage-expedition-type"),
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
Margin = new Thickness(0f, 5f, 0f, 5f), Margin = new Thickness(0f, 5f, 0f, 5f),
}); });
@@ -81,48 +84,25 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
Text = Loc.GetString("salvage-expedition-window-difficulty") Text = Loc.GetString("salvage-expedition-window-difficulty")
}); });
Color difficultyColor; var difficultyColor = difficultyProto.Color;
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();
}
lBox.AddChild(new Label lBox.AddChild(new Label
{ {
Text = Loc.GetString($"salvage-expedition-difficulty-{missionParams.Difficulty.ToString()}"), Text = Loc.GetString("salvage-expedition-difficulty-Moderate"),
FontColorOverride = difficultyColor, FontColorOverride = difficultyColor,
HorizontalAlignment = HAlignment.Left, HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(0f, 0f, 0f, 5f), Margin = new Thickness(0f, 0f, 0f, 5f),
}); });
// Details
var details = _salvage.GetMissionDescription(mission);
lBox.AddChild(new Label 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 lBox.AddChild(new Label
{ {
Text = details, Text = difficultyProto.RecommendedPlayers.ToString(),
FontColorOverride = StyleNano.NanoGold, FontColorOverride = StyleNano.NanoGold,
HorizontalAlignment = HAlignment.Left, HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(0f, 0f, 0f, 5f), Margin = new Thickness(0f, 0f, 0f, 5f),
@@ -168,7 +148,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
lBox.AddChild(new Label lBox.AddChild(new Label
{ {
Text = Loc.GetString(_prototype.Index<SalvageBiomeMod>(biome).ID), Text = Loc.GetString(_prototype.Index<SalvageBiomeModPrototype>(biome).ID),
FontColorOverride = StyleNano.NanoGold, FontColorOverride = StyleNano.NanoGold,
HorizontalAlignment = HAlignment.Left, HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(0f, 0f, 0f, 5f), Margin = new Thickness(0f, 0f, 0f, 5f),
@@ -190,29 +170,6 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
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 // Claim
var claimButton = new Button() var claimButton = new Button()
{ {
@@ -289,7 +246,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
else else
{ {
var cooldown = _cooldown var cooldown = _cooldown
? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionFailedCooldown)) ? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown))
: TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown)); : TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown));
NextOfferBar.Value = 1f - (float) (remaining / cooldown); NextOfferBar.Value = 1f - (float) (remaining / cooldown);

View File

@@ -83,17 +83,14 @@ public sealed partial class DungeonJob
var lastDirection = new Dictionary<Vector2i, Direction>(); var lastDirection = new Dictionary<Vector2i, Direction>();
costSoFar[start] = 0f; costSoFar[start] = 0f;
lastDirection[start] = Direction.Invalid; 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) while (remaining.Count > 0)
{ {
if (frontier.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(); 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), 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;
using Content.Shared.Salvage.Expeditions; using Content.Shared.Salvage.Expeditions;
@@ -18,7 +19,7 @@ public sealed partial class SalvageSystem
SpawnMission(missionparams, station.Value); SpawnMission(missionparams, station.Value);
data.ActiveMission = args.Index; 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); data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1);
UpdateConsoles(data); UpdateConsoles(data);
} }

View File

@@ -8,6 +8,7 @@ using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues; using Robust.Shared.CPUJob.JobQueues.Queues;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Content.Shared.Procedural;
using Content.Shared.Salvage.Expeditions; using Content.Shared.Salvage.Expeditions;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -26,7 +27,6 @@ public sealed partial class SalvageSystem
private const double SalvageJobTime = 0.002; private const double SalvageJobTime = 0.002;
private float _cooldown; private float _cooldown;
private float _failedCooldown;
private void InitializeExpeditions() private void InitializeExpeditions()
{ {
@@ -43,9 +43,7 @@ public sealed partial class SalvageSystem
SubscribeLocalEvent<SalvageStructureComponent, ExaminedEvent>(OnStructureExamine); SubscribeLocalEvent<SalvageStructureComponent, ExaminedEvent>(OnStructureExamine);
_cooldown = _configurationManager.GetCVar(CCVars.SalvageExpeditionCooldown); _cooldown = _configurationManager.GetCVar(CCVars.SalvageExpeditionCooldown);
_failedCooldown = _configurationManager.GetCVar(CCVars.SalvageExpeditionFailedCooldown);
_configurationManager.OnValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange); _configurationManager.OnValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange);
_configurationManager.OnValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange);
} }
private void OnExpeditionGetState(EntityUid uid, SalvageExpeditionComponent component, ref ComponentGetState args) private void OnExpeditionGetState(EntityUid uid, SalvageExpeditionComponent component, ref ComponentGetState args)
@@ -59,7 +57,6 @@ public sealed partial class SalvageSystem
private void ShutdownExpeditions() private void ShutdownExpeditions()
{ {
_configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange); _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange);
_configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange);
} }
private void SetCooldownChange(float obj) private void SetCooldownChange(float obj)
@@ -77,20 +74,6 @@ public sealed partial class SalvageSystem
_cooldown = 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) private void OnExpeditionShutdown(EntityUid uid, SalvageExpeditionComponent component, ComponentShutdown args)
{ {
component.Stream?.Stop(); component.Stream?.Stop();
@@ -110,7 +93,7 @@ public sealed partial class SalvageSystem
// Finish mission // Finish mission
if (TryComp<SalvageExpeditionDataComponent>(component.Station, out var data)) 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. component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown);
switch (expedition.MissionParams.MissionType) Announce(uid, Loc.GetString("salvage-expedition-mission-completed"));
{
// 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.ActiveMission = 0;
component.Cooldown = true; component.Cooldown = true;
UpdateConsoles(component); 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) private void GenerateMissions(SalvageExpeditionDataComponent component)
{ {
component.Missions.Clear(); 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++) for (var i = 0; i < MissionLimit; i++)
{ {
_random.Shuffle(configs); var mission = new SalvageMissionParams
var rating = difficulties[i];
foreach (var config in configs)
{ {
var mission = new SalvageMissionParams Index = component.NextIndex,
{ Seed = _random.Next(),
Index = component.NextIndex, Difficulty = "Moderate",
MissionType = config, };
Seed = _random.Next(),
Difficulty = rating,
};
component.Missions[component.NextIndex++] = mission; component.Missions[component.NextIndex++] = mission;
break;
}
} }
} }
@@ -271,13 +174,13 @@ public sealed partial class SalvageSystem
SalvageJobTime, SalvageJobTime,
EntityManager, EntityManager,
_timing, _timing,
_logManager,
_mapManager, _mapManager,
_prototypeManager, _prototypeManager,
_anchorable, _anchorable,
_biome, _biome,
_dungeon, _dungeon,
_metaData, _metaData,
this,
station, station,
missionParams, missionParams,
cancelToken.Token); cancelToken.Token);
@@ -290,19 +193,4 @@ public sealed partial class SalvageSystem
{ {
args.PushMarkup(Loc.GetString("salvage-expedition-structure-examine")); 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. // TODO: This is terrible but need bluespace harnesses or something.
var query = EntityQueryEnumerator<HumanoidAppearanceComponent, MobStateComponent, TransformComponent>(); 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) if (mobXform.MapUid != xform.MapUid)
continue; continue;
@@ -109,22 +109,11 @@ public sealed partial class SalvageSystem
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir()))); Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir())));
component.Stage = ExpeditionStage.Running; component.Stage = ExpeditionStage.Running;
Dirty(component); Dirty(args.MapUid, component);
} }
private void OnFTLStarted(ref FTLStartedEvent ev) 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) || if (!TryComp<SalvageExpeditionComponent>(ev.FromMapUid, out var expedition) ||
!TryComp<SalvageExpeditionDataComponent>(expedition.Station, out var station)) !TryComp<SalvageExpeditionDataComponent>(expedition.Station, out var station))
{ {
@@ -169,7 +158,7 @@ public sealed partial class SalvageSystem
Dirty(uid, comp); Dirty(uid, comp);
Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(2).Minutes))); 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; comp.Stage = ExpeditionStage.Countdown;
Dirty(uid, comp); Dirty(uid, comp);
@@ -210,72 +199,5 @@ public sealed partial class SalvageSystem
QueueDel(uid); 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 IChatManager _chat = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AnchorableSystem _anchorable = default!; [Dependency] private readonly AnchorableSystem _anchorable = default!;
[Dependency] private readonly BiomeSystem _biome = default!; [Dependency] private readonly BiomeSystem _biome = default!;
[Dependency] private readonly CargoSystem _cargo = default!;
[Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly MapLoaderSystem _map = default!; [Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;

View File

@@ -1,3 +1,4 @@
using System.Collections;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
@@ -19,6 +20,7 @@ using Content.Shared.Parallax.Biomes;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Procedural; using Content.Shared.Procedural;
using Content.Shared.Procedural.Loot; using Content.Shared.Procedural.Loot;
using Content.Shared.Random;
using Content.Shared.Salvage; using Content.Shared.Salvage;
using Content.Shared.Salvage.Expeditions; using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers; using Content.Shared.Salvage.Expeditions.Modifiers;
@@ -29,6 +31,7 @@ using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Salvage; namespace Content.Server.Salvage;
@@ -42,22 +45,23 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
private readonly BiomeSystem _biome; private readonly BiomeSystem _biome;
private readonly DungeonSystem _dungeon; private readonly DungeonSystem _dungeon;
private readonly MetaDataSystem _metaData; private readonly MetaDataSystem _metaData;
private readonly SalvageSystem _salvage;
public readonly EntityUid Station; public readonly EntityUid Station;
private readonly SalvageMissionParams _missionParams; private readonly SalvageMissionParams _missionParams;
private readonly ISawmill _sawmill;
public SpawnSalvageMissionJob( public SpawnSalvageMissionJob(
double maxTime, double maxTime,
IEntityManager entManager, IEntityManager entManager,
IGameTiming timing, IGameTiming timing,
ILogManager logManager,
IMapManager mapManager, IMapManager mapManager,
IPrototypeManager protoManager, IPrototypeManager protoManager,
AnchorableSystem anchorable, AnchorableSystem anchorable,
BiomeSystem biome, BiomeSystem biome,
DungeonSystem dungeon, DungeonSystem dungeon,
MetaDataSystem metaData, MetaDataSystem metaData,
SalvageSystem salvage,
EntityUid station, EntityUid station,
SalvageMissionParams missionParams, SalvageMissionParams missionParams,
CancellationToken cancellation = default) : base(maxTime, cancellation) CancellationToken cancellation = default) : base(maxTime, cancellation)
@@ -70,15 +74,17 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
_biome = biome; _biome = biome;
_dungeon = dungeon; _dungeon = dungeon;
_metaData = metaData; _metaData = metaData;
_salvage = salvage;
Station = station; Station = station;
_missionParams = missionParams; _missionParams = missionParams;
_sawmill = logManager.GetSawmill("salvage_job");
#if !DEBUG
_sawmill.Level = LogLevel.Info;
#endif
} }
protected override async Task<bool> Process() protected override async Task<bool> Process()
{ {
Logger.DebugS("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}"); _sawmill.Debug("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}");
var config = _missionParams.MissionType;
var mapId = _mapManager.CreateMap(); var mapId = _mapManager.CreateMap();
var mapUid = _mapManager.GetMapEntityId(mapId); var mapUid = _mapManager.GetMapEntityId(mapId);
_mapManager.AddUninitializedMap(mapId); _mapManager.AddUninitializedMap(mapId);
@@ -88,16 +94,17 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
// Setup mission configs // Setup mission configs
// As we go through the config the rating will deplete so we'll go for most important to least important. // 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>() var mission = _entManager.System<SharedSalvageSystem>()
.GetMission(_missionParams.MissionType, _missionParams.Difficulty, _missionParams.Seed); .GetMission(difficultyProto, _missionParams.Seed);
var missionBiome = _prototypeManager.Index<SalvageBiomeMod>(mission.Biome); var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
BiomeComponent? biome = null;
if (missionBiome.BiomePrototype != null) if (missionBiome.BiomePrototype != null)
{ {
biome = _entManager.AddComponent<BiomeComponent>(mapUid); var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
var biomeSystem = _entManager.System<BiomeSystem>(); var biomeSystem = _entManager.System<BiomeSystem>();
biomeSystem.SetTemplate(biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype)); biomeSystem.SetTemplate(biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
biomeSystem.SetSeed(biome, mission.Seed); biomeSystem.SetSeed(biome, mission.Seed);
@@ -125,7 +132,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
{ {
var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid); var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
lighting.AmbientLightColor = mission.Color.Value; 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.Station = Station;
expedition.EndTime = _timing.CurTime + mission.Duration; expedition.EndTime = _timing.CurTime + mission.Duration;
expedition.MissionParams = _missionParams; expedition.MissionParams = _missionParams;
expedition.Difficulty = _missionParams.Difficulty;
expedition.Rewards = mission.Rewards;
// Don't want consoles to have the incorrect name until refreshed. // Don't want consoles to have the incorrect name until refreshed.
var ftlUid = _entManager.CreateEntityUninitialized("FTLPoint", new EntityCoordinates(mapUid, grid.TileSizeHalfVector)); var ftlUid = _entManager.CreateEntityUninitialized("FTLPoint", new EntityCoordinates(mapUid, grid.TileSizeHalfVector));
@@ -151,29 +156,23 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
// We'll use the dungeon rotation as the spawn angle // We'll use the dungeon rotation as the spawn angle
var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed); var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed);
Dungeon dungeon = default!; 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));
if (config != SalvageMissionType.Mining) // Aborty
if (dungeon.Rooms.Count == 0)
{ {
var maxDungeonOffset = minDungeonOffset + 12; return false;
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;
} }
expedition.DungeonLocation = dungeonOffset;
List<Vector2i> reservedTiles = new(); List<Vector2i> reservedTiles = new();
foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false)) foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false))
@@ -184,24 +183,14 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
reservedTiles.Add(tile.GridIndices); reservedTiles.Add(tile.GridIndices);
} }
// Mission setup var budgetEntries = new List<IBudgetEntry>();
switch (config)
{ /*
case SalvageMissionType.Mining: * GUARANTEED LOOT
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();
}
// Handle loot
// We'll always add this loot if possible // We'll always add this loot if possible
// mainly used for ore layers.
foreach (var lootProto in _prototypeManager.EnumeratePrototypes<SalvageLootPrototype>()) foreach (var lootProto in _prototypeManager.EnumeratePrototypes<SalvageLootPrototype>())
{ {
if (!lootProto.Guaranteed) if (!lootProto.Guaranteed)
@@ -210,10 +199,104 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
await SpawnDungeonLoot(dungeon, missionBiome, lootProto, mapUid, grid, random, reservedTiles); 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; 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++) 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); SalvageForced = CVarDef.Create("salvage.forced", "", CVar.SERVERONLY);
/// <summary> /// <summary>
/// Cooldown for successful missions. /// Duration for missions
/// </summary> /// </summary>
public static readonly CVarDef<float> 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> 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 * 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.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.Loot; namespace Content.Shared.Procedural.Loot;
@@ -17,14 +15,6 @@ public sealed class SalvageLootPrototype : IPrototype
/// </summary> /// </summary>
[DataField("guaranteed")] public bool Guaranteed; [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> /// <summary>
/// All of the loot rules /// All of the loot rules
/// </summary> /// </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; public float Cost { get; private set; } = 0f;
/// <inheritdoc/> /// <inheritdoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))] [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null; public List<string>? Biomes { get; private set; } = null;
/// <summary> /// <summary>

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Salvage.Expeditions.Modifiers;
/// Affects the biome to be used for salvage. /// Affects the biome to be used for salvage.
/// </summary> /// </summary>
[Prototype("salvageBiomeMod")] [Prototype("salvageBiomeMod")]
public sealed class SalvageBiomeMod : IPrototype, ISalvageMod public sealed class SalvageBiomeModPrototype : IPrototype, ISalvageMod
{ {
[IdDataField] public string ID { get; } = default!; [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; namespace Content.Shared.Salvage.Expeditions.Modifiers;
[Prototype("salvageDungeonMod")] [Prototype("salvageDungeonMod")]
public sealed class SalvageDungeonMod : IPrototype, IBiomeSpecificMod public sealed class SalvageDungeonModPrototype : IPrototype, IBiomeSpecificMod
{ {
[IdDataField] public string ID { get; } = default!; [IdDataField] public string ID { get; } = default!;
@@ -17,7 +17,7 @@ public sealed class SalvageDungeonMod : IPrototype, IBiomeSpecificMod
public float Cost { get; private set; } = 0f; public float Cost { get; private set; } = 0f;
/// <inheridoc/> /// <inheridoc/>
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))] [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeModPrototype>))]
public List<string>? Biomes { get; private set; } = null; public List<string>? Biomes { get; private set; } = null;
/// <summary> /// <summary>

View File

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

View File

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

View File

@@ -5,20 +5,14 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Salvage.Expeditions; namespace Content.Shared.Salvage.Expeditions;
[Prototype("salvageFaction")] [Prototype("salvageFaction")]
public sealed class SalvageFactionPrototype : IPrototype, ISalvageMod public sealed class SalvageFactionPrototype : IPrototype
{ {
[IdDataField] public string ID { get; } = default!; [IdDataField] public string ID { get; } = default!;
[DataField("desc")] public string Description { get; private set; } = string.Empty; [DataField("desc")] public string Description { get; private set; } = string.Empty;
/// <summary> [ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)]
/// Cost for difficulty modifiers. public List<SalvageMobEntry> MobGroups = new();
/// </summary>
[DataField("cost")]
public float Cost { get; private set; } = 0f;
[ViewVariables(VVAccess.ReadWrite), DataField("groups", required: true)]
public List<SalvageMobGroup> MobGroups = default!;
/// <summary> /// <summary>
/// Miscellaneous data for factions. /// 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 System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Dataset; using Content.Shared.Dataset;
using Content.Shared.Procedural;
using Content.Shared.Procedural.Loot;
using Content.Shared.Random; using Content.Shared.Random;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
using Content.Shared.Salvage.Expeditions; using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers; using Content.Shared.Salvage.Expeditions.Modifiers;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -13,73 +17,14 @@ namespace Content.Shared.Salvage;
public abstract class SharedSalvageSystem : EntitySystem public abstract class SharedSalvageSystem : EntitySystem
{ {
[Dependency] private readonly ILocalizationManager _loc = default!; [Dependency] protected readonly IConfigurationManager CfgManager = default!;
[Dependency] private readonly IPrototypeManager _proto = 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> /// <summary>
/// Gets the amount of structures to destroy. /// Main loot table for salvage expeditions.
/// </summary> /// </summary>
public int GetStructureCount(DifficultyRating baseRating) [ValidatePrototypeId<SalvageLootPrototype>]
{ public const string ExpeditionsLootProto = "SalvageLoot";
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;
}
public static string GetFTLName(DatasetPrototype dataset, int seed) 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))}"; 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 // This is on shared to ensure the client display for missions and what the server generates are consistent
var rating = (float) GetDifficulty(difficulty); var modifierBudget = difficulty.ModifierBudget;
// Don't want easy missions to have any negative modifiers but also want
// easy to be a 1 for difficulty.
rating -= 1f;
var rand = new System.Random(seed); var rand = new System.Random(seed);
var faction = GetMod<SalvageFactionPrototype>(rand, ref rating);
var biome = GetMod<SalvageBiomeMod>(rand, ref rating); // Run budget in order of priority
var dungeon = GetBiomeMod<SalvageDungeonMod>(biome.ID, rand, ref rating); // - 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 mods = new List<string>();
var air = GetBiomeMod<SalvageAirMod>(biome.ID, rand, ref rating);
if (air.Description != string.Empty) if (air.Description != string.Empty)
{ {
mods.Add(air.Description); mods.Add(air.Description);
} }
// only show the description if there is an atmosphere since wont matter otherwise // 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) if (temp.Description != string.Empty && !air.Space)
{ {
mods.Add(temp.Description); mods.Add(temp.Description);
} }
var light = GetBiomeMod<SalvageLightMod>(biome.ID, rand, ref rating);
if (light.Description != string.Empty) if (light.Description != string.Empty)
{ {
mods.Add(light.Description); mods.Add(light.Description);
} }
var time = GetMod<SalvageTimeMod>(rand, ref rating); var duration = TimeSpan.FromSeconds(CfgManager.GetCVar(CCVars.SalvageExpeditionDuration));
// 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) return new SalvageMission(seed, dungeon.ID, faction.ID, biome.ID, air.ID, temp.Temperature, light.Color, duration, mods);
{
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);
} }
public T GetBiomeMod<T>(string biome, System.Random rand, ref float rating) where T : class, IPrototype, IBiomeSpecificMod 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(); 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] [salvage]
expedition_cooldown = 30.0 expedition_cooldown = 30.0
expedition_failed_cooldown = 30.0
[shuttle] [shuttle]
grid_fill = false grid_fill = false

View File

@@ -4,8 +4,7 @@ salvage-expedition-structure-remaining = {$count ->
*[other] {$count} structures remaining. *[other] {$count} structures remaining.
} }
salvage-expedition-megafauna-remaining = {$count} megafauna remaining. salvage-expedition-type = Mission
salvage-expedition-window-title = Salvage expeditions salvage-expedition-window-title = Salvage expeditions
salvage-expedition-window-difficulty = Difficulty: salvage-expedition-window-difficulty = Difficulty:
salvage-expedition-window-details = Details: salvage-expedition-window-details = Details:
@@ -13,31 +12,17 @@ salvage-expedition-window-hostiles = Hostiles:
salvage-expedition-window-duration = Duration: salvage-expedition-window-duration = Duration:
salvage-expedition-window-biome = Biome: salvage-expedition-window-biome = Biome:
salvage-expedition-window-modifiers = Modifiers: salvage-expedition-window-modifiers = Modifiers:
salvage-expedition-window-rewards = Rewards:
salvage-expedition-window-claimed = Claimed salvage-expedition-window-claimed = Claimed
salvage-expedition-window-claim = Claim salvage-expedition-window-claim = Claim
salvage-expedition-window-next = Next offer 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-Moderate = Moderate
salvage-expedition-difficulty-Hazardous = Hazardous salvage-expedition-difficulty-Hazardous = Hazardous
salvage-expedition-difficulty-Extreme = Extreme salvage-expedition-difficulty-Extreme = Extreme
salvage-expedition-difficulty-players = Recommended salvagers:
# Runner # Runner
salvage-expedition-not-all-present = Not all salvagers are aboard the shuttle! salvage-expedition-not-all-present = Not all salvagers are aboard the shuttle!

View File

@@ -164,7 +164,14 @@
parent: MobCarp parent: MobCarp
suffix: Dungeon suffix: Dungeon
components: components:
- type: MobThresholds
thresholds:
0: Alive
50: Dead
- type: SlowOnDamage
speedModifierThresholds:
25: 0.7
- type: MeleeWeapon - type: MeleeWeapon
damage: damage:
types: types:
Slash: 5 Slash: 6

View File

@@ -60,6 +60,9 @@
thresholds: thresholds:
0: Alive 0: Alive
50: Dead 50: Dead
- type: SlowOnDamage
speedModifierThresholds:
25: 0.5
- type: Stamina - type: Stamina
critThreshold: 200 critThreshold: 200
- type: Bloodstream - type: Bloodstream
@@ -129,7 +132,7 @@
- type: MobThresholds - type: MobThresholds
thresholds: thresholds:
0: Alive 0: Alive
75: Dead 100: Dead
- type: Stamina - type: Stamina
critThreshold: 300 critThreshold: 300
- type: SlowOnDamage - type: SlowOnDamage
@@ -162,18 +165,16 @@
- type: MobThresholds - type: MobThresholds
thresholds: thresholds:
0: Alive 0: Alive
150: Dead 80: Dead
- type: SlowOnDamage
speedModifierThresholds:
40: 0.7
- type: MeleeWeapon - type: MeleeWeapon
damage: damage:
groups: groups:
Brute: 5 Brute: 6
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWalkSpeed: 2.0 baseSprintSpeed: 4
baseSprintSpeed: 2.5
- type: SlowOnDamage
speedModifierThresholds:
100: 0.4
50: 0.7
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@@ -202,20 +203,15 @@
thresholds: thresholds:
0: Alive 0: Alive
300: Dead 300: Dead
- type: Stamina - type: SlowOnDamage
critThreshold: 1500 speedModifierThresholds:
150: 0.7
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWalkSpeed: 2.8
baseSprintSpeed: 3.8
- type: MeleeWeapon - type: MeleeWeapon
hidden: true hidden: true
damage: damage:
groups: groups:
Brute: 20 Brute: 12
- type: SlowOnDamage
speedModifierThresholds:
250: 0.4
200: 0.7
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@@ -246,12 +242,9 @@
- type: MobThresholds - type: MobThresholds
thresholds: thresholds:
0: Alive 0: Alive
200: Dead 100: Dead
- type: Stamina
critThreshold: 550
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWalkSpeed: 2.3 baseSprintSpeed: 4
baseSprintSpeed: 4.2
- type: MeleeWeapon - type: MeleeWeapon
hidden: true hidden: true
damage: damage:
@@ -259,8 +252,7 @@
Brute: 10 Brute: 10
- type: SlowOnDamage - type: SlowOnDamage
speedModifierThresholds: speedModifierThresholds:
150: 0.5 50: 0.7
100: 0.7
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@@ -285,16 +277,13 @@
layers: layers:
- map: ["enum.DamageStateVisualLayers.Base"] - map: ["enum.DamageStateVisualLayers.Base"]
state: running state: running
- type: Stamina
critThreshold: 250
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWalkSpeed: 2.7
baseSprintSpeed: 6.0 baseSprintSpeed: 6.0
- type: MeleeWeapon - type: MeleeWeapon
hidden: true hidden: true
damage: damage:
groups: groups:
Brute: 3 Brute: 5
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@@ -332,12 +321,13 @@
- type: MobThresholds - type: MobThresholds
thresholds: thresholds:
0: Alive 0: Alive
75: Dead 50: Dead
- type: SlowOnDamage
speedModifierThresholds:
25: 0.7
- type: HTN - type: HTN
rootTask: rootTask:
task: SimpleRangedHostileCompound task: SimpleRangedHostileCompound
- type: Stamina
critThreshold: 300
- type: RechargeBasicEntityAmmo - type: RechargeBasicEntityAmmo
rechargeCooldown: 0.75 rechargeCooldown: 0.75
- type: BasicEntityAmmoProvider - type: BasicEntityAmmoProvider
@@ -351,9 +341,6 @@
availableModes: availableModes:
- FullAuto - FullAuto
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
- type: SlowOnDamage
speedModifierThresholds:
50: 0.4
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:

View File

@@ -144,21 +144,22 @@
components: components:
- type: GhostRole - type: GhostRole
description: ghost-role-information-space-dragon-dungeon-description description: ghost-role-information-space-dragon-dungeon-description
# less tanky, no crit - type: SlowOnDamage
speedModifierThresholds:
100: 0.7
- type: MobThresholds - type: MobThresholds
thresholds: thresholds:
0: Alive 0: Alive
300: Dead 200: Dead
# less meat spawned since it's a lot easier to kill # less meat spawned since it's a lot easier to kill
- type: Butcherable - type: Butcherable
spawned: spawned:
- id: FoodMeatDragon - id: FoodMeatDragon
amount: 1 amount: 1
# half damage, spread evenly
- type: MeleeWeapon - type: MeleeWeapon
damage: damage:
groups: groups:
Brute: 15 Brute: 12
- type: entity - type: entity
id: ActionSpawnRift id: ActionSpawnRift

View File

@@ -13,16 +13,16 @@
- type: ItemCooldown - type: ItemCooldown
- type: MeleeWeapon - type: MeleeWeapon
damage: damage:
groups:
Brute: 5
types: types:
Blunt: 2.5
Piercing: 2.5
Structural: 10 Structural: 10
- type: Wieldable - type: Wieldable
- type: IncreaseDamageOnWield - type: IncreaseDamageOnWield
damage: damage:
groups:
Brute: 10
types: types:
Blunt: 5
Piercing: 5
Structural: 10 Structural: 10
- type: Item - type: Item
size: 80 size: 80
@@ -44,7 +44,7 @@
- type: MeleeWeapon - type: MeleeWeapon
attackRate: 1.5 attackRate: 1.5
damage: damage:
groups:
Brute: 10
types: types:
Blunt: 5
Piercing: 5
Structural: 10 Structural: 10

View File

@@ -44,6 +44,7 @@
- type: entity - type: entity
id: Thruster id: Thruster
name: thruster
parent: [ BaseThruster, ConstructibleMachine ] parent: [ BaseThruster, ConstructibleMachine ]
components: components:
- type: Thruster - type: Thruster
@@ -64,6 +65,15 @@
visible: false visible: false
offset: 0, 1 offset: 0, 1
- type: entity
id: ThrusterUnanchored
parent: Thruster
components:
- type: Transform
anchored: false
- type: Physics
bodyType: Dynamic
- type: entity - type: entity
id: DebugThruster id: DebugThruster
parent: BaseThruster parent: BaseThruster
@@ -91,6 +101,7 @@
- type: entity - type: entity
id: Gyroscope id: Gyroscope
name: gyroscope
parent: [ BaseThruster, ConstructibleMachine ] parent: [ BaseThruster, ConstructibleMachine ]
components: components:
- type: Thruster - type: Thruster
@@ -133,6 +144,15 @@
- type: StaticPrice - type: StaticPrice
price: 2000 price: 2000
- type: entity
id: GyroscopeUnanchored
parent: Gyroscope
components:
- type: Transform
anchored: false
- type: Physics
bodyType: Dynamic
- type: entity - type: entity
id: DebugGyroscope id: DebugGyroscope
parent: BaseThruster 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 - type: salvageFaction
id: Xenos id: Xenos
groups: entries:
- entries: - proto: MobXeno
- id: MobXeno - proto: MobXenoDrone
amount: 2 cost: 2
maxAmount: 3 - proto: MobXenoPraetorian
- id: MobXenoDrone cost: 5
amount: 1 prob: 0.1
- entries: - proto: MobXenoQueen
- id: MobXenoPraetorian cost: 10
amount: 1 prob: 0.02
maxAmount: 2 - proto: MobXenoRavager
prob: 0.5 cost: 5
- entries: - proto: MobXenoRouny
- id: MobXenoDrone cost: 3
amount: 0 prob: 0.02
maxAmount: 2 - proto: MobXenoSpitter
prob: 0.25 - proto: WeaponTurretXeno
- entries: prob: 0.1
- 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
configs: configs:
DefenseStructure: XenoWardingTower DefenseStructure: XenoWardingTower
Mining: Xenos
Megafauna: MobXenoQueen Megafauna: MobXenoQueen
- type: salvageFaction - type: salvageFaction
id: Carps id: Carps
groups: entries:
- entries: - proto: MobCarpDungeon
- id: MobCarpDungeon # These do too much damage for salvage, need nerfs
amount: 1 #- proto: MobCarpHolo
maxAmount: 4 # cost: 5
- entries: # prob: 0.1
- id: MobCarpMagic #- proto: MobCarpMagic
amount: 1 # cost: 5
maxAmount: 3 # prob: 0.1
prob: 0.5 - proto: MobDragonDungeon
- entries: cost: 10
- id: MobCarpHolo prob: 0.02
amount: 1
maxAmount: 2
prob: 0.25
configs: configs:
DefenseStructure: CarpStatue DefenseStructure: CarpStatue
Mining: Carps
Megafauna: MobDragonDungeon 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 # - Low value
- type: salvageLoot - type: salvageLoot
id: OreTin id: OreTin
desc: Veins of steel
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -14,7 +131,6 @@
- type: salvageLoot - type: salvageLoot
id: OreQuartz id: OreQuartz
desc: Veins of quartz
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -27,7 +143,6 @@
# - Medium value # - Medium value
- type: salvageLoot - type: salvageLoot
id: OreGold id: OreGold
desc: Veins of gold ore
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -39,7 +154,6 @@
- type: salvageLoot - type: salvageLoot
id: OreSilver id: OreSilver
desc: Veins of silver ore
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -52,7 +166,6 @@
# - High value # - High value
- type: salvageLoot - type: salvageLoot
id: OrePlasma id: OrePlasma
desc: Veins of plasma ore
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -64,7 +177,6 @@
- type: salvageLoot - type: salvageLoot
id: OreUranium id: OreUranium
desc: Veins of uranium ore
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -76,7 +188,6 @@
- type: salvageLoot - type: salvageLoot
id: OreBananium id: OreBananium
desc: Veins of bananium ore
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot
@@ -88,7 +199,6 @@
- type: salvageLoot - type: salvageLoot
id: OreArtifactFragment id: OreArtifactFragment
desc: artifact fragment-embedded rock
guaranteed: true guaranteed: true
loots: loots:
- !type:BiomeMarkerLoot - !type:BiomeMarkerLoot

View File

@@ -4,58 +4,44 @@
parent: FTLPoint parent: FTLPoint
# Biome mods -> at least 1 required # Biome mods -> at least 1 required
- type: salvageBiomeMod
id: Caves
biome: Caves
- type: salvageBiomeMod - type: salvageBiomeMod
id: Grasslands id: Grasslands
biome: Grasslands biome: Grasslands
- type: salvageBiomeMod
id: Snow
cost: 1
biome: Snow
- type: salvageBiomeMod - type: salvageBiomeMod
id: Lava id: Lava
cost: 2 cost: 2
biome: Lava biome: Lava
- type: salvageBiomeMod
id: Snow
biome: Snow
- type: salvageBiomeMod
id: Caves
cost: 1
biome: Caves
#- type: salvageBiomeMod #- type: salvageBiomeMod
# id: Space # id: Space
# cost: 1 # cost: 1
# weather: false # weather: false
# biome: null # 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 # Light mods -> required
# At some stage with sub-biomes this will probably be moved onto the biome itself
- type: salvageLightMod - type: salvageLightMod
id: Daylight id: Daylight
desc: Daylight desc: Daylight
color: "#D8B059" color: "#D8B059"
biomes: biomes:
- Grasslands - Grasslands
- type: salvageLightMod - type: salvageLightMod
id: Lavalight id: Lavalight
desc: Daylight desc: Daylight
color: "#A34931" color: "#A34931"
biomes: biomes:
- Lava - Lava
- type: salvageLightMod - type: salvageLightMod
id: Evening id: Evening
@@ -65,24 +51,169 @@
- type: salvageLightMod - type: salvageLightMod
id: Night id: Night
desc: Night time desc: Night time
cost: 2 cost: 1
color: null color: null
# Time mods -> at least 1 required # Temperatures
- type: salvageTimeMod - type: salvageTemperatureMod
id: StandardTime id: RoomTemp
cost: 0
- type: salvageTimeMod - type: salvageTemperatureMod
id: RushTime id: Hot
desc: Rush
minDuration: 420
maxDuration: 465
cost: 1 cost: 1
temperature: 323.15 # 50C
biomes:
- Caves
#- LowDesert
- Grasslands
- Lava
# Misc mods - type: salvageTemperatureMod
- type: salvageMod id: Burning
id: LongDistance desc: High temperature
desc: Long distance 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: 2
biomes:
- Caves
- Lava
- type: salvageAirMod
id: Breathable
cost: 0
gases:
- 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
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- Lava
- type: salvageAirMod
id: Poisoned
cost: 2
desc: Dangerous atmosphere
gases:
- 21.824779 # oxygen
- 77.10312 # nitrogen
- 10 # carbon dioxide
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- Lava
- type: salvageAirMod
id: Poison
cost: 3
desc: Toxic atmosphere
gases:
- 21.824779 # oxygen
- 0
- 82.10312 # carbon dioxide
biomes:
- Caves
- Snow
- Lava
- type: salvageAirMod
id: Plasma
cost: 4
desc: Toxic atmosphere
gases:
- 0
- 0
- 0
- 103.927899 # plasma
biomes:
- Caves
- Lava
- type: salvageAirMod
id: Burnable
cost: 5
desc: Volatile atmosphere
gases:
- 21.824779 # oxygen
- 0
- 0
- 82.10312 # plasma
biomes:
- Caves
- Lava
# Weather mods -> not required
#- type: salvageWeatherMod
# id: SnowfallHeavy
# weather: SnowfallHeavy
# cost: 1
#
#- type: salvageWeatherMod
# id: Rain
# weather: Rain
# Dungeons # Dungeons
# For now just simple 1-dungeon setups # For now just simple 1-dungeon setups
@@ -100,160 +231,3 @@
proto: LavaBrig proto: LavaBrig
biomes: biomes:
- Lava - Lava
# Air mixtures
- type: salvageAirMod
id: Space
desc: No atmosphere
space: true
cost: 1
biomes:
- Caves
- Lava
- type: salvageAirMod
id: Breathable
gases:
- 21.824779 # oxygen
- 82.10312 # nitrogen
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- type: salvageAirMod
id: Sleepy
cost: 1
desc: Dangerous atmosphere
gases:
- 21.824779 # oxygen
- 72.10312 # nitrogen
- 0
- 0
- 0
- 0
- 0
- 10 # nitrous oxide
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- Lava
- type: salvageAirMod
id: Poisoned
cost: 2
desc: Dangerous atmosphere
gases:
- 21.824779 # oxygen
- 77.10312 # nitrogen
- 10 # carbon dioxide
biomes:
- Caves
#- LowDesert
- Snow
- Grasslands
- Lava
- type: salvageAirMod
id: Poison
cost: 3
desc: Toxic atmosphere
gases:
- 21.824779 # oxygen
- 0
- 82.10312 # carbon dioxide
biomes:
- Caves
- Snow
- Lava
- type: salvageAirMod
id: Plasma
cost: 4
desc: Toxic atmosphere
gases:
- 0
- 0
- 0
- 103.927899 # plasma
biomes:
- 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
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
- 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