diff --git a/Content.Client/Salvage/UI/SalvageExpeditionWindow.xaml.cs b/Content.Client/Salvage/UI/SalvageExpeditionWindow.xaml.cs index 7875ac6ef04..150f4a29573 100644 --- a/Content.Client/Salvage/UI/SalvageExpeditionWindow.xaml.cs +++ b/Content.Client/Salvage/UI/SalvageExpeditionWindow.xaml.cs @@ -4,7 +4,6 @@ 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; @@ -54,10 +53,8 @@ public void UpdateState(SalvageExpeditionConsoleState state) for (var i = 0; i < state.Missions.Count; i++) { var missionParams = state.Missions[i]; - var difficultyId = "Moderate"; - var difficultyProto = _prototype.Index(difficultyId); - // TODO: Selectable difficulty soon. - var mission = _salvage.GetMission(difficultyProto, missionParams.Seed); + var config = missionParams.MissionType; + var mission = _salvage.GetMission(missionParams.MissionType, missionParams.Difficulty, missionParams.Seed); // Mission title var missionStripe = new StripeBack() @@ -67,7 +64,7 @@ public void UpdateState(SalvageExpeditionConsoleState state) missionStripe.AddChild(new Label() { - Text = Loc.GetString($"salvage-expedition-type"), + Text = Loc.GetString($"salvage-expedition-type-{config.ToString()}"), HorizontalAlignment = HAlignment.Center, Margin = new Thickness(0f, 5f, 0f, 5f), }); @@ -84,25 +81,48 @@ public void UpdateState(SalvageExpeditionConsoleState state) Text = Loc.GetString("salvage-expedition-window-difficulty") }); - var difficultyColor = difficultyProto.Color; + 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(); + } lBox.AddChild(new Label { - Text = Loc.GetString("salvage-expedition-difficulty-Moderate"), + Text = Loc.GetString($"salvage-expedition-difficulty-{missionParams.Difficulty.ToString()}"), 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-difficulty-players"), - HorizontalAlignment = HAlignment.Left, + Text = Loc.GetString("salvage-expedition-window-details") }); lBox.AddChild(new Label { - Text = difficultyProto.RecommendedPlayers.ToString(), + Text = details, FontColorOverride = StyleNano.NanoGold, HorizontalAlignment = HAlignment.Left, Margin = new Thickness(0f, 0f, 0f, 5f), @@ -148,7 +168,7 @@ public void UpdateState(SalvageExpeditionConsoleState state) lBox.AddChild(new Label { - Text = Loc.GetString(_prototype.Index(biome).ID), + Text = Loc.GetString(_prototype.Index(biome).ID), FontColorOverride = StyleNano.NanoGold, HorizontalAlignment = HAlignment.Left, Margin = new Thickness(0f, 0f, 0f, 5f), @@ -170,6 +190,29 @@ public void UpdateState(SalvageExpeditionConsoleState state) Margin = new Thickness(0f, 0f, 0f, 5f), }); + lBox.AddChild(new Label() + { + Text = Loc.GetString("salvage-expedition-window-rewards") + }); + + var rewards = new Dictionary(); + foreach (var reward in mission.Rewards) + { + var name = _prototype.Index(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() { @@ -246,7 +289,7 @@ protected override void FrameUpdate(FrameEventArgs args) else { var cooldown = _cooldown - ? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown)) + ? TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionFailedCooldown)) : TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.SalvageExpeditionCooldown)); NextOfferBar.Value = 1f - (float) (remaining / cooldown); diff --git a/Content.Server/Procedural/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob.PostGen.cs index 4718de7c0c0..1bbcc438ef5 100644 --- a/Content.Server/Procedural/DungeonJob.PostGen.cs +++ b/Content.Server/Procedural/DungeonJob.PostGen.cs @@ -83,14 +83,17 @@ private async Task PostGen(AutoCablingPostGen gen, Dungeon dungeon, EntityUid gr var lastDirection = new Dictionary(); costSoFar[start] = 0f; lastDirection[start] = Direction.Invalid; + var tagQuery = _entManager.GetEntityQuery(); + // 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) { - var newStart = remaining.First(); - frontier.Enqueue(newStart, 0f); - lastDirection[newStart] = Direction.Invalid; + frontier.Enqueue(remaining.First(), 0f); } var node = frontier.Dequeue(); diff --git a/Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs new file mode 100644 index 00000000000..a3ec66ff302 --- /dev/null +++ b/Content.Server/Salvage/Expeditions/SalvageEliminationExpeditionComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Salvage; + +namespace Content.Server.Salvage.Expeditions.Structure; + +/// +/// Tracks expedition data for +/// +[RegisterComponent, Access(typeof(SalvageSystem), typeof(SpawnSalvageMissionJob))] +public sealed partial class SalvageEliminationExpeditionComponent : Component +{ + /// + /// List of mobs that need to be killed for the mission to be complete. + /// + [DataField("megafauna")] + public List Megafauna = new(); +} diff --git a/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs index 2bc00397bc9..ee4b7c88cd6 100644 --- a/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs +++ b/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs @@ -49,4 +49,16 @@ public sealed partial class SalvageExpeditionComponent : SharedSalvageExpedition { Params = AudioParams.Default.WithVolume(-5), }; + + /// + /// The difficulty this mission had or, in the future, was selected. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("difficulty")] + public DifficultyRating Difficulty; + + /// + /// List of items to order on mission completion + /// + [ViewVariables(VVAccess.ReadWrite), DataField("rewards", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List Rewards = default!; } diff --git a/Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs new file mode 100644 index 00000000000..b9a1379aab9 --- /dev/null +++ b/Content.Server/Salvage/Expeditions/SalvageMiningExpeditionComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Salvage; + +namespace Content.Server.Salvage.Expeditions; + +/// +/// Tracks expedition data for +/// +[RegisterComponent, Access(typeof(SalvageSystem))] +public sealed partial class SalvageMiningExpeditionComponent : Component +{ + /// + /// Entities that were present on the shuttle and match the loot tax. + /// + [DataField("exemptEntities")] + public List ExemptEntities = new(); +} diff --git a/Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs new file mode 100644 index 00000000000..0dec1f81adf --- /dev/null +++ b/Content.Server/Salvage/Expeditions/SalvageStructureExpeditionComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Salvage; + +namespace Content.Server.Salvage.Expeditions.Structure; + +/// +/// Tracks expedition data for +/// +[RegisterComponent, Access(typeof(SalvageSystem), typeof(SpawnSalvageMissionJob))] +public sealed partial class SalvageStructureExpeditionComponent : Component +{ + [DataField("structures")] + public List Structures = new(); +} diff --git a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs index 1d6b0948597..6439d672509 100644 --- a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs +++ b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs @@ -1,6 +1,7 @@ using Content.Server.Station.Components; using Content.Shared.Popups; using Content.Shared.Procedural; +using Content.Shared.Salvage; using Content.Shared.Salvage.Expeditions; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; @@ -45,7 +46,7 @@ private void OnSalvageClaimMessage(EntityUid uid, SalvageExpeditionConsoleCompon PlayDenySound(uid, component); _popupSystem.PopupEntity(Loc.GetString("shuttle-ftl-proximity"), uid, PopupType.MediumCaution); - UpdateConsoles((station.Value, data)); + UpdateConsoles(data); return; } // end of Frontier proximity check @@ -53,9 +54,9 @@ private void OnSalvageClaimMessage(EntityUid uid, SalvageExpeditionConsoleCompon SpawnMission(missionparams, station.Value); data.ActiveMission = args.Index; - var mission = GetMission(_prototypeManager.Index(missionparams.Difficulty), missionparams.Seed); + var mission = GetMission(missionparams.MissionType, missionparams.Difficulty, missionparams.Seed); data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1); - UpdateConsoles((station.Value, data)); + UpdateConsoles(data); } private void OnSalvageConsoleInit(Entity console, ref ComponentInit args) @@ -68,7 +69,7 @@ private void OnSalvageConsoleParent(Entity co UpdateConsole(console); } - private void UpdateConsoles(Entity component) + private void UpdateConsoles(SalvageExpeditionDataComponent component) { var state = GetState(component); diff --git a/Content.Server/Salvage/SalvageSystem.Expeditions.cs b/Content.Server/Salvage/SalvageSystem.Expeditions.cs index 451d7a9b897..8fe0deafb23 100644 --- a/Content.Server/Salvage/SalvageSystem.Expeditions.cs +++ b/Content.Server/Salvage/SalvageSystem.Expeditions.cs @@ -14,6 +14,10 @@ using Content.Server.Station.Systems; using Content.Shared.Coordinates; using Content.Shared.Procedural; +using System.Linq; +using System.Threading; +using Content.Shared.Salvage; +using Content.Shared.Salvage.Expeditions; using Robust.Shared.GameStates; using Robust.Shared.Random; @@ -33,6 +37,7 @@ public sealed partial class SalvageSystem private const double SalvageJobTime = 0.002; private float _cooldown; + private float _failedCooldown; private void InitializeExpeditions() { @@ -49,7 +54,9 @@ private void InitializeExpeditions() SubscribeLocalEvent(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) @@ -63,6 +70,7 @@ private void OnExpeditionGetState(EntityUid uid, SalvageExpeditionComponent comp private void ShutdownExpeditions() { _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionCooldown, SetCooldownChange); + _configurationManager.UnsubValueChanged(CCVars.SalvageExpeditionFailedCooldown, SetFailedCooldownChange); } private void SetCooldownChange(float obj) @@ -80,6 +88,20 @@ private void SetCooldownChange(float obj) _cooldown = obj; } + private void SetFailedCooldownChange(float obj) + { + var diff = obj - _failedCooldown; + + var query = AllEntityQuery(); + + 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(); @@ -99,7 +121,7 @@ private void OnExpeditionShutdown(EntityUid uid, SalvageExpeditionComponent comp // Finish mission if (TryComp(component.Station, out var data)) { - FinishExpedition((component.Station, data), uid); + FinishExpedition(data, uid, component, null); } } @@ -138,34 +160,116 @@ private void UpdateExpeditions() comp.Cooldown = false; comp.NextOffer += TimeSpan.FromSeconds(_cooldown); GenerateMissions(comp); - UpdateConsoles((uid, comp)); + UpdateConsoles(comp); } } - private void FinishExpedition(Entity expedition, EntityUid uid) + private void FinishExpedition(SalvageExpeditionDataComponent component, EntityUid uid, SalvageExpeditionComponent expedition, EntityUid? shuttle) { - var component = expedition.Comp; component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown); Announce(uid, Loc.GetString("salvage-expedition-mission-completed")); + // Finish mission cleanup. + switch (expedition.MissionParams.MissionType) + { + // Handles the mining taxation. + case SalvageMissionType.Mining: + expedition.Completed = true; + + if (shuttle != null && TryComp(uid, out var mining)) + { + var xformQuery = GetEntityQuery(); + var entities = new List(); + MiningTax(entities, shuttle.Value, mining, xformQuery); + + var tax = GetMiningTax(expedition.MissionParams.Difficulty); + _random.Shuffle(entities); + + // TODO: urgh this pr is already taking so long I'll do this later + for (var i = 0; i < Math.Ceiling(entities.Count * tax); i++) + { + // QueueDel(entities[i]); + } + } + + break; + } + + // Handle payout after expedition has finished + if (expedition.Completed) + { + Log.Debug($"Completed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}"); + component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_cooldown); + Announce(uid, Loc.GetString("salvage-expedition-mission-completed")); + GiveRewards(expedition); + } + else + { + Log.Debug($"Failed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}"); + component.NextOffer = _timing.CurTime + TimeSpan.FromSeconds(_failedCooldown); + Announce(uid, Loc.GetString("salvage-expedition-mission-failed")); + } + + component.ActiveMission = 0; component.Cooldown = true; - UpdateConsoles(expedition); + UpdateConsoles(component); + } + + /// + /// Deducts ore tax for mining. + /// + private void MiningTax(List entities, EntityUid entity, SalvageMiningExpeditionComponent mining, EntityQuery 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().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(); + _random.Shuffle(allDifficulties); + var difficulties = allDifficulties.Take(MissionLimit).ToList(); + difficulties.Sort(); + + if (configs.Count == 0) + return; for (var i = 0; i < MissionLimit; i++) { - var mission = new SalvageMissionParams - { - Index = component.NextIndex, - Seed = _random.Next(), - Difficulty = "Moderate", - }; + _random.Shuffle(configs); + var rating = difficulties[i]; - component.Missions[component.NextIndex++] = mission; + foreach (var config in configs) + { + var mission = new SalvageMissionParams + { + Index = component.NextIndex, + MissionType = config, + Seed = _random.Next(), + Difficulty = rating, + }; + + component.Missions[component.NextIndex++] = mission; + break; + } } } @@ -182,7 +286,6 @@ private void SpawnMission(SalvageMissionParams missionParams, EntityUid station) SalvageJobTime, EntityManager, _timing, - _logManager, _mapManager, _prototypeManager, _anchorable, @@ -191,6 +294,7 @@ private void SpawnMission(SalvageMissionParams missionParams, EntityUid station) _shuttle, _stationSystem, _metaData, + this, station, missionParams, cancelToken.Token); @@ -203,4 +307,25 @@ private void OnStructureExamine(EntityUid uid, SalvageStructureComponent compone { args.PushMarkup(Loc.GetString("salvage-expedition-structure-examine")); } + + private void GiveRewards(SalvageExpeditionComponent comp) + { + var palletList = new List(); + var pallets = EntityQueryEnumerator(); + while (pallets.MoveNext(out var pallet, out var palletComp)) + { + if (_stationSystem.GetOwningStation(pallet) == comp.Station) + { + palletList.Add(pallet); + } + } + + if (!(palletList.Count > 0)) + return; + + foreach (var reward in comp.Rewards) + { + Spawn(reward, (Transform(_random.Pick(palletList)).MapPosition)); + } + } } diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs index 0863362131c..ce65ead9283 100644 --- a/Content.Server/Salvage/SalvageSystem.Runner.cs +++ b/Content.Server/Salvage/SalvageSystem.Runner.cs @@ -43,7 +43,7 @@ private void OnConsoleFTLAttempt(ref ConsoleFTLAttemptEvent ev) // TODO: This is terrible but need bluespace harnesses or something. var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var mobState, out var mobXform)) + while (query.MoveNext(out var uid, out var _, out var mobState, out var mobXform)) { if (mobXform.MapUid != xform.MapUid) continue; @@ -109,11 +109,22 @@ private void OnFTLCompleted(ref FTLCompletedEvent args) Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir()))); component.Stage = ExpeditionStage.Running; - Dirty(args.MapUid, component); + Dirty(component); } private void OnFTLStarted(ref FTLStartedEvent ev) { + // Started a mining mission so work out exempt entities + if (TryComp( + _mapManager.GetMapEntityId(ev.TargetCoordinates.ToMap(EntityManager, _transform).MapId), + out var mining)) + { + var ents = new List(); + var xformQuery = GetEntityQuery(); + MiningTax(ents, ev.Entity, mining, xformQuery); + mining.ExemptEntities = ents; + } + if (!TryComp(ev.FromMapUid, out var expedition) || !TryComp(expedition.Station, out var station)) { @@ -158,7 +169,7 @@ private void UpdateRunner() 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(4)) + else if (comp.Stage < ExpeditionStage.Countdown && remaining < TimeSpan.FromMinutes(5)) { comp.Stage = ExpeditionStage.Countdown; Dirty(uid, comp); @@ -199,5 +210,72 @@ private void UpdateRunner() QueueDel(uid); } } + + // Mining missions: NOOP since it's handled after ftling + + // Structure missions + var structureQuery = EntityQueryEnumerator(); + + 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(); + 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")); + } + } } } diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index 0da62072898..1065f7426e6 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -37,12 +37,12 @@ public sealed partial class SalvageSystem : SharedSalvageSystem [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!; diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index d80de8b6b62..93c44e2eb37 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Linq; using System.Numerics; using System.Threading; @@ -24,7 +23,6 @@ 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; @@ -35,7 +33,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.Salvage; @@ -51,17 +48,15 @@ public sealed class SpawnSalvageMissionJob : Job private readonly MetaDataSystem _metaData; private readonly ShuttleSystem _shuttle; private readonly StationSystem _stationSystem; + 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, @@ -70,6 +65,7 @@ public SpawnSalvageMissionJob( ShuttleSystem shuttle, StationSystem stationSystem, MetaDataSystem metaData, + SalvageSystem salvage, EntityUid station, SalvageMissionParams missionParams, CancellationToken cancellation = default) : base(maxTime, cancellation) @@ -84,17 +80,15 @@ public SpawnSalvageMissionJob( _shuttle = shuttle; _stationSystem = stationSystem; _metaData = metaData; + _salvage = salvage; Station = station; _missionParams = missionParams; - _sawmill = logManager.GetSawmill("salvage_job"); -#if !DEBUG - _sawmill.Level = LogLevel.Info; -#endif } protected override async Task Process() { - _sawmill.Debug("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}"); + Logger.DebugS("salvage", $"Spawning salvage mission with seed {_missionParams.Seed}"); + var config = _missionParams.MissionType; var mapId = _mapManager.CreateMap(); var mapUid = _mapManager.GetMapEntityId(mapId); _mapManager.AddUninitializedMap(mapId); @@ -104,17 +98,16 @@ protected override async Task Process() // 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(difficultyId); var mission = _entManager.System() - .GetMission(difficultyProto, _missionParams.Seed); + .GetMission(_missionParams.MissionType, _missionParams.Difficulty, _missionParams.Seed); - var missionBiome = _prototypeManager.Index(mission.Biome); + var missionBiome = _prototypeManager.Index(mission.Biome); + BiomeComponent? biome = null; if (missionBiome.BiomePrototype != null) { - var biome = _entManager.AddComponent(mapUid); + biome = _entManager.AddComponent(mapUid); var biomeSystem = _entManager.System(); biomeSystem.SetTemplate(biome, _prototypeManager.Index(missionBiome.BiomePrototype)); biomeSystem.SetSeed(biome, mission.Seed); @@ -142,7 +135,7 @@ protected override async Task Process() { var lighting = _entManager.EnsureComponent(mapUid); lighting.AmbientLightColor = mission.Color.Value; - _entManager.Dirty(mapUid, lighting); + _entManager.Dirty(lighting); } } @@ -154,6 +147,8 @@ protected override async Task Process() expedition.Station = Station; expedition.EndTime = _timing.CurTime + mission.Duration; expedition.MissionParams = _missionParams; + expedition.Difficulty = _missionParams.Difficulty; + expedition.Rewards = mission.Rewards; // On Frontier, we cant share our locations it breaks ftl in a bad bad way // Don't want consoles to have the incorrect name until refreshed. @@ -176,22 +171,28 @@ protected override async Task Process() // We'll use the dungeon rotation as the spawn angle var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed); - 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(mission.Dungeon); - var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto); - var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset, - _missionParams.Seed)); - - // Aborty - if (dungeon.Rooms.Count == 0) + Dungeon dungeon = default!; + + if (config != SalvageMissionType.Mining) { - return false; - } + 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(mission.Dungeon); + var dungeonConfig = _prototypeManager.Index(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 reservedTiles = new(); @@ -203,147 +204,206 @@ protected override async Task Process() reservedTiles.Add(tile.GridIndices); } - var budgetEntries = new List(); - - /* - * GUARANTEED LOOT - */ + // 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(); + } + // Handle loot // We'll always add this loot if possible - // mainly used for ore layers. foreach (var lootProto in _prototypeManager.EnumeratePrototypes()) + { if (!lootProto.Guaranteed) continue; - await SpawnDungeonLoot(dungeon, missionBiome, lootProto, mapUid, grid, random, reservedTiles); } + return true; + } - // Handle boss loot (when relevant). - - // Handle mob loot. - - // Handle remaining loot - - /* - * MOB SPAWNS - */ - - var mobBudget = difficultyProto.MobBudget; - var faction = _prototypeManager.Index(mission.Faction); - var randomSystem = _entManager.System(); - - foreach (var entry in faction.MobGroups) - { - budgetEntries.Add(entry); - } - - var probSum = budgetEntries.Sum(x => x.Prob); - - while (mobBudget > 0f) + private async Task SpawnDungeonLoot(Dungeon? dungeon, SalvageBiomeMod biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List reservedTiles) + { + for (var i = 0; i < loot.LootRules.Count; i++) { - var entry = randomSystem.GetBudgetEntry(ref mobBudget, ref probSum, budgetEntries, random); - if (entry == null) - break; - - await SpawnRandomEntry(grid, entry, dungeon, random); - } - - var allLoot = _prototypeManager.Index(SharedSalvageSystem.ExpeditionsLootProto); - var lootBudget = difficultyProto.LootBudget; + var rule = loot.LootRules[i]; - foreach (var rule in allLoot.LootRules) - { switch (rule) { - case RandomSpawnsLoot randomLoot: - budgetEntries.Clear(); - - foreach (var entry in randomLoot.Entries) + case BiomeMarkerLoot biomeLoot: { - budgetEntries.Add(entry); + if (_entManager.TryGetComponent(gridUid, out var biome) && + biomeLoot.Prototype.TryGetValue(biomeMod.ID, out var mod)) + { + _biome.AddMarkerLayer(biome, mod); + } } - - probSum = budgetEntries.Sum(x => x.Prob); - - while (lootBudget > 0f) + break; + case BiomeTemplateLoot biomeLoot: { - 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); + if (_entManager.TryGetComponent(gridUid, out var biome)) + { + _biome.AddTemplate(biome, "Loot", _prototypeManager.Index(biomeLoot.Prototype), i); + } } break; - default: - throw new NotImplementedException(); } } + } - return true; + #region Mission Specific + + private async Task SetupMining( + SalvageMission mission, + EntityUid gridUid) + { + var faction = _prototypeManager.Index(mission.Faction); + + if (_entManager.TryGetComponent(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 SpawnRandomEntry(MapGridComponent grid, IBudgetEntry entry, Dungeon dungeon, Random random) + private async Task SetupStructure( + SalvageMission mission, + Dungeon dungeon, + EntityUid gridUid, + MapGridComponent grid, + Random random) { - await SuspendIfOutOfTime(); + var structureComp = _entManager.EnsureComponent(gridUid); + var availableRooms = dungeon.Rooms.ToList(); + var faction = _prototypeManager.Index(mission.Faction); + await SpawnMobsRandomRooms(mission, dungeon, faction, grid, random); - var availableRooms = new ValueList(dungeon.Rooms); - var availableTiles = new List(); + var structureCount = _salvage.GetStructureCount(mission.Difficulty); + var shaggy = faction.Configs["DefenseStructure"]; + var validSpawns = new List(); - while (availableRooms.Count > 0) + // Spawn the objectives + for (var i = 0; i < structureCount; i++) { - availableTiles.Clear(); - var roomIndex = random.Next(availableRooms.Count); - var room = availableRooms.RemoveSwap(roomIndex); - availableTiles.AddRange(room.Tiles); + var structureRoom = availableRooms[random.Next(availableRooms.Count)]; + validSpawns.Clear(); + validSpawns.AddRange(structureRoom.Tiles); + random.Shuffle(validSpawns); - while (availableTiles.Count > 0) + while (validSpawns.Count > 0) { - var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count)); + var spawnTile = validSpawns[^1]; + validSpawns.RemoveAt(validSpawns.Count - 1); - if (!_anchorable.TileFree(grid, tile, (int) CollisionGroup.MachineLayer, + if (!_anchorable.TileFree(grid, spawnTile, (int) CollisionGroup.MachineLayer, (int) CollisionGroup.MachineLayer)) { continue; } - var uid = _entManager.SpawnAtPosition(entry.Proto, grid.GridTileToLocal(tile)); - _entManager.RemoveComponent(uid); - _entManager.RemoveComponent(uid); - return; + var spawnPosition = grid.GridTileToLocal(spawnTile); + var uid = _entManager.SpawnEntity(shaggy, spawnPosition); + _entManager.AddComponent(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); - // oh noooooooooooo + var faction = _prototypeManager.Index(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(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 SpawnDungeonLoot(Dungeon dungeon, SalvageBiomeModPrototype biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List reservedTiles) + private async Task SpawnMobsRandomRooms(SalvageMission mission, Dungeon dungeon, SalvageFactionPrototype faction, MapGridComponent grid, Random random, float scale = 1f) { - for (var i = 0; i < loot.LootRules.Count; i++) + // 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(); + + for (var i = 0; i < groupSpawns; i++) { - var rule = loot.LootRules[i]; + var roll = random.NextFloat() * groupSum; + var value = 0f; - switch (rule) + foreach (var group in faction.MobGroups) { - case BiomeMarkerLoot biomeLoot: - { - if (_entManager.TryGetComponent(gridUid, out var biome) && - biomeLoot.Prototype.TryGetValue(biomeMod.ID, out var mod)) - { - _biome.AddMarkerLayer(biome, mod); - } - } - break; - case BiomeTemplateLoot biomeLoot: + 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) { - if (_entManager.TryGetComponent(gridUid, out var biome)) + var spawnTile = validSpawns[^1]; + validSpawns.RemoveAt(validSpawns.Count - 1); + + if (!_anchorable.TileFree(grid, spawnTile, (int) CollisionGroup.MachineLayer, + (int) CollisionGroup.MachineLayer)) { - _biome.AddTemplate(biome, "Loot", _prototypeManager.Index(biomeLoot.Prototype), i); + continue; } + + var spawnPosition = grid.GridTileToLocal(spawnTile); + + var uid = _entManager.CreateEntityUninitialized(entry, spawnPosition); + _entManager.RemoveComponent(uid); + _entManager.RemoveComponent(uid); + _entManager.InitializeAndStartEntity(uid); + + break; } - break; + } + + await SuspendIfOutOfTime(); + break; } } } + + #endregion } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 8050ffc8092..a91cba55bbd 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1565,16 +1565,13 @@ public static readonly CVarDef SalvageForced = CVarDef.Create("salvage.forced", "", CVar.SERVERONLY); /// - /// Duration for missions + /// Cooldown for successful missions. /// public static readonly CVarDef - SalvageExpeditionDuration = CVarDef.Create("salvage.expedition_duration", 900f, CVar.REPLICATED); + SalvageExpeditionCooldown = CVarDef.Create("salvage.expedition_cooldown", 300f, CVar.REPLICATED); - /// - /// Cooldown for missions. - /// public static readonly CVarDef - SalvageExpeditionCooldown = CVarDef.Create("salvage.expedition_cooldown", 360f, CVar.REPLICATED); + SalvageExpeditionFailedCooldown = CVarDef.Create("salvage.expedition_failed_cooldown", 600f, CVar.REPLICATED); /* * Flavor diff --git a/Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs b/Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs deleted file mode 100644 index a9b71e7b619..00000000000 --- a/Content.Shared/Procedural/Loot/RandomSpawnsLoot.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.Random; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.Loot; - -/// -/// Randomly places loot in free areas inside the dungeon. -/// -public sealed partial class RandomSpawnsLoot : IDungeonLoot -{ - [ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)] - public List Entries = new(); -} - -[DataDefinition] -public partial record struct RandomSpawnLootEntry : IBudgetEntry -{ - [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Proto { get; set; } = string.Empty; - - /// - /// Cost for this loot to spawn. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("cost")] - public float Cost { get; set; } = 1f; - - /// - /// Unit probability for this entry. Weighted against the entire table. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("prob")] - public float Prob { get; set; } = 1f; -} diff --git a/Content.Shared/Procedural/Loot/SalvageLootPrototype.cs b/Content.Shared/Procedural/Loot/SalvageLootPrototype.cs index ba684dfe435..f5f8ab3fb2c 100644 --- a/Content.Shared/Procedural/Loot/SalvageLootPrototype.cs +++ b/Content.Shared/Procedural/Loot/SalvageLootPrototype.cs @@ -1,4 +1,6 @@ +using Content.Shared.Salvage; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Procedural.Loot; @@ -15,6 +17,14 @@ public sealed class SalvageLootPrototype : IPrototype /// [DataField("guaranteed")] public bool Guaranteed; + [DataField("desc")] public string Description = string.Empty; + + /// + /// Mission types this loot is not allowed to spawn for + /// + [DataField("blacklist")] + public List Blacklist = new(); + /// /// All of the loot rules /// diff --git a/Content.Shared/Procedural/SalvageDifficultyPrototype.cs b/Content.Shared/Procedural/SalvageDifficultyPrototype.cs deleted file mode 100644 index 335bebde3f8..00000000000 --- a/Content.Shared/Procedural/SalvageDifficultyPrototype.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Procedural; - -[Prototype("salvageDifficulty")] -public sealed class SalvageDifficultyPrototype : IPrototype -{ - [IdDataField] public string ID { get; } = string.Empty; - - /// - /// Color to be used in UI. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("color")] - public Color Color = Color.White; - - /// - /// How much loot this difficulty is allowed to spawn. - /// - [DataField("lootBudget", required : true)] - public float LootBudget; - - /// - /// How many mobs this difficulty is allowed to spawn. - /// - [DataField("mobBudget", required : true)] - public float MobBudget; - - /// - /// Budget allowed for mission modifiers like no light, etc. - /// - [DataField("modifierBudget")] - public float ModifierBudget; - - [DataField("recommendedPlayers", required: true)] - public int RecommendedPlayers; -} diff --git a/Content.Shared/Random/IBudgetEntry.cs b/Content.Shared/Random/IBudgetEntry.cs deleted file mode 100644 index 22dc69ed008..00000000000 --- a/Content.Shared/Random/IBudgetEntry.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Shared.Random; - -/// -/// Budgeted random spawn entry. -/// -public interface IBudgetEntry : IProbEntry -{ - float Cost { get; set; } - - string Proto { get; set; } -} - -/// -/// Random entry that has a prob. See -/// -public interface IProbEntry -{ - float Prob { get; set; } -} - diff --git a/Content.Shared/Random/RandomSystem.cs b/Content.Shared/Random/RandomSystem.cs deleted file mode 100644 index 78297e1da56..00000000000 --- a/Content.Shared/Random/RandomSystem.cs +++ /dev/null @@ -1,58 +0,0 @@ -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 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; - } - - /// - /// Gets a random entry based on each entry having a different probability. - /// - public IProbEntry GetProbEntry(IEnumerable 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(); - } -} diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageAirMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageAirMod.cs index 717c21947bc..aee79fdf027 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageAirMod.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageAirMod.cs @@ -24,7 +24,7 @@ public sealed class SalvageAirMod : IPrototype, IBiomeSpecificMod public float Cost { get; private set; } = 0f; /// - [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List? Biomes { get; private set; } = null; /// diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeMod.cs similarity index 92% rename from Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs rename to Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeMod.cs index fe806f2cd3c..fc6ce7e9de9 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeMod.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Salvage.Expeditions.Modifiers; /// Affects the biome to be used for salvage. /// [Prototype("salvageBiomeMod")] -public sealed class SalvageBiomeModPrototype : IPrototype, ISalvageMod +public sealed class SalvageBiomeMod : IPrototype, ISalvageMod { [IdDataField] public string ID { get; } = default!; diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonModPrototype.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonMod.cs similarity index 88% rename from Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonModPrototype.cs rename to Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonMod.cs index f86f7cfd3b6..335a127081d 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonModPrototype.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageDungeonMod.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Salvage.Expeditions.Modifiers; [Prototype("salvageDungeonMod")] -public sealed class SalvageDungeonModPrototype : IPrototype, IBiomeSpecificMod +public sealed class SalvageDungeonMod : IPrototype, IBiomeSpecificMod { [IdDataField] public string ID { get; } = default!; @@ -17,7 +17,7 @@ public sealed class SalvageDungeonModPrototype : IPrototype, IBiomeSpecificMod public float Cost { get; private set; } = 0f; /// - [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List? Biomes { get; private set; } = null; /// diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageLightMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageLightMod.cs index cfdc6a2b762..e738c98a3a7 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageLightMod.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageLightMod.cs @@ -15,7 +15,7 @@ public sealed class SalvageLightMod : IPrototype, IBiomeSpecificMod public float Cost { get; private set; } = 0f; /// - [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List? Biomes { get; private set; } = null; [DataField("color", required: true)] public Color? Color; diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTemperatureMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTemperatureMod.cs index bc3d5eb8512..c52b2010e4e 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTemperatureMod.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTemperatureMod.cs @@ -16,7 +16,7 @@ public sealed class SalvageTemperatureMod : IPrototype, IBiomeSpecificMod public float Cost { get; private set; } = 0f; /// - [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List? Biomes { get; private set; } = null; /// diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs new file mode 100644 index 00000000000..6cc3507538f --- /dev/null +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageTimeMod.cs @@ -0,0 +1,23 @@ +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; + + /// + /// Cost for difficulty modifiers. + /// + [DataField("cost")] + public float Cost { get; private set; } + + [DataField("minDuration")] + public int MinDuration = 630; + + [DataField("maxDuration")] + public int MaxDuration = 570; +} diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageWeatherMod.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageWeatherMod.cs index 89fc84c4168..caa89afeb4a 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageWeatherMod.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageWeatherMod.cs @@ -17,7 +17,7 @@ public sealed class SalvageWeatherMod : IPrototype, IBiomeSpecificMod public float Cost { get; private set; } = 0f; /// - [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List? Biomes { get; private set; } = null; /// diff --git a/Content.Shared/Salvage/Expeditions/SalvageExpeditions.cs b/Content.Shared/Salvage/Expeditions/SalvageExpeditions.cs index 6ad37ad56e5..fa78100cd16 100644 --- a/Content.Shared/Salvage/Expeditions/SalvageExpeditions.cs +++ b/Content.Shared/Salvage/Expeditions/SalvageExpeditions.cs @@ -75,14 +75,28 @@ public sealed partial class SalvageExpeditionDataComponent : Component } [Serializable, NetSerializable] -public sealed record SalvageMissionParams +public sealed record SalvageMissionParams : IComparable { [ViewVariables] public ushort Index; + [ViewVariables(VVAccess.ReadWrite)] + public SalvageMissionType MissionType; + [ViewVariables(VVAccess.ReadWrite)] public int Seed; - public string Difficulty = string.Empty; + /// + /// Base difficulty for this mission. + /// + [ViewVariables(VVAccess.ReadWrite)] public DifficultyRating Difficulty; + + public int CompareTo(SalvageMissionParams? other) + { + if (other == null) + return -1; + + return Difficulty.CompareTo(other.Difficulty); + } } /// @@ -91,13 +105,16 @@ public sealed record SalvageMissionParams /// 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 Rewards, List Modifiers) { /// @@ -106,7 +123,12 @@ public sealed record SalvageMission( public readonly int Seed = Seed; /// - /// to be used. + /// Difficulty rating. + /// + public DifficultyRating Difficulty = Difficulty; + + /// + /// to be used. /// public readonly string Dungeon = Dungeon; @@ -115,6 +137,11 @@ public sealed record SalvageMission( /// public readonly string Faction = Faction; + /// + /// Underlying mission params that generated this. + /// + public readonly SalvageMissionType Mission = Mission; + /// /// Biome to be used for the mission. /// @@ -140,6 +167,11 @@ public sealed record SalvageMission( /// public TimeSpan Duration = Duration; + /// + /// The list of items to order on mission completion. + /// + public List Rewards = Rewards; + /// /// Modifiers (outside of the above) applied to the mission. /// diff --git a/Content.Shared/Salvage/Expeditions/SalvageFactionPrototype.cs b/Content.Shared/Salvage/Expeditions/SalvageFactionPrototype.cs index 9de6d5221b9..cead0624319 100644 --- a/Content.Shared/Salvage/Expeditions/SalvageFactionPrototype.cs +++ b/Content.Shared/Salvage/Expeditions/SalvageFactionPrototype.cs @@ -5,14 +5,20 @@ namespace Content.Shared.Salvage.Expeditions; [Prototype("salvageFaction")] -public sealed class SalvageFactionPrototype : IPrototype +public sealed class SalvageFactionPrototype : IPrototype, ISalvageMod { [IdDataField] public string ID { get; } = default!; [DataField("desc")] public string Description { get; private set; } = string.Empty; - [ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)] - public List MobGroups = new(); + /// + /// Cost for difficulty modifiers. + /// + [DataField("cost")] + public float Cost { get; private set; } = 0f; + + [ViewVariables(VVAccess.ReadWrite), DataField("groups", required: true)] + public List MobGroups = default!; /// /// Miscellaneous data for factions. diff --git a/Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs b/Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs deleted file mode 100644 index fcfd865d295..00000000000 --- a/Content.Shared/Salvage/Expeditions/SalvageMobEntry.cs +++ /dev/null @@ -1,24 +0,0 @@ -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 -{ - /// - /// Cost for this mob in a budget. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("cost")] - public float Cost { get; set; } = 1f; - - /// - /// Probability to spawn this mob. Summed with everything else for the faction. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("prob")] - public float Prob { get; set; } = 1f; - - [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Proto { get; set; } = string.Empty; -} diff --git a/Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs b/Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs new file mode 100644 index 00000000000..df1917acdcb --- /dev/null +++ b/Content.Shared/Salvage/Expeditions/SalvageMobGroup.cs @@ -0,0 +1,18 @@ +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. + + /// + /// Probability to spawn this group. Summed with everything else for the faction. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("prob")] + public float Prob = 1f; + + [ViewVariables(VVAccess.ReadWrite), DataField("entries", required: true)] + public List Entries = new(); +} diff --git a/Content.Shared/Salvage/SharedSalvageSystem.cs b/Content.Shared/Salvage/SharedSalvageSystem.cs index e5124ab4d03..bc498d6a04a 100644 --- a/Content.Shared/Salvage/SharedSalvageSystem.cs +++ b/Content.Shared/Salvage/SharedSalvageSystem.cs @@ -1,13 +1,9 @@ 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; @@ -17,14 +13,73 @@ namespace Content.Shared.Salvage; public abstract class SharedSalvageSystem : EntitySystem { - [Dependency] protected readonly IConfigurationManager CfgManager = default!; + [Dependency] private readonly ILocalizationManager _loc = 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(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; + } + + /// + /// Gets the amount of structures to destroy. + /// + 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); + } + } + /// - /// Main loot table for salvage expeditions. + /// How many groups of mobs to spawn for a mission. /// - [ValidatePrototypeId] - public const string ExpeditionsLootProto = "SalvageLoot"; + public float GetSpawnCount(DifficultyRating difficulty) + { + return (int) difficulty * 2; + } public static string GetFTLName(DatasetPrototype dataset, int seed) { @@ -32,21 +87,22 @@ 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(SalvageDifficultyPrototype difficulty, int seed) + public SalvageMission GetMission(SalvageMissionType config, DifficultyRating difficulty, int seed) { // This is on shared to ensure the client display for missions and what the server generates are consistent - var modifierBudget = difficulty.ModifierBudget; + var rating = (float) GetDifficulty(difficulty); + // Don't want easy missions to have any negative modifiers but also want + // easy to be a 1 for difficulty. + rating -= 1f; var rand = new System.Random(seed); // Run budget in order of priority // - Biome // - Lighting // - Atmos - var biome = GetMod(rand, ref modifierBudget); - var light = GetBiomeMod(biome.ID, rand, ref modifierBudget); - var temp = GetBiomeMod(biome.ID, rand, ref modifierBudget); - var air = GetBiomeMod(biome.ID, rand, ref modifierBudget); - var dungeon = GetBiomeMod(biome.ID, rand, ref modifierBudget); + var biome = GetMod(rand, ref rating); + var air = GetBiomeMod(biome.ID, rand, ref rating); + var dungeon = GetBiomeMod(biome.ID, rand, ref rating); var factionProtos = _proto.EnumeratePrototypes().ToList(); factionProtos.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal)); var faction = factionProtos[rand.Next(factionProtos.Count)]; @@ -59,19 +115,31 @@ public SalvageMission GetMission(SalvageDifficultyPrototype difficulty, int seed } // only show the description if there is an atmosphere since wont matter otherwise + var temp = GetBiomeMod(biome.ID, rand, ref rating); if (temp.Description != string.Empty && !air.Space) { mods.Add(temp.Description); } + var light = GetBiomeMod(biome.ID, rand, ref rating); if (light.Description != string.Empty) { mods.Add(light.Description); } - var duration = TimeSpan.FromSeconds(CfgManager.GetCVar(CCVars.SalvageExpeditionDuration)); + var time = GetMod(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); + } - return new SalvageMission(seed, dungeon.ID, faction.ID, biome.ID, air.ID, temp.Temperature, light.Color, duration, mods); + 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(string biome, System.Random rand, ref float rating) where T : class, IPrototype, IBiomeSpecificMod @@ -111,5 +179,72 @@ public T GetMod(System.Random rand, ref float rating) where T : class, IProto throw new InvalidOperationException(); } + + private List GetRewards(DifficultyRating difficulty, System.Random rand) + { + var rewards = new List(3); + var ids = RewardsForDifficulty(difficulty); + foreach (var id in ids) + { + // pick a random reward to give + var weights = _proto.Index(id); + rewards.Add(weights.Pick(rand)); + } + + return rewards; + } + + /// + /// Get a list of WeightedRandomEntityPrototype IDs with the rewards for a certain difficulty. + /// + 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 +{ + /// + /// No dungeon, just ore loot and random mob spawns. + /// + Mining, + + /// + /// Destroy the specified structures in a dungeon. + /// + Destruction, + + /// + /// Kill a large creature in a dungeon. + /// + Elimination, +} + +[Serializable, NetSerializable] +public enum DifficultyRating : byte +{ + Minimal, + Minor, + Moderate, + Hazardous, + Extreme, +} diff --git a/Resources/ConfigPresets/Build/development.toml b/Resources/ConfigPresets/Build/development.toml index 26098d955e3..26699f9ddad 100644 --- a/Resources/ConfigPresets/Build/development.toml +++ b/Resources/ConfigPresets/Build/development.toml @@ -13,6 +13,7 @@ preload = false [salvage] expedition_cooldown = 30.0 +expedition_failed_cooldown = 30.0 [shuttle] grid_fill = false diff --git a/Resources/Locale/en-US/procedural/expeditions.ftl b/Resources/Locale/en-US/procedural/expeditions.ftl index f309da1bda3..a0192fabd32 100644 --- a/Resources/Locale/en-US/procedural/expeditions.ftl +++ b/Resources/Locale/en-US/procedural/expeditions.ftl @@ -4,7 +4,8 @@ salvage-expedition-structure-remaining = {$count -> *[other] {$count} structures remaining. } -salvage-expedition-type = Mission +salvage-expedition-megafauna-remaining = {$count} megafauna remaining. + salvage-expedition-window-title = Salvage expeditions salvage-expedition-window-difficulty = Difficulty: salvage-expedition-window-details = Details: @@ -12,17 +13,31 @@ salvage-expedition-window-hostiles = Hostiles: salvage-expedition-window-duration = Duration: salvage-expedition-window-biome = Biome: salvage-expedition-window-modifiers = Modifiers: +salvage-expedition-window-rewards = Rewards: salvage-expedition-window-claimed = Claimed salvage-expedition-window-claim = Claim salvage-expedition-window-next = Next offer +# Expedition descriptions +salvage-expedition-desc-mining = Collect resources inside the area. +# You will be taxed {$tax}% of the resources collected. +salvage-expedition-desc-structure = {$count -> + [one] Destroy {$count} {$structure} inside the area. + *[other] Destroy {$count} {$structure}s inside the area. +} +salvage-expedition-desc-elimination = Kill a large and dangerous creature inside the area. + +salvage-expedition-type-Mining = Mining +salvage-expedition-type-Destruction = Destruction +salvage-expedition-type-Elimination = Elimination + +salvage-expedition-difficulty-Minimal = Minimal +salvage-expedition-difficulty-Minor = Minor salvage-expedition-difficulty-Moderate = Moderate salvage-expedition-difficulty-Hazardous = Hazardous salvage-expedition-difficulty-Extreme = Extreme -salvage-expedition-difficulty-players = Recommended salvagers: - # Runner salvage-expedition-not-all-present = Not all salvagers are aboard the shuttle! diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 7de80f1d40a..3057708852d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -189,14 +189,7 @@ parent: MobCarp suffix: Dungeon components: - - type: MobThresholds - thresholds: - 0: Alive - 50: Dead - - type: SlowOnDamage - speedModifierThresholds: - 25: 0.7 - type: MeleeWeapon damage: types: - Slash: 6 + Slash: 5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index dfa13326213..8bf82afd674 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -66,9 +66,6 @@ thresholds: 0: Alive 50: Dead - - type: SlowOnDamage - speedModifierThresholds: - 25: 0.5 - type: Stamina critThreshold: 200 - type: Bloodstream @@ -137,7 +134,7 @@ - type: MobThresholds thresholds: 0: Alive - 100: Dead + 75: Dead - type: Stamina critThreshold: 300 - type: SlowOnDamage @@ -172,16 +169,18 @@ - type: MobThresholds thresholds: 0: Alive - 80: Dead - - type: SlowOnDamage - speedModifierThresholds: - 40: 0.7 + 150: Dead - type: MeleeWeapon damage: groups: - Brute: 6 + Brute: 5 - type: MovementSpeedModifier - baseSprintSpeed: 4 + baseWalkSpeed: 2.0 + baseSprintSpeed: 2.5 + - type: SlowOnDamage + speedModifierThresholds: + 100: 0.4 + 50: 0.7 - type: Fixtures fixtures: fix1: @@ -212,14 +211,19 @@ thresholds: 0: Alive 300: Dead - - type: SlowOnDamage - speedModifierThresholds: - 150: 0.7 + - type: Stamina + critThreshold: 1500 - type: MovementSpeedModifier + baseWalkSpeed: 2.8 + baseSprintSpeed: 3.8 - type: MeleeWeapon damage: groups: - Brute: 12 + Brute: 20 + - type: SlowOnDamage + speedModifierThresholds: + 250: 0.4 + 200: 0.7 - type: Fixtures fixtures: fix1: @@ -250,16 +254,20 @@ - type: MobThresholds thresholds: 0: Alive - 100: Dead + 200: Dead + - type: Stamina + critThreshold: 550 - type: MovementSpeedModifier - baseSprintSpeed: 4 + baseWalkSpeed: 2.3 + baseSprintSpeed: 4.2 - type: MeleeWeapon damage: groups: Brute: 10 - type: SlowOnDamage speedModifierThresholds: - 50: 0.7 + 150: 0.5 + 100: 0.7 - type: Fixtures fixtures: fix1: @@ -286,12 +294,15 @@ layers: - map: ["enum.DamageStateVisualLayers.Base"] state: running + - type: Stamina + critThreshold: 250 - type: MovementSpeedModifier + baseWalkSpeed: 2.7 baseSprintSpeed: 6.0 - type: MeleeWeapon damage: groups: - Brute: 5 + Brute: 3 - type: Fixtures fixtures: fix1: @@ -338,13 +349,12 @@ - type: MobThresholds thresholds: 0: Alive - 50: Dead - - type: SlowOnDamage - speedModifierThresholds: - 25: 0.7 + 75: Dead - type: HTN rootTask: task: SimpleRangedHostileCompound + - type: Stamina + critThreshold: 300 - type: RechargeBasicEntityAmmo rechargeCooldown: 0.75 - type: BasicEntityAmmoProvider @@ -358,6 +368,9 @@ availableModes: - FullAuto soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg + - type: SlowOnDamage + speedModifierThresholds: + 50: 0.4 - type: Fixtures fixtures: fix1: diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index ccdd6cffe27..9ea54e9f33d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -156,7 +156,7 @@ - type: Butcherable spawned: - id: FoodMeatDragon - amount: 1 + amount: 3 - type: SalvageMobRestrictions # half damage, spread evenly - type: MeleeWeapon diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml index 5c2883abb10..438493bb513 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml @@ -14,16 +14,16 @@ - type: MeleeWeapon wideAnimationRotation: -135 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 @@ -54,9 +54,9 @@ - type: MeleeWeapon attackRate: 1.5 damage: - groups: - Brute: 10 types: + Blunt: 5 + Piercing: 5 Structural: 10 - type: StaticPrice price: 95.5 diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml index ff9e6189673..4576a7125c2 100644 --- a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml +++ b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml @@ -115,7 +115,6 @@ - type: entity id: Gyroscope - name: gyroscope parent: [ BaseThruster, ConstructibleMachine ] components: - type: Thruster diff --git a/Resources/Prototypes/Procedural/salvage_difficulties.yml b/Resources/Prototypes/Procedural/salvage_difficulties.yml deleted file mode 100644 index caef822329e..00000000000 --- a/Resources/Prototypes/Procedural/salvage_difficulties.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: salvageDifficulty - id: Moderate - lootBudget: 30 - mobBudget: 25 - modifierBudget: 2 - color: "#52B4E996" - recommendedPlayers: 2 - - #9FED5896 - #EFB34196 - #DE3A3A96 - #D381C996 diff --git a/Resources/Prototypes/Procedural/salvage_factions.yml b/Resources/Prototypes/Procedural/salvage_factions.yml index 0754e964c2b..e28c866128f 100644 --- a/Resources/Prototypes/Procedural/salvage_factions.yml +++ b/Resources/Prototypes/Procedural/salvage_factions.yml @@ -1,23 +1,38 @@ - type: salvageFaction id: Xenos - 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 + 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 configs: DefenseStructure: XenoWardingTower Mining: Xenos @@ -25,21 +40,27 @@ - type: salvageFaction id: Carps - 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: MobCarpRainbow # carp version of rouny... - cost: 3 - prob: 0.05 - - proto: MobDragonDungeon - cost: 10 - prob: 0.02 + groups: + - entries: + - id: MobCarpDungeon + amount: 1 + maxAmount: 4 + - entries: + - id: MobCarpMagic + amount: 1 + maxAmount: 3 + prob: 0.1 +# - entries: +# - id: MobCarpHolo +# amount: 1 +# maxAmount: 2 +# prob: 0.25' + - entries: + - id: MobCarpRainbow # carp version of rouny... + amount: 1 + maxAmount: 1 + prob: 0.05 configs: DefenseStructure: CarpStatue + Mining: Carps Megafauna: MobDragonDungeon diff --git a/Resources/Prototypes/Procedural/salvage_loot.yml b/Resources/Prototypes/Procedural/salvage_loot.yml index 10f6fdd016b..e5b9887f8bf 100644 --- a/Resources/Prototypes/Procedural/salvage_loot.yml +++ b/Resources/Prototypes/Procedural/salvage_loot.yml @@ -1,122 +1,4 @@ -# 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 +# Ores # - Low value # - type: salvageLoot # id: OreTin diff --git a/Resources/Prototypes/Procedural/salvage_mods.yml b/Resources/Prototypes/Procedural/salvage_mods.yml index 50df3ce238a..c7921e62eac 100644 --- a/Resources/Prototypes/Procedural/salvage_mods.yml +++ b/Resources/Prototypes/Procedural/salvage_mods.yml @@ -4,23 +4,23 @@ parent: FTLPoint # Biome mods -> at least 1 required -- type: salvageBiomeMod - id: Caves - biome: Caves - - type: salvageBiomeMod id: Grasslands biome: Grasslands +- type: salvageBiomeMod + id: Lava + cost: 2 + biome: Lava + - type: salvageBiomeMod id: Snow - cost: 1 biome: Snow - type: salvageBiomeMod - id: Lava - cost: 2 - biome: Lava + id: Caves + cost: 1 + biome: Caves #- type: salvageBiomeMod # id: Space @@ -28,20 +28,34 @@ # 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 @@ -51,120 +65,97 @@ - type: salvageLightMod id: Night desc: Night time - cost: 1 + cost: 2 color: null -# Temperatures -- type: salvageTemperatureMod - id: RoomTemp - cost: 0 +# Time mods -> at least 1 required +- type: salvageTimeMod + id: StandardTime -- type: salvageTemperatureMod - id: Hot +- type: salvageTimeMod + id: RushTime + desc: Rush + minDuration: 420 + maxDuration: 465 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 -- type: salvageTemperatureMod - id: Melting - desc: Extreme heat - cost: 4 - temperature: 1273.15 # 1000C hot hot hot - biomes: - - Lava +# Misc mods +- type: salvageMod + id: LongDistance + desc: Long distance -- type: salvageTemperatureMod - id: Cold - cost: 1 - temperature: 275.15 # 2C +# Dungeons +# For now just simple 1-dungeon setups +- type: salvageDungeonMod + id: Experiment + proto: Experiment biomes: - Caves #- LowDesert - - Grasslands - - Snow - -- type: salvageTemperatureMod - id: Tundra - desc: Low temperature - cost: 2 - temperature: 263.15 # -40C - biomes: - - Caves - Snow + - Grasslands -- type: salvageTemperatureMod - id: Frozen - desc: Extreme cold - cost: 4 - temperature: 123.15 # -150C +- type: salvageDungeonMod + id: LavaBrig + proto: LavaBrig biomes: - - Snow + - Lava # Air mixtures - type: salvageAirMod id: Space desc: No atmosphere space: true - cost: 2 + cost: 1 biomes: - - Caves - - Lava + - Caves + - Lava - type: salvageAirMod id: Breathable - cost: 0 gases: - - 21.824779 # oxygen - - 82.10312 # nitrogen + - 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 - - 8 # nitrous oxide + - 21.824779 # oxygen + - 72.10312 # nitrogen + - 0 + - 0 + - 0 + - 0 + - 0 + - 8 # 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 - - 8 # carbon dioxide + - 21.824779 # oxygen + - 77.10312 # nitrogen + - 8 # carbon dioxide biomes: - - Caves - #- LowDesert - - Snow - - Grasslands - - Lava + - Caves + #- LowDesert + - Snow + - Grasslands + - Lava - type: salvageAirMod id: Poison @@ -181,9 +172,9 @@ - 0 # nitrous oxide - 0 # frezon biomes: - - Caves - - Snow - - Lava + - Caves + - Snow + - Lava - type: salvageAirMod id: Plasma @@ -196,12 +187,12 @@ - 38.453562 # plasma - 0 # tritium - 0 # water vapor - - 21.824779 # miasma + - 0 # miasma - 0 # nitrous oxide - 0 # frezon biomes: - - Caves - - Lava + - Caves + - Lava - type: salvageAirMod id: Burnable @@ -218,32 +209,67 @@ - 0 # nitrous oxide - 0 # frezon biomes: - - Caves - - Lava + - Caves + - Lava -# Weather mods -> not required -#- type: salvageWeatherMod -# id: SnowfallHeavy -# weather: SnowfallHeavy -# cost: 1 -# -#- type: salvageWeatherMod -# id: Rain -# weather: Rain +# Temperatures -# Dungeons -# For now just simple 1-dungeon setups -- type: salvageDungeonMod - id: Experiment - proto: Experiment +- type: salvageTemperatureMod + id: RoomTemp biomes: - - Caves - #- LowDesert - - Snow - - Grasslands + - Caves + #- LowDesert + - Grasslands -- type: salvageDungeonMod - id: LavaBrig - proto: LavaBrig +- type: salvageTemperatureMod + id: Hot + temperature: 323.15 # 50C biomes: - - Lava + - 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