Salvage expeditions (#12745)
This commit is contained in:
@@ -128,7 +128,7 @@ namespace Content.Client.Lobby.UI
|
||||
_viewBox.AddChild(viewWest);
|
||||
_viewBox.AddChild(viewEast);
|
||||
_summaryLabel.Text = selectedCharacter.Summary;
|
||||
EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
|
||||
_entityManager.System<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
|
||||
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'Salvage expeditions'}"
|
||||
Title="{Loc 'salvage-expedition-window-title'}"
|
||||
MinSize="800 360">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NextOfferLabel"
|
||||
Text="Next offer:"
|
||||
Text="{Loc 'salvage-expedition-window-next'}"
|
||||
Margin="5"></Label>
|
||||
<ProgressBar Name="NextOfferBar"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
using Content.Shared.Procedural.Rewards;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Structure;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -31,6 +27,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
|
||||
public event Action<ushort>? ClaimMission;
|
||||
private bool _claimed;
|
||||
private bool _cooldown;
|
||||
private TimeSpan _nextOffer;
|
||||
|
||||
public SalvageExpeditionWindow()
|
||||
@@ -44,35 +41,17 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
public void UpdateState(SalvageExpeditionConsoleState state)
|
||||
{
|
||||
_claimed = state.Claimed;
|
||||
_cooldown = state.Cooldown;
|
||||
_nextOffer = state.NextOffer;
|
||||
Container.DisposeAllChildren();
|
||||
|
||||
for (var i = 0; i < state.Missions.Count; i++)
|
||||
{
|
||||
// TODO: Make this XAML
|
||||
var mission = state.Missions[i];
|
||||
var config = _prototype.Index<SalvageExpeditionPrototype>(mission.Config);
|
||||
var dungeonConfig = _prototype.Index<DungeonConfigPrototype>(config.DungeonConfigPrototype);
|
||||
var faction = SharedSalvageSystem.GetFaction(config.Factions, mission.Seed);
|
||||
var factionConfig = _prototype.Index<SalvageFactionPrototype>(faction);
|
||||
var missionParams = state.Missions[i];
|
||||
var config = missionParams.MissionType;
|
||||
var mission = _salvage.GetMission(missionParams.MissionType, missionParams.Difficulty, missionParams.Seed);
|
||||
|
||||
// If we ever need this on server then move it
|
||||
var missionDesc = string.Empty;
|
||||
var missionDetails = string.Empty;
|
||||
|
||||
switch (config.Mission)
|
||||
{
|
||||
case SalvageStructure structure:
|
||||
var structureConfig = (SalvageStructureFaction) factionConfig.Configs[mission.Config];
|
||||
missionDesc = "Demolition";
|
||||
// TODO:
|
||||
missionDetails = $"Destroy {SharedSalvageSystem.GetStructureCount(structure, mission.Seed)} {_prototype.Index<EntityPrototype>(structureConfig.Spawn).Name} structures.";
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Mission
|
||||
// Mission title
|
||||
var missionStripe = new StripeBack()
|
||||
{
|
||||
Margin = new Thickness(0f, -5f, 0f, 0f)
|
||||
@@ -80,7 +59,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
|
||||
missionStripe.AddChild(new Label()
|
||||
{
|
||||
Text = missionDesc,
|
||||
Text = Loc.GetString($"salvage-expedition-type-{config.ToString()}"),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(0f, 5f, 0f, 5f),
|
||||
});
|
||||
@@ -94,21 +73,27 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
// Details
|
||||
lBox.AddChild(new Label()
|
||||
{
|
||||
Text = $"Difficulty:"
|
||||
Text = Loc.GetString("salvage-expedition-window-difficulty")
|
||||
});
|
||||
|
||||
var difficultyColor = StyleNano.NanoGold;
|
||||
Color difficultyColor;
|
||||
|
||||
switch (config.DifficultyRating)
|
||||
switch (missionParams.Difficulty)
|
||||
{
|
||||
case DifficultyRating.None:
|
||||
difficultyColor = StyleNano.ButtonColorDefault;
|
||||
difficultyColor = Color.FromHex("#52B4E996");
|
||||
break;
|
||||
case DifficultyRating.Minor:
|
||||
difficultyColor = StyleNano.GoodGreenFore;
|
||||
difficultyColor = Color.FromHex("#9FED5896");
|
||||
break;
|
||||
case DifficultyRating.Moderate:
|
||||
difficultyColor = StyleNano.ConcerningOrangeFore;
|
||||
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();
|
||||
@@ -116,21 +101,23 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = config.DifficultyRating.ToString(),
|
||||
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 = $"Details:"
|
||||
Text = Loc.GetString("salvage-expedition-window-details")
|
||||
});
|
||||
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = missionDetails,
|
||||
Text = details,
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
Margin = new Thickness(0f, 0f, 0f, 5f),
|
||||
@@ -139,9 +126,11 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
// Details
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = $"Hostiles:"
|
||||
Text = Loc.GetString("salvage-expedition-window-hostiles")
|
||||
});
|
||||
|
||||
var faction = mission.Faction;
|
||||
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = faction,
|
||||
@@ -153,7 +142,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
// Duration
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = $"Duration:"
|
||||
Text = Loc.GetString("salvage-expedition-window-duration")
|
||||
});
|
||||
|
||||
lBox.AddChild(new Label
|
||||
@@ -167,133 +156,103 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
// Biome
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = "Biome:"
|
||||
Text = Loc.GetString("salvage-expedition-window-biome")
|
||||
});
|
||||
|
||||
var biome = mission.Biome;
|
||||
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = _prototype.Index<BiomePrototype>(config.Biome).Description,
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
Margin = new Thickness(0f, 0f, 0f, 5f),
|
||||
});
|
||||
|
||||
// Environment
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = "Environment:"
|
||||
});
|
||||
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = config.Description,
|
||||
Text = Loc.GetString(_prototype.Index<SalvageBiomeMod>(biome).ID),
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
Margin = new Thickness(0f, 0f, 0f, 5f),
|
||||
});
|
||||
|
||||
// Modifiers
|
||||
// TODO
|
||||
|
||||
// Rewards
|
||||
lBox.AddChild(new Label()
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
Text = $"Reward:"
|
||||
Text = Loc.GetString("salvage-expedition-window-modifiers")
|
||||
});
|
||||
|
||||
var salvageReward = SharedSalvageSystem.GetReward(_prototype.Index<WeightedRandomPrototype>(config.Reward), mission.Seed, _prototype);
|
||||
var difficulty = config.DifficultyRating;
|
||||
var rewardDesc = string.Empty;
|
||||
var mods = mission.Modifiers;
|
||||
|
||||
switch (salvageReward)
|
||||
lBox.AddChild(new Label
|
||||
{
|
||||
case BankReward bank:
|
||||
rewardDesc = $"Bank payment of {(int) (bank.Amount * SharedSalvageSystem.GetDifficultyModifier(difficulty))}";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
lBox.AddChild(new Label()
|
||||
{
|
||||
Text = rewardDesc,
|
||||
FontColorOverride = StyleNano.GoodGreenFore,
|
||||
Text = string.Join("\n", mods.Select(o => "- " + o)).TrimEnd(),
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
Margin = new Thickness(0f, 0f, 0f, 5f),
|
||||
});
|
||||
|
||||
|
||||
lBox.AddChild(new Label()
|
||||
{
|
||||
Text = $"Materials:"
|
||||
Text = Loc.GetString("salvage-expedition-window-loot")
|
||||
});
|
||||
|
||||
if (config.Loots.Count == 0)
|
||||
if (mission.Loot.Count == 0)
|
||||
{
|
||||
lBox.AddChild(new Label()
|
||||
{
|
||||
Text = "N/A",
|
||||
Text = Loc.GetString("salvage-expedition-window-none"),
|
||||
FontColorOverride = StyleNano.ConcerningOrangeFore,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
Margin = new Thickness(0f, 0f, 0f, 5f),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var lootProto in SharedSalvageSystem.GetLoot(config.Loots, mission.Seed, _prototype))
|
||||
{
|
||||
lBox.AddChild(new Label()
|
||||
{
|
||||
Text = lootProto.Description,
|
||||
Text = string.Join("\n", mission.Loot.Select(o => "- " + _prototype.Index<SalvageLootPrototype>(o.Key).Description + (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()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Pressed = state.ActiveMission == mission.Index,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
Pressed = state.ActiveMission == missionParams.Index,
|
||||
ToggleMode = true,
|
||||
Disabled = state.Claimed,
|
||||
Disabled = state.Claimed || state.Cooldown,
|
||||
};
|
||||
|
||||
claimButton.Label.Margin = new Thickness(0f, 5f);
|
||||
|
||||
claimButton.OnPressed += args =>
|
||||
{
|
||||
ClaimMission?.Invoke(mission.Index);
|
||||
ClaimMission?.Invoke(missionParams.Index);
|
||||
};
|
||||
|
||||
if (state.ActiveMission == mission.Index)
|
||||
if (state.ActiveMission == missionParams.Index)
|
||||
{
|
||||
claimButton.Text = "Claimed";
|
||||
claimButton.Text = Loc.GetString("salvage-expedition-window-claimed");
|
||||
claimButton.AddStyleClass(StyleBase.ButtonCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
claimButton.Text = "Claim";
|
||||
claimButton.Text = Loc.GetString("salvage-expedition-window-claim");
|
||||
}
|
||||
|
||||
// TODO: Fix this copypaste bullshit
|
||||
|
||||
var box = new PanelContainer()
|
||||
var box = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat(new Color(30, 30, 34)),
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(5f, 0f),
|
||||
Children =
|
||||
{
|
||||
new BoxContainer()
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
missionStripe,
|
||||
lBox,
|
||||
new Control() {VerticalExpand = true},
|
||||
claimButton,
|
||||
},
|
||||
Margin = new Thickness(5f, 5f)
|
||||
@@ -314,7 +273,7 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
if (_claimed)
|
||||
{
|
||||
NextOfferBar.Value = 0f;
|
||||
NextOfferText.Text = "N/A";
|
||||
NextOfferText.Text = "00:00";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,7 +286,11 @@ public sealed partial class SalvageExpeditionWindow : FancyWindow,
|
||||
}
|
||||
else
|
||||
{
|
||||
NextOfferBar.Value = 1f - (float) (remaining / SharedSalvageSystem.MissionCooldown);
|
||||
var cooldown = _cooldown
|
||||
? SharedSalvageSystem.MissionFailedCooldown
|
||||
: SharedSalvageSystem.MissionCooldown;
|
||||
|
||||
NextOfferBar.Value = 1f - (float) (remaining / cooldown);
|
||||
NextOfferText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Module;
|
||||
|
||||
namespace Content.Server.IoC
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_protoManager.HasIndex<BiomePrototype>(args[1]))
|
||||
if (!_protoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var biomeTemplate))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
|
||||
return;
|
||||
@@ -63,8 +63,9 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
MetaDataComponent? metadata = null;
|
||||
|
||||
var biome = _entManager.EnsureComponent<BiomeComponent>(mapUid);
|
||||
_entManager.System<BiomeSystem>().SetPrototype(biome, args[1]);
|
||||
_entManager.System<BiomeSystem>().SetSeed(biome, _random.Next());
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
biomeSystem.SetSeed(biome, _random.Next());
|
||||
biomeSystem.SetTemplate(biome, biomeTemplate);
|
||||
_entManager.Dirty(biome);
|
||||
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
|
||||
@@ -106,7 +107,7 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var options = _protoManager.EnumeratePrototypes<BiomePrototype>()
|
||||
var options = _protoManager.EnumeratePrototypes<BiomeTemplatePrototype>()
|
||||
.Select(o => new CompletionOption(o.ID, "Biome"));
|
||||
return CompletionResult.FromOptions(options);
|
||||
}
|
||||
|
||||
@@ -162,8 +162,9 @@ public sealed class HTNSystem : EntitySystem
|
||||
public void UpdateNPC(ref int count, int maxUpdates, float frameTime)
|
||||
{
|
||||
_planQueue.Process();
|
||||
var query = EntityQueryEnumerator<ActiveNPCComponent, HTNComponent>();
|
||||
|
||||
foreach (var (_, comp) in EntityQuery<ActiveNPCComponent, HTNComponent>())
|
||||
while(query.MoveNext(out var uid, out _, out var comp))
|
||||
{
|
||||
// If we're over our max count or it's not MapInit then ignore the NPC.
|
||||
if (count >= maxUpdates)
|
||||
@@ -173,10 +174,10 @@ public sealed class HTNSystem : EntitySystem
|
||||
{
|
||||
if (comp.PlanningJob.Exception != null)
|
||||
{
|
||||
_sawmill.Fatal($"Received exception on planning job for {comp.Owner}!");
|
||||
_npc.SleepNPC(comp.Owner);
|
||||
_sawmill.Fatal($"Received exception on planning job for {uid}!");
|
||||
_npc.SleepNPC(uid);
|
||||
var exc = comp.PlanningJob.Exception;
|
||||
RemComp<HTNComponent>(comp.Owner);
|
||||
RemComp<HTNComponent>(uid);
|
||||
throw exc;
|
||||
}
|
||||
|
||||
@@ -231,7 +232,7 @@ public sealed class HTNSystem : EntitySystem
|
||||
|
||||
RaiseNetworkEvent(new HTNMessage()
|
||||
{
|
||||
Uid = comp.Owner,
|
||||
Uid = uid,
|
||||
Text = text.ToString(),
|
||||
}, session.ConnectedClient);
|
||||
}
|
||||
|
||||
172
Content.Server/Parallax/BiomeSystem.Commands.cs
Normal file
172
Content.Server/Parallax/BiomeSystem.Commands.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Parallax;
|
||||
|
||||
public sealed partial class BiomeSystem
|
||||
{
|
||||
private void InitializeCommands()
|
||||
{
|
||||
_console.RegisterCommand("biome_clear", Loc.GetString("cmd-biome_clear-desc"), Loc.GetString("cmd-biome_clear-help"), BiomeClearCallback, BiomeClearCallbackHelper);
|
||||
_console.RegisterCommand("biome_addlayer", Loc.GetString("cmd-biome_addlayer-desc"), Loc.GetString("cmd-biome_addlayer-help"), AddLayerCallback, AddLayerCallbackHelp);
|
||||
_console.RegisterCommand("biome_addmarkerlayer", Loc.GetString("cmd-biome_addmarkerlayer-desc"), Loc.GetString("cmd-biome_addmarkerlayer-desc"), AddMarkerLayerCallback, AddMarkerLayerCallbackHelper);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void BiomeClearCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int.TryParse(args[0], out var mapInt);
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (_mapManager.MapExists(mapId) ||
|
||||
!TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearTemplate(biome);
|
||||
}
|
||||
|
||||
private CompletionResult BiomeClearCallbackHelper(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.Components<BiomeComponent>(args[0], EntityManager), "Biome");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddLayerCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length < 3 || args.Length > 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_mapManager.MapExists(mapId) || !TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
|
||||
if (args.Length == 4)
|
||||
{
|
||||
int.TryParse(args[3], out offset);
|
||||
}
|
||||
|
||||
AddTemplate(biome, args[2], template, offset);
|
||||
}
|
||||
|
||||
private CompletionResult AddLayerCallbackHelp(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map ID");
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: _proto), "Biome template");
|
||||
}
|
||||
|
||||
if (args.Length == 3)
|
||||
{
|
||||
if (int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
|
||||
{
|
||||
var results = new List<string>();
|
||||
|
||||
foreach (var layer in biome.Layers)
|
||||
{
|
||||
if (layer is not BiomeDummyLayer dummy)
|
||||
continue;
|
||||
|
||||
results.Add(dummy.ID);
|
||||
}
|
||||
|
||||
return CompletionResult.FromHintOptions(results, "Dummy layer ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length == 4)
|
||||
{
|
||||
return CompletionResult.FromHint("Seed offset");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddMarkerLayerCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_mapManager.MapExists(mapId) || !TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
biome.MarkerLayers.Add(args[1]);
|
||||
}
|
||||
|
||||
private CompletionResult AddMarkerLayerCallbackHelper(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.Components<BiomeComponent>(args[0], EntityManager), "Biome");
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: _proto), "Marker");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,31 @@
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Content.Shared.Parallax.Biomes.Points;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Parallax;
|
||||
|
||||
public sealed class BiomeSystem : SharedBiomeSystem
|
||||
public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DecalSystem _decals = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
@@ -23,6 +33,10 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
private readonly HashSet<EntityUid> _handledEntities = new();
|
||||
private const float DefaultLoadRange = 16f;
|
||||
private float _loadRange = DefaultLoadRange;
|
||||
|
||||
/// <summary>
|
||||
/// Load area for chunks containing tiles, decals etc.
|
||||
/// </summary>
|
||||
private Box2 _loadArea = new(-DefaultLoadRange, -DefaultLoadRange, DefaultLoadRange, DefaultLoadRange);
|
||||
|
||||
/// <summary>
|
||||
@@ -30,18 +44,41 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
/// </summary>
|
||||
private readonly Dictionary<BiomeComponent, HashSet<Vector2i>> _activeChunks = new();
|
||||
|
||||
private readonly Dictionary<BiomeComponent,
|
||||
Dictionary<string, HashSet<Vector2i>>> _markerChunks = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BiomeComponent, ComponentStartup>(OnBiomeStartup);
|
||||
SubscribeLocalEvent<BiomeComponent, MapInitEvent>(OnBiomeMapInit);
|
||||
SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
|
||||
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, SetLoadRange, true);
|
||||
InitializeCommands();
|
||||
_proto.PrototypesReloaded += ProtoReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, SetLoadRange);
|
||||
_proto.PrototypesReloaded -= ProtoReload;
|
||||
}
|
||||
|
||||
private void ProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.ByType.TryGetValue(typeof(BiomeTemplatePrototype), out var reloads))
|
||||
return;
|
||||
|
||||
var query = AllEntityQuery<BiomeComponent>();
|
||||
|
||||
while (query.MoveNext(out var biome))
|
||||
{
|
||||
if (biome.Template == null || !reloads.Modified.TryGetValue(biome.Template, out var proto))
|
||||
continue;
|
||||
|
||||
SetTemplate(biome, (BiomeTemplatePrototype) proto);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLoadRange(float obj)
|
||||
@@ -61,15 +98,6 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
SetSeed(component, _random.Next());
|
||||
}
|
||||
|
||||
public void SetPrototype(BiomeComponent component, string proto)
|
||||
{
|
||||
if (component.BiomePrototype == proto)
|
||||
return;
|
||||
|
||||
component.BiomePrototype = proto;
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
public void SetSeed(BiomeComponent component, int seed)
|
||||
{
|
||||
component.Seed = seed;
|
||||
@@ -77,6 +105,120 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
public void ClearTemplate(BiomeComponent component)
|
||||
{
|
||||
component.Layers.Clear();
|
||||
component.Template = null;
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="BiomeComponent.Template"/> and refreshes layers.
|
||||
/// </summary>
|
||||
public void SetTemplate(BiomeComponent component, BiomeTemplatePrototype template)
|
||||
{
|
||||
component.Layers.Clear();
|
||||
component.Template = template.ID;
|
||||
|
||||
foreach (var layer in template.Layers)
|
||||
{
|
||||
component.Layers.Add(layer);
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified layer at the specified marker if it exists.
|
||||
/// </summary>
|
||||
public void AddLayer(BiomeComponent component, string id, IBiomeLayer addedLayer, int seedOffset = 0)
|
||||
{
|
||||
for (var i = 0; i < component.Layers.Count; i++)
|
||||
{
|
||||
var layer = component.Layers[i];
|
||||
|
||||
if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
|
||||
continue;
|
||||
|
||||
addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
|
||||
component.Layers.Insert(i, addedLayer);
|
||||
break;
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
public void AddMarkerLayer(BiomeComponent component, string marker)
|
||||
{
|
||||
if (!_proto.HasIndex<BiomeMarkerLayerPrototype>(marker))
|
||||
{
|
||||
// TODO: Log when we get a sawmill
|
||||
return;
|
||||
}
|
||||
|
||||
component.MarkerLayers.Add(marker);
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified template at the specified marker if it exists, withour overriding every layer.
|
||||
/// </summary>
|
||||
public void AddTemplate(BiomeComponent component, string id, BiomeTemplatePrototype template, int seedOffset = 0)
|
||||
{
|
||||
for (var i = 0; i < component.Layers.Count; i++)
|
||||
{
|
||||
var layer = component.Layers[i];
|
||||
|
||||
if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
|
||||
continue;
|
||||
|
||||
for (var j = template.Layers.Count - 1; j >= 0; j--)
|
||||
{
|
||||
var addedLayer = template.Layers[j];
|
||||
addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
|
||||
component.Layers.Insert(i, addedLayer);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void OnFTLStarted(ref FTLStartedEvent ev)
|
||||
{
|
||||
var targetMap = ev.TargetCoordinates.ToMap(EntityManager, _transform);
|
||||
var targetMapUid = _mapManager.GetMapEntityId(targetMap.MapId);
|
||||
|
||||
if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
|
||||
return;
|
||||
|
||||
var targetArea = new Box2(targetMap.Position - 64f, targetMap.Position + 64f);
|
||||
Preload(targetMapUid, biome, targetArea);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preloads biome for the specified area.
|
||||
/// </summary>
|
||||
public void Preload(EntityUid uid, BiomeComponent component, Box2 area)
|
||||
{
|
||||
var markers = component.MarkerLayers;
|
||||
var goobers = _markerChunks.GetOrNew(component);
|
||||
|
||||
foreach (var layer in markers)
|
||||
{
|
||||
var proto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var enumerator = new ChunkIndicesEnumerator(area, proto.Size);
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var chunkOrigin = chunk * proto.Size;
|
||||
var layerChunks = goobers.GetOrNew(proto.ID);
|
||||
layerChunks.Add(chunkOrigin.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -87,6 +229,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
while (biomes.MoveNext(out var biome))
|
||||
{
|
||||
_activeChunks.Add(biome, new HashSet<Vector2i>());
|
||||
_markerChunks.GetOrNew(biome);
|
||||
}
|
||||
|
||||
// Get chunks in range
|
||||
@@ -98,7 +241,14 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
_handledEntities.Add(pSession.AttachedEntity.Value) &&
|
||||
biomeQuery.TryGetComponent(xform.MapUid, out var biome))
|
||||
{
|
||||
AddChunksInRange(biome, _transform.GetWorldPosition(xform, xformQuery));
|
||||
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
|
||||
AddChunksInRange(biome, worldPos);
|
||||
|
||||
foreach (var layer in biome.MarkerLayers)
|
||||
{
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
AddMarkerChunksInRange(biome, worldPos, layerProto);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var viewer in pSession.ViewSubscriptions)
|
||||
@@ -110,16 +260,22 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
AddChunksInRange(biome, _transform.GetWorldPosition(xform, xformQuery));
|
||||
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
|
||||
AddChunksInRange(biome, worldPos);
|
||||
|
||||
foreach (var layer in biome.MarkerLayers)
|
||||
{
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
AddMarkerChunksInRange(biome, worldPos, layerProto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadBiomes = AllEntityQuery<BiomeComponent, MapGridComponent>();
|
||||
|
||||
while (loadBiomes.MoveNext(out var biome, out var grid))
|
||||
while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
|
||||
{
|
||||
var noise = biome.Noise;
|
||||
var gridUid = grid.Owner;
|
||||
|
||||
// Load new chunks
|
||||
LoadChunks(biome, gridUid, grid, noise, xformQuery);
|
||||
@@ -129,6 +285,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
_handledEntities.Clear();
|
||||
_activeChunks.Clear();
|
||||
_markerChunks.Clear();
|
||||
}
|
||||
|
||||
private void AddChunksInRange(BiomeComponent biome, Vector2 worldPos)
|
||||
@@ -141,6 +298,21 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMarkerChunksInRange(BiomeComponent biome, Vector2 worldPos, IBiomeMarkerLayer layer)
|
||||
{
|
||||
// Offset the load area so it's centralised.
|
||||
var loadArea = new Box2(0, 0, layer.Size, layer.Size);
|
||||
var enumerator = new ChunkIndicesEnumerator(loadArea.Translated(worldPos - layer.Size / 2f), layer.Size);
|
||||
|
||||
while (enumerator.MoveNext(out var chunkOrigin))
|
||||
{
|
||||
var lay = _markerChunks[biome].GetOrNew(layer.ID);
|
||||
lay.Add(chunkOrigin.Value * layer.Size);
|
||||
}
|
||||
}
|
||||
|
||||
#region Load
|
||||
|
||||
private void LoadChunks(
|
||||
BiomeComponent component,
|
||||
EntityUid gridUid,
|
||||
@@ -148,8 +320,54 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
FastNoiseLite noise,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var markers = _markerChunks[component];
|
||||
var loadedMarkers = component.LoadedMarkers;
|
||||
|
||||
foreach (var (layer, chunks) in markers)
|
||||
{
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (loadedMarkers.TryGetValue(layer, out var mobChunks) && mobChunks.Contains(chunk))
|
||||
continue;
|
||||
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var buffer = layerProto.Radius / 2f;
|
||||
mobChunks ??= new HashSet<Vector2i>();
|
||||
mobChunks.Add(chunk);
|
||||
loadedMarkers[layer] = mobChunks;
|
||||
var rand = new Random(noise.GetSeed() + chunk.X * 8 + chunk.Y);
|
||||
|
||||
// Load NOW
|
||||
// TODO: Need poisson but crashes whenever I use moony's due to inputs or smth
|
||||
var count = (int) ((layerProto.Size - buffer) * (layerProto.Size - buffer) / (layerProto.Radius * layerProto.Radius));
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
for (var j = 0; j < 5; j++)
|
||||
{
|
||||
var point = new Vector2(
|
||||
chunk.X + buffer * rand.NextFloat() * (layerProto.Size - buffer),
|
||||
chunk.Y + buffer * rand.NextFloat() * (layerProto.Size - buffer));
|
||||
|
||||
var coords = new EntityCoordinates(gridUid, point);
|
||||
var tile = grid.LocalToTile(coords);
|
||||
|
||||
// Blocked spawn, try again.
|
||||
if (grid.GetAnchoredEntitiesEnumerator(tile).MoveNext(out _))
|
||||
continue;
|
||||
|
||||
for (var k = 0; k < layerProto.GroupCount; k++)
|
||||
{
|
||||
Spawn(layerProto.Prototype, new EntityCoordinates(gridUid, point));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var active = _activeChunks[component];
|
||||
var prototype = ProtoManager.Index<BiomePrototype>(component.BiomePrototype);
|
||||
List<(Vector2i, Tile)>? tiles = null;
|
||||
|
||||
foreach (var chunk in active)
|
||||
@@ -159,7 +377,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
|
||||
// Load NOW!
|
||||
LoadChunk(component, gridUid, grid, chunk, noise, prototype, tiles, xformQuery);
|
||||
LoadChunk(component, gridUid, grid, chunk, noise, tiles, xformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +387,6 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
MapGridComponent grid,
|
||||
Vector2i chunk,
|
||||
FastNoiseLite noise,
|
||||
BiomePrototype prototype,
|
||||
List<(Vector2i, Tile)> tiles,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
@@ -191,7 +408,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Pass in null so we don't try to get the tileref.
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, null, out var biomeTile) || biomeTile.Value == tileRef.Tile)
|
||||
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) || biomeTile.Value == tileRef.Tile)
|
||||
continue;
|
||||
|
||||
tiles.Add((indices, biomeTile.Value));
|
||||
@@ -217,7 +434,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, prototype, noise, grid, out var entPrototype))
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component.Layers, noise, grid, out var entPrototype))
|
||||
continue;
|
||||
|
||||
// TODO: Fix non-anchored ents spawning.
|
||||
@@ -227,7 +444,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
// At least for now unless we do lookups or smth, only work with anchoring.
|
||||
if (xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored)
|
||||
{
|
||||
_transform.AnchorEntity(ent, xform, grid, indices);
|
||||
_transform.AnchorEntity(ent, xform, gridUid, grid, indices);
|
||||
}
|
||||
|
||||
loadedEntities.Add(ent);
|
||||
@@ -250,7 +467,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, prototype, noise, grid, out var decals))
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, noise, grid, out var decals))
|
||||
continue;
|
||||
|
||||
foreach (var decal in decals)
|
||||
@@ -273,6 +490,10 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unload
|
||||
|
||||
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, FastNoiseLite noise)
|
||||
{
|
||||
var active = _activeChunks[component];
|
||||
@@ -292,7 +513,6 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, FastNoiseLite noise, List<(Vector2i, Tile)> tiles)
|
||||
{
|
||||
// Reverse order to loading
|
||||
var prototype = ProtoManager.Index<BiomePrototype>(component.BiomePrototype);
|
||||
component.ModifiedTiles.TryGetValue(chunk, out var modified);
|
||||
modified ??= new HashSet<Vector2i>();
|
||||
|
||||
@@ -338,7 +558,7 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
|
||||
// If it's default data unload the tile.
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, null, out var biomeTile) ||
|
||||
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) ||
|
||||
grid.TryGetTileRef(indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
|
||||
{
|
||||
modified.Add(indices);
|
||||
@@ -362,4 +582,6 @@ public sealed class BiomeSystem : SharedBiomeSystem
|
||||
component.ModifiedTiles[chunk] = modified;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -125,7 +125,10 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
|
||||
var tiles = new List<(Vector2i, Tile)>();
|
||||
var dungeon = new Dungeon();
|
||||
var dungeon = new Dungeon()
|
||||
{
|
||||
Position = _position
|
||||
};
|
||||
var availablePacks = new List<DungeonRoomPackPrototype>();
|
||||
var chosenPacks = new DungeonRoomPackPrototype?[gen.RoomPacks.Count];
|
||||
var packTransforms = new Matrix3[gen.RoomPacks.Count];
|
||||
@@ -424,6 +427,16 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate center
|
||||
var dungeonCenter = Vector2.Zero;
|
||||
|
||||
foreach (var room in dungeon.Rooms)
|
||||
{
|
||||
dungeonCenter += room.Center;
|
||||
}
|
||||
|
||||
dungeon.Center = (Vector2i) (dungeonCenter / dungeon.Rooms.Count);
|
||||
|
||||
return dungeon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,13 +110,11 @@ public sealed partial class DungeonJob
|
||||
var rooms = new List<DungeonRoom>(dungeon.Rooms);
|
||||
var roomTiles = new List<Vector2i>();
|
||||
var tileData = new Tile(_tileDefManager[gen.Tile].TileId);
|
||||
var count = gen.Count;
|
||||
|
||||
while (count > 0 && rooms.Count > 0)
|
||||
for (var i = 0; i < gen.Count; i++)
|
||||
{
|
||||
var roomIndex = random.Next(rooms.Count);
|
||||
var room = rooms[roomIndex];
|
||||
rooms.RemoveAt(roomIndex);
|
||||
|
||||
// Move out 3 tiles in a direction away from center of the room
|
||||
// If none of those intersect another tile it's probably external
|
||||
@@ -126,12 +124,6 @@ public sealed partial class DungeonJob
|
||||
|
||||
foreach (var tile in roomTiles)
|
||||
{
|
||||
// Check the interior node is at least accessible?
|
||||
// Can't do anchored because it might be a locker or something.
|
||||
// TODO: Better collision mask check
|
||||
if (_lookup.GetEntitiesIntersecting(gridUid, tile, LookupFlags.Dynamic | LookupFlags.Static).Any())
|
||||
continue;
|
||||
|
||||
var direction = (tile - room.Center).ToAngle().GetCardinalDir().ToAngle().ToVec();
|
||||
var isValid = true;
|
||||
|
||||
@@ -163,8 +155,6 @@ public sealed partial class DungeonJob
|
||||
_entManager.SpawnEntity(ent, gridCoords);
|
||||
}
|
||||
|
||||
count--;
|
||||
|
||||
// Clear out any biome tiles nearby to avoid blocking it
|
||||
foreach (var nearTile in grid.GetTilesIntersecting(new Circle(gridCoords.Position, 1.5f), false))
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed partial class DungeonJob : Job<Dungeon>
|
||||
|
||||
private readonly DungeonConfigPrototype _gen;
|
||||
private readonly int _seed;
|
||||
private readonly Vector2 _position;
|
||||
private readonly Vector2i _position;
|
||||
|
||||
private readonly MapGridComponent _grid;
|
||||
private readonly EntityUid _gridUid;
|
||||
@@ -51,7 +51,7 @@ public sealed partial class DungeonJob : Job<Dungeon>
|
||||
MapGridComponent grid,
|
||||
EntityUid gridUid,
|
||||
int seed,
|
||||
Vector2 position,
|
||||
Vector2i position,
|
||||
CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed partial class DungeonSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var position = new Vector2(posX, posY);
|
||||
var position = new Vector2i(posX, posY);
|
||||
var dungeonUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
if (!TryComp<MapGridComponent>(dungeonUid, out var dungeonGrid))
|
||||
|
||||
@@ -161,7 +161,7 @@ public sealed partial class DungeonSystem : EntitySystem
|
||||
public void GenerateDungeon(DungeonConfigPrototype gen,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
Vector2 position,
|
||||
Vector2i position,
|
||||
int seed)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
@@ -193,7 +193,7 @@ public sealed partial class DungeonSystem : EntitySystem
|
||||
DungeonConfigPrototype gen,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
Vector2 position,
|
||||
Vector2i position,
|
||||
int seed)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Salvage.Expeditions;
|
||||
|
||||
/// <summary>
|
||||
/// Designates this entity as holding a salvage expedition.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SalvageExpeditionComponent : Component
|
||||
{
|
||||
public SalvageMissionParams MissionParams = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Where the dungeon is located for initial announcement.
|
||||
/// </summary>
|
||||
[DataField("dungeonLocation")]
|
||||
public Vector2 DungeonLocation = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// When the expeditions ends.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan EndTime;
|
||||
|
||||
/// <summary>
|
||||
/// Station whose mission this is.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("station")]
|
||||
public EntityUid Station;
|
||||
|
||||
[ViewVariables] public bool Completed = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("stage")]
|
||||
public ExpeditionStage Stage = ExpeditionStage.Added;
|
||||
}
|
||||
|
||||
public enum ExpeditionStage : byte
|
||||
{
|
||||
Added,
|
||||
Running,
|
||||
Countdown,
|
||||
FinalCountdown,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Salvage.Expeditions;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks expedition data for <see cref="SalvageMissionType.Mining"/>
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SalvageSystem))]
|
||||
public sealed class SalvageMiningExpeditionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Entities that were present on the shuttle and match the loot tax.
|
||||
/// </summary>
|
||||
[DataField("exemptEntities")]
|
||||
public List<EntityUid> ExemptEntities = new();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Salvage.Expeditions.Structure;
|
||||
|
||||
/// <summary>
|
||||
/// Mission objective for salvage expeditions.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SalvageSystem))]
|
||||
public sealed class SalvageStructureComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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 class SalvageStructureExpeditionComponent : Component
|
||||
{
|
||||
[DataField("structures")]
|
||||
public readonly List<EntityUid> Structures = new();
|
||||
}
|
||||
67
Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs
Normal file
67
Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
public sealed partial class SalvageSystem
|
||||
{
|
||||
private void OnSalvageClaimMessage(EntityUid uid, SalvageExpeditionConsoleComponent component, ClaimSalvageMessage args)
|
||||
{
|
||||
var station = _station.GetOwningStation(uid);
|
||||
|
||||
if (!TryComp<SalvageExpeditionDataComponent>(station, out var data) || data.Claimed)
|
||||
return;
|
||||
|
||||
if (!data.Missions.TryGetValue(args.Index, out var missionparams))
|
||||
return;
|
||||
|
||||
SpawnMission(missionparams, station.Value);
|
||||
|
||||
data.ActiveMission = args.Index;
|
||||
var mission = GetMission(missionparams.MissionType, missionparams.Difficulty, missionparams.Seed);
|
||||
data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1);
|
||||
UpdateConsoles(data);
|
||||
}
|
||||
|
||||
private void OnSalvageConsoleInit(EntityUid uid, SalvageExpeditionConsoleComponent component, ComponentInit args)
|
||||
{
|
||||
UpdateConsole(component);
|
||||
}
|
||||
|
||||
private void OnSalvageConsoleParent(EntityUid uid, SalvageExpeditionConsoleComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
UpdateConsole(component);
|
||||
}
|
||||
|
||||
private void UpdateConsoles(SalvageExpeditionDataComponent component)
|
||||
{
|
||||
var state = GetState(component);
|
||||
|
||||
foreach (var (console, xform, uiComp) in EntityQuery<SalvageExpeditionConsoleComponent, TransformComponent, ServerUserInterfaceComponent>(true))
|
||||
{
|
||||
var station = _station.GetOwningStation(console.Owner, xform);
|
||||
|
||||
if (station != component.Owner)
|
||||
continue;
|
||||
|
||||
_ui.TrySetUiState(console.Owner, SalvageConsoleUiKey.Expedition, state, ui: uiComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateConsole(SalvageExpeditionConsoleComponent component)
|
||||
{
|
||||
var station = _station.GetOwningStation(component.Owner);
|
||||
SalvageExpeditionConsoleState state;
|
||||
|
||||
if (TryComp<SalvageExpeditionDataComponent>(station, out var dataComponent))
|
||||
{
|
||||
state = GetState(dataComponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = new SalvageExpeditionConsoleState(TimeSpan.Zero, false, true, 0, new List<SalvageMissionParams>());
|
||||
}
|
||||
|
||||
_ui.TrySetUiState(component.Owner, SalvageConsoleUiKey.Expedition, state);
|
||||
}
|
||||
}
|
||||
241
Content.Server/Salvage/SalvageSystem.Expeditions.cs
Normal file
241
Content.Server/Salvage/SalvageSystem.Expeditions.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.CPUJob.JobQueues;
|
||||
using Content.Server.CPUJob.JobQueues.Queues;
|
||||
using Content.Server.Salvage.Expeditions;
|
||||
using Content.Server.Salvage.Expeditions.Structure;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Salvage;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
public sealed partial class SalvageSystem
|
||||
{
|
||||
/*
|
||||
* Handles setup / teardown of salvage expeditions.
|
||||
*/
|
||||
|
||||
private const int MissionLimit = 5;
|
||||
|
||||
private readonly JobQueue _salvageQueue = new();
|
||||
private readonly List<(SpawnSalvageMissionJob Job, CancellationTokenSource CancelToken)> _salvageJobs = new();
|
||||
private const double SalvageJobTime = 0.002;
|
||||
|
||||
private void InitializeExpeditions()
|
||||
{
|
||||
SubscribeLocalEvent<StationInitializedEvent>(OnSalvageExpStationInit);
|
||||
|
||||
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ComponentInit>(OnSalvageConsoleInit);
|
||||
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, EntParentChangedMessage>(OnSalvageConsoleParent);
|
||||
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ClaimSalvageMessage>(OnSalvageClaimMessage);
|
||||
|
||||
SubscribeLocalEvent<SalvageExpeditionDataComponent, EntityUnpausedEvent>(OnDataUnpaused);
|
||||
|
||||
SubscribeLocalEvent<SalvageExpeditionComponent, ComponentShutdown>(OnExpeditionShutdown);
|
||||
SubscribeLocalEvent<SalvageExpeditionComponent, EntityUnpausedEvent>(OnExpeditionUnpaused);
|
||||
|
||||
SubscribeLocalEvent<SalvageStructureComponent, ExaminedEvent>(OnStructureExamine);
|
||||
}
|
||||
|
||||
private void OnExpeditionShutdown(EntityUid uid, SalvageExpeditionComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var (job, cancelToken) in _salvageJobs.ToArray())
|
||||
{
|
||||
if (job.Station == component.Station)
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
_salvageJobs.Remove((job, cancelToken));
|
||||
}
|
||||
}
|
||||
|
||||
if (Deleted(component.Station))
|
||||
return;
|
||||
|
||||
// Finish mission
|
||||
if (TryComp<SalvageExpeditionDataComponent>(component.Station, out var data))
|
||||
{
|
||||
FinishExpedition(data, component, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataUnpaused(EntityUid uid, SalvageExpeditionDataComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.NextOffer += args.PausedTime;
|
||||
}
|
||||
|
||||
private void OnExpeditionUnpaused(EntityUid uid, SalvageExpeditionComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.EndTime += args.PausedTime;
|
||||
}
|
||||
|
||||
private void OnSalvageExpStationInit(StationInitializedEvent ev)
|
||||
{
|
||||
EnsureComp<SalvageExpeditionDataComponent>(ev.Station);
|
||||
}
|
||||
|
||||
private void UpdateExpeditions()
|
||||
{
|
||||
var currentTime = _timing.CurTime;
|
||||
_salvageQueue.Process();
|
||||
|
||||
foreach (var (job, cancelToken) in _salvageJobs.ToArray())
|
||||
{
|
||||
switch (job.Status)
|
||||
{
|
||||
case JobStatus.Finished:
|
||||
_salvageJobs.Remove((job, cancelToken));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var comp in EntityQuery<SalvageExpeditionDataComponent>())
|
||||
{
|
||||
// Update offers
|
||||
if (comp.NextOffer > currentTime || comp.Claimed)
|
||||
continue;
|
||||
|
||||
comp.Cooldown = false;
|
||||
comp.NextOffer += MissionCooldown;
|
||||
GenerateMissions(comp);
|
||||
UpdateConsoles(comp);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<SalvageExpeditionComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.EndTime < currentTime)
|
||||
{
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FinishExpedition(SalvageExpeditionDataComponent component, SalvageExpeditionComponent expedition, EntityUid? shuttle)
|
||||
{
|
||||
// Finish mission cleanup.
|
||||
switch (expedition.MissionParams.MissionType)
|
||||
{
|
||||
// Handles the mining taxation.
|
||||
case SalvageMissionType.Mining:
|
||||
expedition.Completed = true;
|
||||
|
||||
if (shuttle != null && TryComp<SalvageMiningExpeditionComponent>(expedition.Owner, 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;
|
||||
}
|
||||
|
||||
// Payout already handled elsewhere.
|
||||
if (expedition.Completed)
|
||||
{
|
||||
_sawmill.Debug($"Completed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
|
||||
component.NextOffer = _timing.CurTime + MissionCooldown;
|
||||
Announce(expedition.Owner, Loc.GetString("salvage-expedition-mission-completed"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmill.Debug($"Failed mission {expedition.MissionParams.MissionType} with seed {expedition.MissionParams.Seed}");
|
||||
component.NextOffer = _timing.CurTime + MissionFailedCooldown;
|
||||
Announce(expedition.Owner, Loc.GetString("salvage-expedition-mission-failed"));
|
||||
}
|
||||
|
||||
component.ActiveMission = 0;
|
||||
component.Cooldown = true;
|
||||
UpdateConsoles(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deducts ore tax for mining.
|
||||
/// </summary>
|
||||
private void MiningTax(List<EntityUid> entities, EntityUid entity, SalvageMiningExpeditionComponent mining, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
if (!mining.ExemptEntities.Contains(entity))
|
||||
{
|
||||
entities.Add(entity);
|
||||
}
|
||||
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
var children = xform.ChildEnumerator;
|
||||
|
||||
while (children.MoveNext(out var child))
|
||||
{
|
||||
MiningTax(entities, child.Value, mining, xformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMissions(SalvageExpeditionDataComponent component)
|
||||
{
|
||||
component.Missions.Clear();
|
||||
var configs = Enum.GetValues<SalvageMissionType>().ToList();
|
||||
|
||||
if (configs.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < MissionLimit; i++)
|
||||
{
|
||||
_random.Shuffle(configs);
|
||||
var rating = (DifficultyRating) i;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SalvageExpeditionConsoleState GetState(SalvageExpeditionDataComponent component)
|
||||
{
|
||||
var missions = component.Missions.Values.ToList();
|
||||
return new SalvageExpeditionConsoleState(component.NextOffer, component.Claimed, component.Cooldown, component.ActiveMission, missions);
|
||||
}
|
||||
|
||||
private void SpawnMission(SalvageMissionParams missionParams, EntityUid station)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var job = new SpawnSalvageMissionJob(
|
||||
SalvageJobTime,
|
||||
EntityManager,
|
||||
_timing,
|
||||
_mapManager,
|
||||
_prototypeManager,
|
||||
_tileDefManager,
|
||||
_biome,
|
||||
_dungeon,
|
||||
this,
|
||||
station,
|
||||
missionParams,
|
||||
cancelToken.Token);
|
||||
|
||||
_salvageJobs.Add((job, cancelToken));
|
||||
_salvageQueue.EnqueueJob(job);
|
||||
}
|
||||
|
||||
private void OnStructureExamine(EntityUid uid, SalvageStructureComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("salvage-expedition-structure-examine"));
|
||||
}
|
||||
}
|
||||
194
Content.Server/Salvage/SalvageSystem.Runner.cs
Normal file
194
Content.Server/Salvage/SalvageSystem.Runner.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using Content.Server.Salvage.Expeditions;
|
||||
using Content.Server.Salvage.Expeditions.Structure;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
public sealed partial class SalvageSystem
|
||||
{
|
||||
/*
|
||||
* Handles actively running a salvage expedition.
|
||||
*/
|
||||
|
||||
private void InitializeRunner()
|
||||
{
|
||||
SubscribeLocalEvent<FTLRequestEvent>(OnFTLRequest);
|
||||
SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
|
||||
SubscribeLocalEvent<FTLCompletedEvent>(OnFTLCompleted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Announces status updates to salvage crewmembers on the state of the expedition.
|
||||
/// </summary>
|
||||
private void Announce(EntityUid mapUid, string text)
|
||||
{
|
||||
var mapId = Comp<MapComponent>(mapUid).MapId;
|
||||
|
||||
// I love TComms and chat!!!
|
||||
_chat.ChatMessageToManyFiltered(
|
||||
Filter.BroadcastMap(mapId),
|
||||
ChatChannel.Radio,
|
||||
text,
|
||||
text,
|
||||
_mapManager.GetMapEntityId(mapId),
|
||||
false,
|
||||
true,
|
||||
null);
|
||||
}
|
||||
|
||||
private void OnFTLRequest(ref FTLRequestEvent ev)
|
||||
{
|
||||
if (!HasComp<SalvageExpeditionComponent>(ev.MapUid) ||
|
||||
!TryComp<FTLDestinationComponent>(ev.MapUid, out var dest))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only one shuttle can occupy an expedition.
|
||||
dest.Enabled = false;
|
||||
_shuttleConsoles.RefreshShuttleConsoles();
|
||||
}
|
||||
|
||||
private void OnFTLCompleted(ref FTLCompletedEvent args)
|
||||
{
|
||||
if (!TryComp<SalvageExpeditionComponent>(args.MapUid, out var component))
|
||||
return;
|
||||
|
||||
// Someone FTLd there so start announcement
|
||||
if (component.Stage != ExpeditionStage.Added)
|
||||
return;
|
||||
|
||||
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", (component.EndTime - _timing.CurTime).Minutes)));
|
||||
|
||||
if (component.DungeonLocation != Vector2.Zero)
|
||||
Announce(args.MapUid, Loc.GetString("salvage-expedition-announcement-dungeon", ("direction", component.DungeonLocation.GetDir())));
|
||||
|
||||
component.Stage = ExpeditionStage.Running;
|
||||
}
|
||||
|
||||
private void OnFTLStarted(ref FTLStartedEvent ev)
|
||||
{
|
||||
// Started a mining mission so work out exempt entities
|
||||
if (TryComp<SalvageMiningExpeditionComponent>(
|
||||
_mapManager.GetMapEntityId(ev.TargetCoordinates.ToMap(EntityManager, _transform).MapId),
|
||||
out var mining))
|
||||
{
|
||||
var ents = new List<EntityUid>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
MiningTax(ents, ev.Entity, mining, xformQuery);
|
||||
mining.ExemptEntities = ents;
|
||||
}
|
||||
|
||||
if (!TryComp<SalvageExpeditionComponent>(ev.FromMapUid, out var expedition) ||
|
||||
!TryComp<SalvageExpeditionDataComponent>(expedition.Station, out var station))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any shuttles remain.
|
||||
var query = EntityQueryEnumerator<ShuttleComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out _, out var xform))
|
||||
{
|
||||
if (xform.MapUid == ev.FromMapUid)
|
||||
return;
|
||||
}
|
||||
|
||||
// Last shuttle has left so finish the mission.
|
||||
QueueDel(ev.FromMapUid.Value);
|
||||
}
|
||||
|
||||
// Runs the expedition
|
||||
private void UpdateRunner()
|
||||
{
|
||||
// Generic missions
|
||||
var query = EntityQueryEnumerator<SalvageExpeditionComponent>();
|
||||
|
||||
// Run the basic mission timers (e.g. announcements, auto-FTL, completion, etc)
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Completed)
|
||||
continue;
|
||||
|
||||
var remaining = comp.EndTime - _timing.CurTime;
|
||||
|
||||
if (comp.Stage < ExpeditionStage.FinalCountdown && remaining < TimeSpan.FromSeconds(30))
|
||||
{
|
||||
comp.Stage = ExpeditionStage.FinalCountdown;
|
||||
Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-seconds", ("duration", TimeSpan.FromSeconds(30).Seconds)));
|
||||
}
|
||||
// TODO: Play song.
|
||||
else if (comp.Stage < ExpeditionStage.Countdown && remaining < TimeSpan.FromMinutes(2))
|
||||
{
|
||||
comp.Stage = ExpeditionStage.Countdown;
|
||||
Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(2).Minutes)));
|
||||
}
|
||||
// Auto-FTL out any shuttles
|
||||
else if (remaining < TimeSpan.FromSeconds(ShuttleSystem.DefaultStartupTime) + TimeSpan.FromSeconds(0.5))
|
||||
{
|
||||
var ftlTime = (float) remaining.TotalSeconds;
|
||||
|
||||
if (remaining < TimeSpan.FromSeconds(ShuttleSystem.DefaultStartupTime))
|
||||
{
|
||||
ftlTime = MathF.Max(0, (float) remaining.TotalSeconds - 0.5f);
|
||||
}
|
||||
|
||||
ftlTime = MathF.Min(ftlTime, ShuttleSystem.DefaultStartupTime);
|
||||
var shuttleQuery = AllEntityQuery<ShuttleComponent, TransformComponent>();
|
||||
|
||||
if (TryComp<StationDataComponent>(comp.Station, out var data))
|
||||
{
|
||||
foreach (var member in data.Grids)
|
||||
{
|
||||
while (shuttleQuery.MoveNext(out var shuttleUid, out var shuttle, out var shuttleXform))
|
||||
{
|
||||
if (shuttleXform.MapUid != uid || HasComp<FTLComponent>(shuttleUid))
|
||||
continue;
|
||||
|
||||
_shuttle.FTLTravel(shuttleUid, shuttle, member, ftlTime);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mining missions: NOOP
|
||||
|
||||
// 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-structures-remaining", ("count", structure.Structures.Count)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,11 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -27,19 +28,22 @@ namespace Content.Server.Salvage
|
||||
{
|
||||
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 IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = 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 PathfindingSystem _pathfinding = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly RadioSystem _radioSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly ShuttleConsoleSystem _shuttleConsoles = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
@@ -62,6 +66,9 @@ namespace Content.Server.Salvage
|
||||
|
||||
// Can't use RoundRestartCleanupEvent, I need to clean up before the grid, and components are gone to prevent the announcements
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd);
|
||||
|
||||
InitializeExpeditions();
|
||||
InitializeRunner();
|
||||
}
|
||||
|
||||
private void OnRoundEnd(GameRunLevelChangedEvent ev)
|
||||
@@ -449,6 +456,9 @@ namespace Content.Server.Salvage
|
||||
state.ActiveMagnets.Remove(magnet);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateExpeditions();
|
||||
UpdateRunner();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
372
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Normal file
372
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Normal file
@@ -0,0 +1,372 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.CPUJob.JobQueues;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Salvage.Expeditions;
|
||||
using Content.Server.Salvage.Expeditions.Structure;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IGameTiming _timing;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
private readonly BiomeSystem _biome;
|
||||
private readonly DungeonSystem _dungeon;
|
||||
private readonly SalvageSystem _salvage;
|
||||
|
||||
public readonly EntityUid Station;
|
||||
private readonly SalvageMissionParams _missionParams;
|
||||
|
||||
public SpawnSalvageMissionJob(
|
||||
double maxTime,
|
||||
IEntityManager entManager,
|
||||
IGameTiming timing,
|
||||
IMapManager mapManager,
|
||||
IPrototypeManager protoManager,
|
||||
ITileDefinitionManager tileDefManager,
|
||||
BiomeSystem biome,
|
||||
DungeonSystem dungeon,
|
||||
SalvageSystem salvage,
|
||||
EntityUid station,
|
||||
SalvageMissionParams missionParams,
|
||||
CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_timing = timing;
|
||||
_mapManager = mapManager;
|
||||
_prototypeManager = protoManager;
|
||||
_tileDefManager = tileDefManager;
|
||||
_biome = biome;
|
||||
_dungeon = dungeon;
|
||||
_salvage = salvage;
|
||||
Station = station;
|
||||
_missionParams = missionParams;
|
||||
}
|
||||
|
||||
protected override async Task<bool> Process()
|
||||
{
|
||||
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);
|
||||
MetaDataComponent? metadata = null;
|
||||
var grid = _entManager.EnsureComponent<MapGridComponent>(mapUid);
|
||||
var random = new Random(_missionParams.Seed);
|
||||
|
||||
// Setup mission configs
|
||||
// As we go through the config the rating will deplete so we'll go for most important to least important.
|
||||
|
||||
var mission = _entManager.System<SharedSalvageSystem>()
|
||||
.GetMission(_missionParams.MissionType, _missionParams.Difficulty, _missionParams.Seed);
|
||||
|
||||
var missionBiome = _prototypeManager.Index<SalvageBiomeMod>(mission.Biome);
|
||||
|
||||
if (missionBiome.BiomePrototype != null)
|
||||
{
|
||||
var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
biomeSystem.SetTemplate(biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
|
||||
biomeSystem.SetSeed(biome, mission.Seed);
|
||||
_entManager.Dirty(biome);
|
||||
|
||||
// Gravity
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
_entManager.Dirty(gravity, metadata);
|
||||
|
||||
// Atmos
|
||||
var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
|
||||
atmos.Space = false;
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
atmos.Mixture = new GasMixture(2500)
|
||||
{
|
||||
Temperature = 293.15f,
|
||||
Moles = moles,
|
||||
};
|
||||
|
||||
if (mission.Color != null)
|
||||
{
|
||||
var lighting = _entManager.EnsureComponent<MapLightComponent>(mapUid);
|
||||
lighting.AmbientLightColor = mission.Color.Value;
|
||||
_entManager.Dirty(lighting);
|
||||
}
|
||||
}
|
||||
|
||||
_mapManager.DoMapInitialize(mapId);
|
||||
_mapManager.SetMapPaused(mapId, true);
|
||||
|
||||
// Setup expedition
|
||||
var expedition = _entManager.AddComponent<SalvageExpeditionComponent>(mapUid);
|
||||
expedition.Station = Station;
|
||||
expedition.EndTime = _timing.CurTime + mission.Duration;
|
||||
expedition.MissionParams = _missionParams;
|
||||
|
||||
// Don't want consoles to have the incorrect name until refreshed.
|
||||
var ftlUid = _entManager.CreateEntityUninitialized("FTLPoint", new EntityCoordinates(mapUid, Vector2.Zero));
|
||||
_entManager.GetComponent<MetaDataComponent>(ftlUid).EntityName = SharedSalvageSystem.GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), _missionParams.Seed);
|
||||
_entManager.InitializeAndStartEntity(ftlUid);
|
||||
|
||||
var landingPadRadius = 24;
|
||||
var minDungeonOffset = landingPadRadius + 12;
|
||||
|
||||
var dungeonRotation = _dungeon.GetDungeonRotation(_missionParams.Seed);
|
||||
var dungeonSpawnRotation = new Angle(random.NextDouble() * Math.Tau);
|
||||
|
||||
// If the dungeon were to spawn facing the landing pad then bump the offset a bit
|
||||
// This isn't robust but fine for now.
|
||||
if (Math.Abs((dungeonRotation - dungeonSpawnRotation).Theta) < Math.PI / 2)
|
||||
{
|
||||
minDungeonOffset += 16;
|
||||
}
|
||||
|
||||
Dungeon dungeon = default!;
|
||||
|
||||
if (config != SalvageMissionType.Mining)
|
||||
{
|
||||
var maxDungeonOffset = minDungeonOffset + 24;
|
||||
var dungeonOffsetDistance = minDungeonOffset + (maxDungeonOffset - minDungeonOffset) * random.NextFloat();
|
||||
var dungeonOffset = new Vector2(dungeonOffsetDistance, 0f);
|
||||
dungeonOffset = dungeonSpawnRotation.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;
|
||||
}
|
||||
|
||||
List<Vector2i> reservedTiles = new();
|
||||
|
||||
// Setup the landing pad
|
||||
var landingPadExtents = new Vector2i(landingPadRadius, landingPadRadius);
|
||||
var tiles = new List<(Vector2i Indices, Tile Tile)>(landingPadExtents.X * landingPadExtents.Y * 2);
|
||||
|
||||
// Set the tiles themselves
|
||||
var landingTile = new Tile(_tileDefManager["FloorSteel"].TileId);
|
||||
|
||||
foreach (var tile in grid.GetTilesIntersecting(new Circle(Vector2.Zero, landingPadRadius), false))
|
||||
{
|
||||
if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
|
||||
continue;
|
||||
|
||||
tiles.Add((tile.GridIndices, landingTile));
|
||||
reservedTiles.Add(tile.GridIndices);
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
|
||||
// Mission setup
|
||||
switch (config)
|
||||
{
|
||||
case SalvageMissionType.Mining:
|
||||
await SetupMining(mission, mapUid);
|
||||
break;
|
||||
case SalvageMissionType.Destruction:
|
||||
await SetupStructure(mission, dungeon, mapUid, grid, random);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Handle loot
|
||||
foreach (var (loot, count) in mission.Loot)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var lootProto = _prototypeManager.Index<SalvageLootPrototype>(loot);
|
||||
await SpawnDungeonLoot(dungeon, lootProto, mapUid, grid, random, reservedTiles);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task SpawnDungeonLoot(Dungeon? dungeon, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
|
||||
{
|
||||
for (var i = 0; i < loot.LootRules.Count; i++)
|
||||
{
|
||||
var rule = loot.LootRules[i];
|
||||
|
||||
switch (rule)
|
||||
{
|
||||
case BiomeTemplateLoot biomeLoot:
|
||||
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
|
||||
{
|
||||
_biome.AddTemplate(biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
|
||||
}
|
||||
break;
|
||||
// Spawns a cluster (like an ore vein) nearby.
|
||||
case DungeonClusterLoot clusterLoot:
|
||||
await SpawnDungeonClusterLoot(dungeon!, clusterLoot, grid, random, reservedTiles);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Loot
|
||||
|
||||
private async Task SpawnDungeonClusterLoot(
|
||||
Dungeon dungeon,
|
||||
DungeonClusterLoot loot,
|
||||
MapGridComponent grid,
|
||||
Random random,
|
||||
List<Vector2i> reservedTiles)
|
||||
{
|
||||
var spawnTiles = new HashSet<Vector2i>();
|
||||
|
||||
for (var i = 0; i < loot.Points; i++)
|
||||
{
|
||||
var room = dungeon.Rooms[random.Next(dungeon.Rooms.Count)];
|
||||
var clusterAmount = loot.ClusterAmount;
|
||||
var spots = room.Tiles.ToList();
|
||||
random.Shuffle(spots);
|
||||
|
||||
foreach (var spot in spots)
|
||||
{
|
||||
if (reservedTiles.Contains(spot))
|
||||
continue;
|
||||
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(spot);
|
||||
|
||||
if (anchored.MoveNext(out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
clusterAmount--;
|
||||
spawnTiles.Add(spot);
|
||||
|
||||
if (clusterAmount == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tile in spawnTiles)
|
||||
{
|
||||
await SuspendIfOutOfTime();
|
||||
var proto = _prototypeManager.Index<WeightedRandomPrototype>(loot.Prototype).Pick(random);
|
||||
_entManager.SpawnEntity(proto, grid.GridTileToLocal(tile));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#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"];
|
||||
|
||||
// Spawn the objectives
|
||||
for (var i = 0; i < structureCount; i++)
|
||||
{
|
||||
var structureRoom = availableRooms[random.Next(availableRooms.Count)];
|
||||
var spawnTile = structureRoom.Tiles.ElementAt(random.Next(structureRoom.Tiles.Count));
|
||||
var uid = _entManager.SpawnEntity(shaggy, grid.GridTileToLocal(spawnTile));
|
||||
_entManager.AddComponent<SalvageStructureComponent>(uid);
|
||||
structureComp.Structures.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SpawnMobsRandomRooms(SalvageMission mission, Dungeon dungeon, SalvageFactionPrototype faction, MapGridComponent grid, Random random)
|
||||
{
|
||||
var groupSpawns = _salvage.GetSpawnCount(mission.Difficulty);
|
||||
var groupSum = faction.MobGroups.Sum(o => o.Prob);
|
||||
|
||||
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];
|
||||
var spawnTile = spawnRoom.Tiles.ElementAt(random.Next(spawnRoom.Tiles.Count));
|
||||
var spawnPosition = grid.GridTileToLocal(spawnTile);
|
||||
|
||||
foreach (var entry in EntitySpawnCollection.GetSpawns(mobGroup.Entries, random))
|
||||
{
|
||||
_entManager.SpawnEntity(entry, spawnPosition);
|
||||
}
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -6,4 +6,4 @@ namespace Content.Server.Shuttles.Events;
|
||||
/// Raised when <see cref="ShuttleSystem.FasterThanLight"/> has completed FTL Travel.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct FTLCompletedEvent;
|
||||
public readonly record struct FTLCompletedEvent(EntityUid Entity, EntityUid MapUid);
|
||||
|
||||
7
Content.Server/Shuttles/Events/FTLRequestEvent.cs
Normal file
7
Content.Server/Shuttles/Events/FTLRequestEvent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Shuttles.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised by a shuttle when it has requested an FTL.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct FTLRequestEvent(EntityUid MapUid);
|
||||
@@ -6,4 +6,4 @@ namespace Content.Server.Shuttles.Events;
|
||||
/// Raised when a shuttle has moved to FTL space.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct FTLStartedEvent(EntityUid? FromMapUid, Matrix3 FTLFrom, Angle FromRotation);
|
||||
public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3 FTLFrom, Angle FromRotation);
|
||||
|
||||
@@ -150,6 +150,8 @@ public sealed partial class ShuttleSystem
|
||||
hyperspace.Dock = false;
|
||||
hyperspace.PriorityTag = priorityTag;
|
||||
_console.RefreshShuttleConsoles();
|
||||
var ev = new FTLRequestEvent(_mapManager.GetMapEntityId(coordinates.ToMap(EntityManager, _transform).MapId));
|
||||
RaiseLocalEvent(shuttleUid, ref ev, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,8 +251,10 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
SetDockBolts(uid, true);
|
||||
_console.RefreshShuttleConsoles(uid);
|
||||
var ev = new FTLStartedEvent(fromMapUid, fromMatrix, fromRotation);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
var target = comp.TargetUid != null ? new EntityCoordinates(comp.TargetUid.Value, Vector2.Zero) : comp.TargetCoordinates;
|
||||
|
||||
var ev = new FTLStartedEvent(uid, target, fromMapUid, fromMatrix, fromRotation);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
if (comp.TravelSound != null)
|
||||
{
|
||||
@@ -344,7 +348,7 @@ public sealed partial class ShuttleSystem
|
||||
comp.Accumulator += FTLCooldown;
|
||||
_console.RefreshShuttleConsoles(uid);
|
||||
_mapManager.SetMapPaused(mapId, false);
|
||||
var ftlEvent = new FTLCompletedEvent();
|
||||
var ftlEvent = new FTLCompletedEvent(uid, _mapManager.GetMapEntityId(mapId));
|
||||
RaiseLocalEvent(uid, ref ftlEvent, true);
|
||||
break;
|
||||
case FTLState.Cooldown:
|
||||
@@ -499,6 +503,7 @@ public sealed partial class ShuttleSystem
|
||||
public bool TryFTLProximity(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref targetXform) ||
|
||||
targetXform.GridUid == null ||
|
||||
targetXform.MapUid == null ||
|
||||
!targetXform.MapUid.Value.IsValid() ||
|
||||
!Resolve(shuttleUid, ref xform))
|
||||
@@ -592,6 +597,23 @@ public sealed partial class ShuttleSystem
|
||||
spawnPos = _transform.GetWorldPosition(targetXform, xformQuery);
|
||||
}
|
||||
|
||||
// TODO: This is pretty crude for multiple landings.
|
||||
if (nearbyGrids.Count > 1 || !HasComp<MapComponent>(targetXform.GridUid.Value))
|
||||
{
|
||||
var minRadius = (MathF.Max(targetAABB.Width, targetAABB.Height) + MathF.Max(shuttleAABB.Width, shuttleAABB.Height)) / 2f;
|
||||
spawnPos = targetAABB.Center + _random.NextVector2(minRadius, minRadius + 64f);
|
||||
}
|
||||
else if (shuttleBody != null)
|
||||
{
|
||||
var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery);
|
||||
var transform = new Transform(targetPos, targetRot);
|
||||
spawnPos = Robust.Shared.Physics.Transform.Mul(transform, -shuttleBody.LocalCenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPos = _transform.GetWorldPosition(targetXform, xformQuery);
|
||||
}
|
||||
|
||||
xform.Coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos);
|
||||
|
||||
if (!HasComp<MapComponent>(targetXform.GridUid))
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
@@ -13,12 +17,22 @@ public sealed partial class BiomeComponent : Component
|
||||
[AutoNetworkedField]
|
||||
public int Seed;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite),
|
||||
DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer<BiomePrototype>))]
|
||||
/// <summary>
|
||||
/// The underlying entity, decal, and tile layers for the biome.
|
||||
/// </summary>
|
||||
[DataField("layers")]
|
||||
[AutoNetworkedField]
|
||||
public string BiomePrototype = "Grasslands";
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
|
||||
// TODO: Need to flag tiles as not requiring custom data anymore, e.g. if we spawn an ent and don't unspawn it.
|
||||
/// <summary>
|
||||
/// Templates to use for <see cref="Layers"/>. Optional as this can be set elsewhere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is really just here for prototype reload support.
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadWrite),
|
||||
DataField("template", customTypeSerializer: typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
|
||||
public string? Template;
|
||||
|
||||
/// <summary>
|
||||
/// If we've already generated a tile and couldn't deload it then we won't ever reload it in future.
|
||||
@@ -42,9 +56,16 @@ public sealed partial class BiomeComponent : Component
|
||||
[DataField("loadedChunks")]
|
||||
public readonly HashSet<Vector2i> LoadedChunks = new();
|
||||
|
||||
#region Markers
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently in the process of generating?
|
||||
/// Used to flag modified tiles without callers having to deal with it.
|
||||
/// Track what markers we've loaded already to avoid double-loading.
|
||||
/// </summary>
|
||||
public bool Generating = false;
|
||||
[DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<Vector2i>, BiomeMarkerLayerPrototype>))]
|
||||
public readonly Dictionary<string, HashSet<Vector2i>> LoadedMarkers = new();
|
||||
|
||||
[DataField("markerLayers", customTypeSerializer: typeof(PrototypeIdListSerializer<BiomeMarkerLayerPrototype>))]
|
||||
public List<string> MarkerLayers = new();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
[Prototype("biome")]
|
||||
public sealed class BiomePrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
[DataField("layers")]
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
}
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public interface IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Seed is used an offset from the relevant BiomeComponent's seed.
|
||||
/// </summary>
|
||||
FastNoiseLite Noise { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for this layer to be present. If set to 0 forces it for every tile.
|
||||
/// </summary>
|
||||
float Threshold { get; }
|
||||
}
|
||||
|
||||
public sealed class BiomeTileLayer : IBiomeLayer
|
||||
{
|
||||
[DataField("noise")] public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Which tile variants to use for this layer. Uses all of the tile's variants if none specified
|
||||
/// </summary>
|
||||
[DataField("variants")]
|
||||
public List<byte>? Variants = null;
|
||||
|
||||
[DataField("tile", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
||||
public string Tile = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles actual objects such as decals and entities.
|
||||
/// </summary>
|
||||
public interface IBiomeWorldLayer : IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// What tiles we're allowed to spawn on, real or biome.
|
||||
/// </summary>
|
||||
List<string> AllowedTiles { get; }
|
||||
}
|
||||
|
||||
public sealed class BiomeDecalLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Divide each tile up by this amount.
|
||||
/// </summary>
|
||||
[DataField("divisions")]
|
||||
public float Divisions = 1f;
|
||||
|
||||
[DataField("noise")]
|
||||
public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.8f;
|
||||
|
||||
[DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
|
||||
public List<string> Decals = new();
|
||||
}
|
||||
|
||||
public sealed class BiomeEntityLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
[DataField("noise")] public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
[DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> Entities = new();
|
||||
}
|
||||
17
Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs
Normal file
17
Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
/// <summary>
|
||||
/// A preset group of biome layers to be used for a <see cref="BiomeComponent"/>
|
||||
/// </summary>
|
||||
[Prototype("biomeTemplate")]
|
||||
public sealed class BiomeTemplatePrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("layers")]
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
}
|
||||
34
Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs
Normal file
34
Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BiomeDecalLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Divide each tile up by this amount.
|
||||
/// </summary>
|
||||
[DataField("divisions")]
|
||||
public float Divisions = 1f;
|
||||
|
||||
[DataField("noise")]
|
||||
public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.8f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")] public bool Invert { get; } = false;
|
||||
|
||||
[DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
|
||||
public List<string> Decals = new();
|
||||
}
|
||||
18
Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs
Normal file
18
Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
/// <summary>
|
||||
/// Dummy layer that specifies a marker to be replaced by external code.
|
||||
/// For example if they wish to add their own layers at specific points across different templates.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BiomeDummyLayer : IBiomeLayer
|
||||
{
|
||||
[DataField("id", required: true)] public string ID = string.Empty;
|
||||
|
||||
public FastNoiseLite Noise { get; } = new();
|
||||
public float Threshold { get; }
|
||||
public bool Invert { get; }
|
||||
}
|
||||
27
Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs
Normal file
27
Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BiomeEntityLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
[DataField("noise")] public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")] public bool Invert { get; } = false;
|
||||
|
||||
[DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> Entities = new();
|
||||
}
|
||||
28
Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs
Normal file
28
Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BiomeTileLayer : IBiomeLayer
|
||||
{
|
||||
[DataField("noise")] public FastNoiseLite Noise { get; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")] public bool Invert { get; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Which tile variants to use for this layer. Uses all of the tile's variants if none specified
|
||||
/// </summary>
|
||||
[DataField("variants")]
|
||||
public List<byte>? Variants = null;
|
||||
|
||||
[DataField("tile", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
||||
public string Tile = string.Empty;
|
||||
}
|
||||
22
Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs
Normal file
22
Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Noise;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public interface IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Seed is used an offset from the relevant BiomeComponent's seed.
|
||||
/// </summary>
|
||||
FastNoiseLite Noise { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for this layer to be present. If set to 0 forces it for every tile.
|
||||
/// </summary>
|
||||
float Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the thresold inverted so we need to be lower than it.
|
||||
/// </summary>
|
||||
public bool Invert { get; }
|
||||
}
|
||||
12
Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs
Normal file
12
Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
/// <summary>
|
||||
/// Handles actual objects such as decals and entities.
|
||||
/// </summary>
|
||||
public interface IBiomeWorldLayer : IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// What tiles we're allowed to spawn on, real or biome.
|
||||
/// </summary>
|
||||
List<string> AllowedTiles { get; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Parallax.Biomes.Points;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Markers;
|
||||
|
||||
[Prototype("biomeMarkerLayer")]
|
||||
public sealed class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField("radius")]
|
||||
public float Radius { get; } = 12f;
|
||||
|
||||
/// <summary>
|
||||
/// How many mobs to spawn in one group.
|
||||
/// </summary>
|
||||
[DataField("groupCount")]
|
||||
public int GroupCount = 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField("size")]
|
||||
public int Size { get; } = 64;
|
||||
}
|
||||
22
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs
Normal file
22
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Points;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies one-off marker points to be used. This could be for dungeon markers, mob markers, etc.
|
||||
/// These are run outside of the tile / decal / entity layers.
|
||||
/// </summary>
|
||||
public interface IBiomeMarkerLayer : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum radius between 2 points
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How large the pre-generated points area is.
|
||||
/// </summary>
|
||||
[DataField("size")]
|
||||
public int Size { get; }
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.GameStates;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
@@ -72,7 +71,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, FastNoiseLite noise, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
|
||||
public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid.TryGetTileRef(indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
@@ -86,14 +85,13 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetBiomeTile(indices, ProtoManager.Index<BiomePrototype>(biome.BiomePrototype),
|
||||
biome.Noise, grid, out tile);
|
||||
return TryGetBiomeTile(indices, biome.Layers, biome.Noise, grid, out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the tile, real or otherwise, for the specified indices.
|
||||
/// </summary>
|
||||
public bool TryGetBiomeTile(Vector2i indices, BiomePrototype prototype, FastNoiseLite noise, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid?.TryGetTileRef(indices, out var tileRef) == true && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
@@ -103,16 +101,16 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
|
||||
var oldSeed = noise.GetSeed();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
var layer = layers[i];
|
||||
|
||||
if (layer is not BiomeTileLayer tileLayer)
|
||||
continue;
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
|
||||
if (TryGetTile(indices, noise, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
if (TryGetTile(indices, noise, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
{
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
@@ -127,9 +125,10 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
private bool TryGetTile(Vector2i indices, FastNoiseLite seed, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
private bool TryGetTile(Vector2i indices, FastNoiseLite seed, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
var found = seed.GetNoise(indices.X, indices.Y);
|
||||
found = invert ? found * -1 : found;
|
||||
|
||||
if (found < threshold)
|
||||
{
|
||||
@@ -159,10 +158,10 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Tries to get the relevant entity for this tile.
|
||||
/// </summary>
|
||||
protected bool TryGetEntity(Vector2i indices, BiomePrototype prototype, FastNoiseLite noise, MapGridComponent grid,
|
||||
protected bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, grid, out var tileRef))
|
||||
if (!TryGetBiomeTile(indices, layers, noise, grid, out var tileRef))
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
@@ -171,13 +170,15 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldSeed = noise.GetSeed();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
var layer = layers[i];
|
||||
|
||||
// Decals might block entity so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case BiomeDummyLayer:
|
||||
continue;
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
@@ -188,7 +189,9 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
}
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
var invert = layer.Invert;
|
||||
var value = noise.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
{
|
||||
@@ -215,10 +218,10 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Tries to get the relevant decals for this tile.
|
||||
/// </summary>
|
||||
public bool TryGetDecals(Vector2i indices, BiomePrototype prototype, FastNoiseLite noise, MapGridComponent grid,
|
||||
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, grid, out var tileRef))
|
||||
if (!TryGetBiomeTile(indices, layers, noise, grid, out var tileRef))
|
||||
{
|
||||
decals = null;
|
||||
return false;
|
||||
@@ -227,13 +230,15 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldSeed = noise.GetSeed();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
var layer = layers[i];
|
||||
|
||||
// Entities might block decal so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case BiomeDummyLayer:
|
||||
continue;
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
@@ -244,11 +249,15 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
}
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
var invert = layer.Invert;
|
||||
|
||||
// Check if the other layer should even render, if not then keep going.
|
||||
if (layer is not BiomeDecalLayer decalLayer)
|
||||
{
|
||||
if (noise.GetNoise(indices.X, indices.Y) < layer.Threshold)
|
||||
var value = noise.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
decals = null;
|
||||
@@ -264,6 +273,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
{
|
||||
var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
|
||||
var decalValue = noise.GetNoise(index.X, index.Y);
|
||||
decalValue = invert ? decalValue * -1 : decalValue;
|
||||
|
||||
if (decalValue < decalLayer.Threshold)
|
||||
continue;
|
||||
|
||||
@@ -2,6 +2,13 @@ namespace Content.Shared.Procedural;
|
||||
|
||||
public sealed class Dungeon
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting position used to generate the dungeon from.
|
||||
/// </summary>
|
||||
public Vector2i Position;
|
||||
|
||||
public Vector2i Center;
|
||||
|
||||
public List<DungeonRoom> Rooms = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
13
Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs
Normal file
13
Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a biome template layer for dungeon loot.
|
||||
/// </summary>
|
||||
public sealed class BiomeTemplateLoot : IDungeonLoot
|
||||
{
|
||||
[DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
|
||||
public string Prototype = string.Empty;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns loot at points in the specified rooms
|
||||
/// </summary>
|
||||
public sealed class ClusterLoot : IDungeonLoot
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum spawns in a cluster.
|
||||
/// </summary>
|
||||
[DataField("minCluster")]
|
||||
public int MinClusterAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum spawns in a cluster.
|
||||
/// </summary>
|
||||
[DataField("maxCluster")] public int MaxClusterAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Amount to spawn for the entire loot.
|
||||
/// </summary>
|
||||
[DataField("max")]
|
||||
public int Amount;
|
||||
|
||||
/// <summary>
|
||||
/// Number of points to spawn.
|
||||
/// </summary>
|
||||
[DataField("points")] public int Points;
|
||||
|
||||
[DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype { get; } = string.Empty;
|
||||
}
|
||||
24
Content.Shared/Procedural/Loot/DungeonClusterLoot.cs
Normal file
24
Content.Shared/Procedural/Loot/DungeonClusterLoot.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns loot at points in the specified area inside of a dungeon room.
|
||||
/// </summary>
|
||||
public sealed class DungeonClusterLoot : IDungeonLoot
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns in a cluster.
|
||||
/// </summary>
|
||||
[DataField("clusterAmount")]
|
||||
public int ClusterAmount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Number of clusters to spawn.
|
||||
/// </summary>
|
||||
[DataField("clusters")] public int Points = 1;
|
||||
|
||||
[DataField("lootTable", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<WeightedRandomPrototype>))]
|
||||
public string Prototype { get; } = string.Empty;
|
||||
}
|
||||
@@ -3,5 +3,4 @@ namespace Content.Shared.Procedural.Loot;
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public interface IDungeonLoot
|
||||
{
|
||||
string Prototype { get; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,6 +14,12 @@ public sealed class SalvageLootPrototype : IPrototype
|
||||
|
||||
[DataField("desc")] public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Mission types this loot is not allowed to spawn for
|
||||
/// </summary>
|
||||
[DataField("blacklist")]
|
||||
public List<SalvageMissionType> Blacklist = new();
|
||||
|
||||
/// <summary>
|
||||
/// All of the loot rules
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Content.Shared.Procedural.Rewards;
|
||||
|
||||
/// <summary>
|
||||
/// Payout to the station's bank account.
|
||||
/// </summary>
|
||||
public sealed class BankReward : ISalvageReward
|
||||
{
|
||||
[DataField("amount")]
|
||||
public int Amount = 0;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Shared.Procedural.Rewards;
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public interface ISalvageReward
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.Rewards;
|
||||
|
||||
/// <summary>
|
||||
/// Given after successful completion of a salvage mission.
|
||||
/// </summary>
|
||||
[Prototype("salvageReward")]
|
||||
public sealed class SalvageRewardPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = string.Empty;
|
||||
|
||||
[DataField("reward", required: true)] public ISalvageReward Reward = default!;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace Content.Shared.Salvage.Expeditions.Extraction;
|
||||
|
||||
public sealed class SalvageExtraction : ISalvageMission
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum weight to be used for a wave.
|
||||
/// </summary>
|
||||
[DataField("minWaveWeight")] public float MinWaveWeight = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between 2 waves. Roughly the end of one to the start of another.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("waveCooldown")]
|
||||
public TimeSpan WaveCooldown = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// How much weight accumulates per second while the expedition is active.
|
||||
/// </summary>
|
||||
[DataField("weightAccumulator")]
|
||||
public float WeightAccumulator = 0.1f;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Shared.Salvage.Expeditions;
|
||||
|
||||
|
||||
public interface IFactionExpeditionConfig
|
||||
{
|
||||
|
||||
}
|
||||
11
Content.Shared/Salvage/Expeditions/Modifiers/ISalvageMod.cs
Normal file
11
Content.Shared/Salvage/Expeditions/Modifiers/ISalvageMod.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
public interface ISalvageMod
|
||||
{
|
||||
/// <summary>
|
||||
/// Player-friendly version describing this modifier.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
float Cost { get; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Affects the biome to be used for salvage.
|
||||
/// </summary>
|
||||
[Prototype("salvageBiomeMod")]
|
||||
public sealed class SalvageBiomeMod : IPrototype, ISalvageMod
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")] public string Description { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Is weather allowed to apply to this biome.
|
||||
/// </summary>
|
||||
[DataField("weather")]
|
||||
public bool Weather = true;
|
||||
|
||||
[DataField("biome", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
|
||||
public string? BiomePrototype;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Procedural;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
[Prototype("salvageDungeonMod")]
|
||||
public sealed class SalvageDungeonMod : IPrototype, ISalvageMod
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")] public string Description { get; } = string.Empty;
|
||||
|
||||
[DataField("proto", customTypeSerializer:typeof(PrototypeIdSerializer<DungeonConfigPrototype>))]
|
||||
public string Proto = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Biomes this dungeon can occur in.
|
||||
/// </summary>
|
||||
[DataField("biomeMods", customTypeSerializer:typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
|
||||
public List<string>? BiomeMods;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
[Prototype("salvageLightMod")]
|
||||
public sealed class SalvageLightMod : IPrototype, ISalvageMod
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")] public string Description { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
[DataField("color", required: true)] public Color? Color;
|
||||
|
||||
/// <summary>
|
||||
/// Biomes that this color applies to.
|
||||
/// </summary>
|
||||
[DataField("biomes", customTypeSerializer: typeof(PrototypeIdListSerializer<SalvageBiomeMod>))]
|
||||
public List<string>? Biomes;
|
||||
}
|
||||
20
Content.Shared/Salvage/Expeditions/Modifiers/SalvageMod.cs
Normal file
20
Content.Shared/Salvage/Expeditions/Modifiers/SalvageMod.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Generic modifiers with no additional data
|
||||
/// </summary>
|
||||
[Prototype("salvageMod")]
|
||||
public sealed class SalvageMod : IPrototype, ISalvageMod
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")] public string Description { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
}
|
||||
@@ -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; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
[DataField("minDuration")]
|
||||
public int MinDuration = 600;
|
||||
|
||||
[DataField("maxDuration")]
|
||||
public int MaxDuration = 660;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
|
||||
[Prototype("salvageWeatherMod")]
|
||||
public sealed class SalvageWeatherMod : IPrototype, ISalvageMod
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("desc")] public string Description { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
[DataField("weather", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<WeatherPrototype>))]
|
||||
public string WeatherPrototype = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whitelist for biomes. If empty assumed any allowed.
|
||||
/// </summary>
|
||||
[DataField("biomes", customTypeSerializer:typeof(PrototypeIdListSerializer<BiomeTemplatePrototype>))]
|
||||
public List<string> Biomes = new();
|
||||
}
|
||||
@@ -1,19 +1,28 @@
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
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; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cost for difficulty modifiers.
|
||||
/// </summary>
|
||||
[DataField("cost")]
|
||||
public float Cost { get; } = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("groups", required: true)]
|
||||
public List<SalvageMobGroup> MobGroups = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Per expedition type data for this faction.
|
||||
/// Miscellaneous data for factions.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("configs", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<IFactionExpeditionConfig, SalvageExpeditionPrototype>))]
|
||||
public Dictionary<string, IFactionExpeditionConfig> Configs = new();
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("configs")]
|
||||
public Dictionary<string, string> Configs = new();
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace Content.Shared.Salvage.Expeditions.Structure;
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the specified number of structures to finish the expedition.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class SalvageStructure : ISalvageMission
|
||||
{
|
||||
[DataField("desc")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("minStructures")]
|
||||
public int MinStructures = 3;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("maxStructures")]
|
||||
public int MaxStructures = 5;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Salvage.Expeditions.Structure;
|
||||
|
||||
/// <summary>
|
||||
/// Per-faction config for Salvage Structure expeditions.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class SalvageStructureFaction : IFactionExpeditionConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity prototype of the structures to destroy.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("spawn", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Spawn = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How many groups of mobs to spawn.
|
||||
/// </summary>
|
||||
[DataField("groupCount")]
|
||||
public int Groups = 5;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
public interface ISalvageMission {}
|
||||
@@ -1,89 +0,0 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
using Content.Shared.Procedural.Rewards;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
[Prototype("salvageExpedition")]
|
||||
public sealed class SalvageExpeditionPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Naming scheme for the FTL marker.
|
||||
/// </summary>
|
||||
[DataField("nameProto", customTypeSerializer:typeof(PrototypeIdSerializer<DatasetPrototype>))]
|
||||
public string NameProto = "names_borer";
|
||||
|
||||
/// <summary>
|
||||
/// Biome to generate the dungeon.
|
||||
/// </summary>
|
||||
[DataField("biome", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<BiomePrototype>))]
|
||||
public string Biome = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Player-friendly description for the console.
|
||||
/// </summary>
|
||||
[DataField("desc")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
[DataField("difficultyRating")]
|
||||
public DifficultyRating DifficultyRating = DifficultyRating.Minor;
|
||||
|
||||
// TODO: Make these modifiers but also add difficulty modifiers.
|
||||
[DataField("light")]
|
||||
public Color Light = Color.Black;
|
||||
|
||||
[DataField("temperature")]
|
||||
public float Temperature = 293.15f;
|
||||
|
||||
[DataField("expedition", required: true)]
|
||||
public ISalvageMission Mission = default!;
|
||||
|
||||
[DataField("minDuration")]
|
||||
public TimeSpan MinDuration = TimeSpan.FromSeconds(9 * 60);
|
||||
|
||||
[DataField("maxDuration")]
|
||||
public TimeSpan MaxDuration = TimeSpan.FromSeconds(12 * 60);
|
||||
|
||||
/// <summary>
|
||||
/// Available factions for selection for this mission prototype.
|
||||
/// </summary>
|
||||
[DataField("factions", customTypeSerializer:typeof(PrototypeIdListSerializer<SalvageFactionPrototype>))]
|
||||
public List<string> Factions = new();
|
||||
|
||||
[DataField("dungeonConfig", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<DungeonConfigPrototype>))]
|
||||
public string DungeonConfigPrototype = string.Empty;
|
||||
|
||||
[DataField("reward", customTypeSerializer: typeof(PrototypeIdSerializer<WeightedRandomPrototype>))]
|
||||
public string Reward = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Possible loot prototypes available for this expedition.
|
||||
/// This spawns during the mission and is not tied to completion.
|
||||
/// </summary>
|
||||
[DataField("loot", customTypeSerializer: typeof(PrototypeIdListSerializer<WeightedRandomPrototype>))]
|
||||
public List<string> Loots = new();
|
||||
|
||||
[DataField("dungeonPosition")]
|
||||
public Vector2i DungeonPosition = new(80, -25);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DifficultyRating : byte
|
||||
{
|
||||
None,
|
||||
Minor,
|
||||
Moderate,
|
||||
Hazardous,
|
||||
Extreme,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
@@ -10,13 +11,15 @@ public sealed class SalvageExpeditionConsoleState : BoundUserInterfaceState
|
||||
{
|
||||
public TimeSpan NextOffer;
|
||||
public bool Claimed;
|
||||
public bool Cooldown;
|
||||
public ushort ActiveMission;
|
||||
public List<SalvageMission> Missions;
|
||||
public List<SalvageMissionParams> Missions;
|
||||
|
||||
public SalvageExpeditionConsoleState(TimeSpan nextOffer, bool claimed, ushort activeMission, List<SalvageMission> missions)
|
||||
public SalvageExpeditionConsoleState(TimeSpan nextOffer, bool claimed, bool cooldown, ushort activeMission, List<SalvageMissionParams> missions)
|
||||
{
|
||||
NextOffer = nextOffer;
|
||||
Claimed = claimed;
|
||||
Cooldown = cooldown;
|
||||
ActiveMission = activeMission;
|
||||
Missions = missions;
|
||||
}
|
||||
@@ -49,6 +52,12 @@ public sealed class SalvageExpeditionDataComponent : Component
|
||||
[ViewVariables]
|
||||
public bool Claimed => ActiveMission != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Are we actively cooling down from the last salvage mission.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("cooldown")]
|
||||
public bool Cooldown = false;
|
||||
|
||||
/// <summary>
|
||||
/// Nexy time salvage missions are offered.
|
||||
/// </summary>
|
||||
@@ -56,7 +65,7 @@ public sealed class SalvageExpeditionDataComponent : Component
|
||||
public TimeSpan NextOffer;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<ushort, SalvageMission> Missions = new();
|
||||
public readonly Dictionary<ushort, SalvageMissionParams> Missions = new();
|
||||
|
||||
[ViewVariables] public ushort ActiveMission;
|
||||
|
||||
@@ -64,24 +73,84 @@ public sealed class SalvageExpeditionDataComponent : Component
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SalvageMission
|
||||
public sealed record SalvageMissionParams
|
||||
{
|
||||
[ViewVariables]
|
||||
public ushort Index;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("config", required: true, customTypeSerializer:typeof(SalvageExpeditionPrototype))]
|
||||
public string Config = default!;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public SalvageMissionType MissionType;
|
||||
|
||||
[ViewVariables] public TimeSpan Duration;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public int Seed;
|
||||
|
||||
[ViewVariables] public int Seed;
|
||||
/// <summary>
|
||||
/// Base difficulty for this mission.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] public DifficultyRating Difficulty;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SalvageEnvironment : byte
|
||||
/// <summary>
|
||||
/// Created from <see cref="SalvageMissionParams"/>. Only needed for data the client also needs for mission
|
||||
/// display.
|
||||
/// </summary>
|
||||
public sealed record SalvageMission(
|
||||
int Seed,
|
||||
DifficultyRating Difficulty,
|
||||
string Dungeon,
|
||||
string Faction,
|
||||
SalvageMissionType Mission,
|
||||
string Biome,
|
||||
Color? Color,
|
||||
TimeSpan Duration,
|
||||
Dictionary<string, int> Loot,
|
||||
List<string> Modifiers)
|
||||
{
|
||||
Invalid = 0,
|
||||
Caves,
|
||||
/// <summary>
|
||||
/// Seed used for the mission.
|
||||
/// </summary>
|
||||
public readonly int Seed = Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Difficulty rating.
|
||||
/// </summary>
|
||||
public DifficultyRating Difficulty = Difficulty;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SalvageDungeonMod"/> to be used.
|
||||
/// </summary>
|
||||
public readonly string Dungeon = Dungeon;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SalvageFactionPrototype"/> to be used.
|
||||
/// </summary>
|
||||
public readonly string Faction = Faction;
|
||||
|
||||
/// <summary>
|
||||
/// Underlying mission params that generated this.
|
||||
/// </summary>
|
||||
public readonly SalvageMissionType Mission = Mission;
|
||||
|
||||
/// <summary>
|
||||
/// Biome to be used for the mission.
|
||||
/// </summary>
|
||||
public readonly string Biome = Biome;
|
||||
|
||||
/// <summary>
|
||||
/// Lighting color to be used (AKA outdoor lighting).
|
||||
/// </summary>
|
||||
public readonly Color? Color = Color;
|
||||
|
||||
/// <summary>
|
||||
/// Mission duration.
|
||||
/// </summary>
|
||||
public TimeSpan Duration = Duration;
|
||||
|
||||
public Dictionary<string, int> Loot = Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Modifiers (outside of the above) applied to the mission.
|
||||
/// </summary>
|
||||
public List<string> Modifiers = Modifiers;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,77 +1,235 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
using Content.Shared.Procedural.Rewards;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Salvage.Expeditions.Structure;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
public abstract class SharedSalvageSystem : EntitySystem
|
||||
{
|
||||
public static readonly TimeSpan MissionCooldown = TimeSpan.FromMinutes(5);
|
||||
public static readonly TimeSpan MissionFailedCooldown = TimeSpan.FromMinutes(10);
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public static float GetDifficultyModifier(DifficultyRating difficulty)
|
||||
public static readonly TimeSpan MissionCooldown = TimeSpan.FromMinutes(5);
|
||||
public static readonly TimeSpan MissionFailedCooldown = TimeSpan.FromMinutes(15);
|
||||
|
||||
#region Descriptions
|
||||
|
||||
public string GetMissionDescription(SalvageMission mission)
|
||||
{
|
||||
// These should reflect how many salvage staff are expected to be required for the mission.
|
||||
switch (difficulty)
|
||||
// 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));
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public float GetMiningTax(DifficultyRating baseRating)
|
||||
{
|
||||
return 0.6f + (int) baseRating * 0.05f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of structures to destroy.
|
||||
/// </summary>
|
||||
public int GetStructureCount(DifficultyRating baseRating)
|
||||
{
|
||||
return 1 + (int) baseRating * 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public int GetDifficulty(DifficultyRating rating)
|
||||
{
|
||||
switch (rating)
|
||||
{
|
||||
case DifficultyRating.None:
|
||||
return 1f;
|
||||
return 1;
|
||||
case DifficultyRating.Minor:
|
||||
return 1.5f;
|
||||
return 2;
|
||||
case DifficultyRating.Moderate:
|
||||
return 3f;
|
||||
return 4;
|
||||
case DifficultyRating.Hazardous:
|
||||
return 6f;
|
||||
return 6;
|
||||
case DifficultyRating.Extreme:
|
||||
return 10f;
|
||||
return 8;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null);
|
||||
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)
|
||||
{
|
||||
var random = new System.Random(seed);
|
||||
return $"{dataset.Values[random.Next(dataset.Values.Count)]}-{random.Next(10, 100)}-{(char) (65 + random.Next(26))}";
|
||||
}
|
||||
|
||||
public static string GetFaction(List<string> factions, int seed)
|
||||
public SalvageMission GetMission(SalvageMissionType config, DifficultyRating difficulty, int seed)
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 1);
|
||||
return factions[adjustedSeed.Next(factions.Count)];
|
||||
// This is on shared to ensure the client display for missions and what the server generates are consistent
|
||||
var rating = (float) GetDifficulty(difficulty);
|
||||
// Don't want easy missions to have any negative modifiers but also want
|
||||
// easy to be a 1 for difficulty.
|
||||
rating -= 1f;
|
||||
var rand = new System.Random(seed);
|
||||
var faction = GetMod<SalvageFactionPrototype>(rand, ref rating);
|
||||
var biome = GetMod<SalvageBiomeMod>(rand, ref rating);
|
||||
var dungeon = GetDungeon(biome.ID, rand, ref rating);
|
||||
var mods = new List<string>();
|
||||
|
||||
SalvageLightMod? light = null;
|
||||
|
||||
if (biome.BiomePrototype != null)
|
||||
{
|
||||
light = GetLight(biome.ID, rand, ref rating);
|
||||
mods.Add(light.Description);
|
||||
}
|
||||
|
||||
public static IEnumerable<SalvageLootPrototype> GetLoot(List<string> loots, int seed, IPrototypeManager protoManager)
|
||||
var time = GetMod<SalvageTimeMod>(rand, ref rating);
|
||||
// Round the duration to nearest 15 seconds.
|
||||
var exactDuration = time.MinDuration + (time.MaxDuration - time.MinDuration) * rand.NextFloat();
|
||||
exactDuration = MathF.Round(exactDuration / 15f) * 15f;
|
||||
var duration = TimeSpan.FromSeconds(exactDuration);
|
||||
|
||||
if (time.ID != "StandardTime")
|
||||
{
|
||||
mods.Add(time.Description);
|
||||
}
|
||||
|
||||
var loots = GetLoot(config, _proto.EnumeratePrototypes<SalvageLootPrototype>().ToList(), GetDifficulty(difficulty), seed);
|
||||
return new SalvageMission(seed, difficulty, dungeon.ID, faction.ID, config, biome.ID, light?.Color, duration, loots, mods);
|
||||
}
|
||||
|
||||
public SalvageDungeonMod GetDungeon(string biome, System.Random rand, ref float rating)
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<SalvageDungeonMod>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.BiomeMods?.Contains(biome) == false ||
|
||||
mod.Cost > rating)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rating -= (int) mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public SalvageLightMod GetLight(string biome, System.Random rand, ref float rating)
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<SalvageLightMod>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.Biomes?.Contains(biome) == false || mod.Cost > rating)
|
||||
continue;
|
||||
|
||||
rating -= mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public T GetMod<T>(System.Random rand, ref float rating) where T : class, IPrototype, ISalvageMod
|
||||
{
|
||||
var mods = _proto.EnumeratePrototypes<T>().ToList();
|
||||
mods.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||
rand.Shuffle(mods);
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.Cost > rating)
|
||||
continue;
|
||||
|
||||
rating -= mod.Cost;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetLoot(SalvageMissionType mission, List<SalvageLootPrototype> loots, int count, int seed)
|
||||
{
|
||||
var results = new Dictionary<string, int>();
|
||||
var adjustedSeed = new System.Random(seed + 2);
|
||||
|
||||
for (var i = 0; i < loots.Count; i++)
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var loot = loots[i];
|
||||
var a = protoManager.Index<WeightedRandomPrototype>(loot);
|
||||
var lootConfig = a.Pick(adjustedSeed);
|
||||
yield return protoManager.Index<SalvageLootPrototype>(lootConfig);
|
||||
}
|
||||
}
|
||||
adjustedSeed.Shuffle(loots);
|
||||
|
||||
public static ISalvageReward GetReward(WeightedRandomPrototype proto, int seed, IPrototypeManager protoManager)
|
||||
foreach (var loot in loots)
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 3);
|
||||
var rewardProto = proto.Pick(adjustedSeed);
|
||||
return protoManager.Index<SalvageRewardPrototype>(rewardProto).Reward;
|
||||
if (loot.Blacklist.Contains(mission))
|
||||
continue;
|
||||
|
||||
var weh = results.GetOrNew(loot.ID);
|
||||
weh++;
|
||||
results[loot.ID] = weh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#region Structure
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetStructureCount(SalvageStructure structure, int seed)
|
||||
[Serializable, NetSerializable]
|
||||
public enum SalvageMissionType : byte
|
||||
{
|
||||
var adjustedSeed = new System.Random(seed + 4);
|
||||
return adjustedSeed.Next(structure.MinStructures, structure.MaxStructures + 1);
|
||||
/// <summary>
|
||||
/// No dungeon, just ore loot and random mob spawns.
|
||||
/// </summary>
|
||||
Mining,
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the specified structures in a dungeon.
|
||||
/// </summary>
|
||||
Destruction,
|
||||
}
|
||||
|
||||
#endregion
|
||||
[Serializable, NetSerializable]
|
||||
public enum DifficultyRating : byte
|
||||
{
|
||||
None,
|
||||
Minor,
|
||||
Moderate,
|
||||
Hazardous,
|
||||
Extreme,
|
||||
}
|
||||
|
||||
6
Resources/Locale/en-US/procedural/biome.ftl
Normal file
6
Resources/Locale/en-US/procedural/biome.ftl
Normal file
@@ -0,0 +1,6 @@
|
||||
cmd-biome_clear-desc = Clears a biome entirely
|
||||
cmd-biome_clear-help = biome_clear <biomecomponent>
|
||||
cmd-biome_addlayer-desc = Adds another biome layer
|
||||
cmd-biome_addlayer-help = biome_addlayer <mapid> <biometemplate> [seed offset]
|
||||
cmd-biome_addmarkerlayer-desc = Adds another biome marker layer
|
||||
cmd-biome_addmarkerlayer-help = biome_addmarkerlayer <mapid> <biomemarkerlayer>
|
||||
34
Resources/Locale/en-US/procedural/expeditions.ftl
Normal file
34
Resources/Locale/en-US/procedural/expeditions.ftl
Normal file
@@ -0,0 +1,34 @@
|
||||
salvage-expedition-structure-examine = This is a [color=#B02E26]destruction[/color] objective
|
||||
|
||||
salvage-expedition-window-title = Salvage expeditions
|
||||
salvage-expedition-window-difficulty = Difficulty:
|
||||
salvage-expedition-window-details = Details:
|
||||
salvage-expedition-window-hostiles = Hostiles:
|
||||
salvage-expedition-window-duration = Duration:
|
||||
salvage-expedition-window-biome = Biome:
|
||||
salvage-expedition-window-modifiers = Modifiers:
|
||||
salvage-expedition-window-loot = Loot:
|
||||
salvage-expedition-window-none = N/A
|
||||
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 = Destroy {$count} {$structure} inside the area.
|
||||
|
||||
salvage-expedition-type-Mining = Mining
|
||||
salvage-expedition-type-Destruction = Destruction
|
||||
|
||||
salvage-expedition-difficulty-None = None
|
||||
salvage-expedition-difficulty-Minor = Minor
|
||||
salvage-expedition-difficulty-Moderate = Moderate
|
||||
salvage-expedition-difficulty-Hazardous = Hazardous
|
||||
salvage-expedition-difficulty-Extreme = Extreme
|
||||
|
||||
# Runner
|
||||
salvage-expedition-announcement-countdown-minutes = {$duration} minutes remaining to complete the expedition.
|
||||
salvage-expedition-announcement-countdown-seconds = {$duration} seconds remaining to complete the expedition.
|
||||
salvage-expedition-announcement-dungeon = Dungeon is located {$direction}.
|
||||
@@ -1089,12 +1089,6 @@ entities:
|
||||
- pos: 0.5,0.5
|
||||
parent: 0
|
||||
type: Transform
|
||||
- uid: 85
|
||||
type: PaperWrittenSalvageLoreMedium1PlasmaTrap
|
||||
components:
|
||||
- pos: 0.48327154,0.5698495
|
||||
parent: 0
|
||||
type: Transform
|
||||
- uid: 86
|
||||
type: ClothingEyesGlassesMeson
|
||||
components:
|
||||
|
||||
@@ -568,12 +568,6 @@ entities:
|
||||
- pos: -3.5,0.5
|
||||
parent: 0
|
||||
type: Transform
|
||||
- uid: 55
|
||||
type: SalvageLorePaperGamingSpawner
|
||||
components:
|
||||
- pos: -1.5,-2.5
|
||||
parent: 0
|
||||
type: Transform
|
||||
- uid: 56
|
||||
type: SalvageMobSpawner75
|
||||
components:
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# ---- SPECIFICS ----
|
||||
|
||||
- type: entity
|
||||
id: PaperWrittenSalvageLoreMedium1PlasmaTrap
|
||||
noSpawn: true # keep this from spamming spawn sheet
|
||||
suffix: "Salvage: Lore: Medium 1: Plasma Trap"
|
||||
parent: Paper
|
||||
components:
|
||||
- type: Paper
|
||||
content: book-text-plasma-trap
|
||||
# ---- GAMING ----
|
||||
|
||||
- type: entity
|
||||
name: Salvage Lore Paper Gaming Spawner
|
||||
id: SalvageLorePaperGamingSpawner
|
||||
parent: MarkerBase
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: red
|
||||
- sprite: Objects/Misc/bureaucracy.rsi
|
||||
state: paper_words
|
||||
- type: RandomSpawner
|
||||
prototypes:
|
||||
- PaperWrittenSalvageLoreGaming1
|
||||
- PaperWrittenSalvageLoreGaming2
|
||||
- PaperWrittenSalvageLoreGaming3
|
||||
- PaperWrittenSalvageLoreGaming4
|
||||
offset: 0.1
|
||||
|
||||
- type: entity
|
||||
id: PaperWrittenSalvageLoreGaming1
|
||||
noSpawn: true # keep this from spamming spawn sheet
|
||||
suffix: "Salvage: Lore: Gaming 1"
|
||||
parent: Paper
|
||||
components:
|
||||
- type: Paper
|
||||
content: book-text-gaming1
|
||||
|
||||
- type: entity
|
||||
id: PaperWrittenSalvageLoreGaming2
|
||||
noSpawn: true # keep this from spamming spawn sheet
|
||||
suffix: "Salvage: Lore: Gaming 2"
|
||||
parent: Paper
|
||||
components:
|
||||
- type: Paper
|
||||
content: book-text-gaming2
|
||||
|
||||
- type: entity
|
||||
id: PaperWrittenSalvageLoreGaming3
|
||||
noSpawn: true # keep this from spamming spawn sheet
|
||||
suffix: "Salvage: Lore: Gaming 3"
|
||||
parent: Paper
|
||||
components:
|
||||
- type: Paper
|
||||
content: book-text-gaming3
|
||||
|
||||
- type: entity
|
||||
id: PaperWrittenSalvageLoreGaming4
|
||||
noSpawn: true # keep this from spamming spawn sheet
|
||||
suffix: "Salvage: Lore: Gaming 4"
|
||||
parent: Paper
|
||||
components:
|
||||
- type: Paper
|
||||
content: book-text-gaming4
|
||||
# ----
|
||||
@@ -89,6 +89,17 @@
|
||||
- type: ComputerBoard
|
||||
prototype: ComputerCargoShuttle
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
id: SalvageExpeditionsComputerCircuitboard
|
||||
name: salvage expeditions computer board
|
||||
description: A computer printed circuit board for a salvage expeditions computer.
|
||||
components:
|
||||
- type: Sprite
|
||||
state: cpu_supply
|
||||
- type: ComputerBoard
|
||||
prototype: ComputerSalvageExpedition
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputerCircuitboard
|
||||
id: CargoShuttleConsoleCircuitboard
|
||||
|
||||
@@ -717,6 +717,49 @@
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: StrongMetallic
|
||||
|
||||
- type: entity
|
||||
id: ComputerSalvageExpedition
|
||||
parent: BaseComputer
|
||||
name: salvage expeditions computer
|
||||
description: Used take salvage missions.
|
||||
components:
|
||||
- type: Sprite
|
||||
layers:
|
||||
- map: ["computerLayerBody"]
|
||||
state: computer
|
||||
- map: ["computerLayerKeyboard"]
|
||||
state: generic_keyboard
|
||||
- map: [ "computerLayerScreen" ]
|
||||
state: mining
|
||||
- map: ["computerLayerKeys"]
|
||||
state: tech_key
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.ComputerVisuals.Powered:
|
||||
computerLayerScreen:
|
||||
True: { visible: true, shader: unshaded }
|
||||
False: { visible: false }
|
||||
computerLayerKeys:
|
||||
True: { visible: true, shader: unshaded }
|
||||
False: { visible: true }
|
||||
- type: SalvageExpeditionConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.SalvageConsoleUiKey.Expedition
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.SalvageConsoleUiKey.Expedition
|
||||
type: SalvageExpeditionConsoleBoundUserInterface
|
||||
- type: Computer
|
||||
board: SalvageExpeditionsComputerCircuitboard
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 1.6
|
||||
color: "#b89f25"
|
||||
- type: AccessReader
|
||||
access: [["Salvage"]]
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
id: ComputerSurveillanceCameraMonitor
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
- type: entity
|
||||
id: XenoWardingTower
|
||||
name: Xeno warding tower
|
||||
description: a
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
snap:
|
||||
@@ -16,6 +15,7 @@
|
||||
Heat:
|
||||
collection:
|
||||
MeatLaserImpact
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
14
Resources/Prototypes/Procedural/biome_markers.yml
Normal file
14
Resources/Prototypes/Procedural/biome_markers.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
- type: biomeMarkerLayer
|
||||
id: Lizards
|
||||
proto: MobLizard
|
||||
groupCount: 5
|
||||
|
||||
# TODO: Needs to be more robust
|
||||
- type: biomeMarkerLayer
|
||||
id: Xenos
|
||||
proto: MobXeno
|
||||
|
||||
|
||||
#- type: biomeMarkerLayer
|
||||
# id: Experiment
|
||||
# proto: DungeonMarkerExperiment
|
||||
108
Resources/Prototypes/Procedural/biome_ore_templates.yml
Normal file
108
Resources/Prototypes/Procedural/biome_ore_templates.yml
Normal file
@@ -0,0 +1,108 @@
|
||||
# Allowed
|
||||
#allowedTiles:
|
||||
#- FloorPlanetGrass
|
||||
#- FloorPlanetDirt
|
||||
#- FloorSnow
|
||||
#- FloorBasalt
|
||||
#- FloorAsteroidSand
|
||||
|
||||
- type: biomeTemplate
|
||||
id: OreTin
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.90
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
- FloorPlanetDirt
|
||||
- FloorSnow
|
||||
- FloorBasalt
|
||||
- FloorAsteroidSand
|
||||
noise:
|
||||
seed: 100
|
||||
noiseType: OpenSimplex2
|
||||
frequency: 0.04
|
||||
fractalType: None
|
||||
entities:
|
||||
- WallRockTin
|
||||
|
||||
# Medium value
|
||||
# Gold
|
||||
- type: biomeTemplate
|
||||
id: OreGold
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.95
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
- FloorPlanetDirt
|
||||
- FloorSnow
|
||||
- FloorBasalt
|
||||
- FloorAsteroidSand
|
||||
noise:
|
||||
seed: 100
|
||||
noiseType: OpenSimplex2
|
||||
frequency: 0.04
|
||||
fractalType: None
|
||||
entities:
|
||||
- WallRockGold
|
||||
|
||||
# Silver
|
||||
- type: biomeTemplate
|
||||
id: OreSilver
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.95
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
- FloorPlanetDirt
|
||||
- FloorSnow
|
||||
- FloorBasalt
|
||||
- FloorAsteroidSand
|
||||
noise:
|
||||
seed: 100
|
||||
noiseType: OpenSimplex2
|
||||
frequency: 0.05
|
||||
fractalType: None
|
||||
entities:
|
||||
- WallRockSilver
|
||||
|
||||
# High value
|
||||
# Plasma
|
||||
- type: biomeTemplate
|
||||
id: OrePlasma
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.99
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
- FloorPlanetDirt
|
||||
- FloorSnow
|
||||
- FloorBasalt
|
||||
- FloorAsteroidSand
|
||||
noise:
|
||||
seed: 100
|
||||
noiseType: OpenSimplex2
|
||||
frequency: 0.04
|
||||
fractalType: None
|
||||
entities:
|
||||
- WallRockPlasma
|
||||
|
||||
# Uranium
|
||||
- type: biomeTemplate
|
||||
id: OreUranium
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.99
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
- FloorPlanetDirt
|
||||
- FloorSnow
|
||||
- FloorBasalt
|
||||
- FloorAsteroidSand
|
||||
noise:
|
||||
seed: 100
|
||||
noiseType: OpenSimplex2
|
||||
frequency: 0.04
|
||||
fractalType: None
|
||||
entities:
|
||||
- WallRockUranium
|
||||
@@ -1,8 +1,7 @@
|
||||
# Desert
|
||||
# TODO: Water in desert
|
||||
- type: biome
|
||||
- type: biomeTemplate
|
||||
id: LowDesert
|
||||
desc: Desert
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.95
|
||||
@@ -33,6 +32,8 @@
|
||||
- FloorLowDesert
|
||||
entities:
|
||||
- AsteroidRock
|
||||
- !type:BiomeDummyLayer
|
||||
id: Loot
|
||||
# Fill layer
|
||||
- !type:BiomeTileLayer
|
||||
threshold: -1
|
||||
@@ -48,9 +49,8 @@
|
||||
frequency: 0.1
|
||||
|
||||
# Grass
|
||||
- type: biome
|
||||
- type: biomeTemplate
|
||||
id: Grasslands
|
||||
desc: Grasslands
|
||||
layers:
|
||||
# Sparse vegetation
|
||||
- !type:BiomeDecalLayer
|
||||
@@ -142,6 +142,8 @@
|
||||
cellularReturnType: Distance2
|
||||
entities:
|
||||
- WallRock
|
||||
- !type:BiomeDummyLayer
|
||||
id: Loot
|
||||
# Water
|
||||
- !type:BiomeEntityLayer
|
||||
allowedTiles:
|
||||
@@ -195,9 +197,8 @@
|
||||
cellularReturnType: Distance2
|
||||
|
||||
# Lava
|
||||
- type: biome
|
||||
- type: biomeTemplate
|
||||
id: Lava
|
||||
desc: Lava
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.9
|
||||
@@ -255,6 +256,8 @@
|
||||
- FloorBasalt
|
||||
entities:
|
||||
- FloorLavaEntity
|
||||
- !type:BiomeDummyLayer
|
||||
id: Loot
|
||||
# Fill basalt
|
||||
- !type:BiomeTileLayer
|
||||
threshold: -1
|
||||
@@ -263,7 +266,7 @@
|
||||
tile: FloorBasalt
|
||||
|
||||
# Snow
|
||||
- type: biome
|
||||
- type: biomeTemplate
|
||||
id: Snow # Similar to Grasslands... but snow
|
||||
layers:
|
||||
# Sparse vegetation
|
||||
@@ -353,6 +356,8 @@
|
||||
- FloraTreeSnow04
|
||||
- FloraTreeSnow05
|
||||
- FloraTreeSnow06
|
||||
- !type:BiomeDummyLayer
|
||||
id: Loot
|
||||
- !type:BiomeTileLayer
|
||||
threshold: -1.0
|
||||
tile: FloorSnow
|
||||
@@ -363,3 +368,27 @@
|
||||
seed: 0
|
||||
frequency: 0.02
|
||||
fractalType: None
|
||||
|
||||
# Caves
|
||||
- type: biomeTemplate
|
||||
id: Caves
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: -0.5
|
||||
invert: true
|
||||
noise:
|
||||
seed: 0
|
||||
noiseType: Perlin
|
||||
fractalType: Ridged
|
||||
octaves: 1
|
||||
frequency: 0.1
|
||||
gain: 0
|
||||
allowedTiles:
|
||||
- FloorAsteroidSand
|
||||
entities:
|
||||
- WallRock
|
||||
- !type:BiomeDummyLayer
|
||||
id: Loot
|
||||
- !type:BiomeTileLayer
|
||||
threshold: -1.0
|
||||
tile: FloorAsteroidSand
|
||||
16
Resources/Prototypes/Procedural/salvage_factions.yml
Normal file
16
Resources/Prototypes/Procedural/salvage_factions.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
- type: salvageFaction
|
||||
id: Xenos
|
||||
groups:
|
||||
- entries:
|
||||
- id: MobXeno
|
||||
amount: 2
|
||||
maxAmount: 3
|
||||
- id: MobXenoDrone
|
||||
amount: 1
|
||||
- entries:
|
||||
- id: MobXenoRavager
|
||||
amount: 1
|
||||
prob: 0.1
|
||||
configs:
|
||||
DefenseStructure: XenoWardingTower
|
||||
Mining: Xenos
|
||||
94
Resources/Prototypes/Procedural/salvage_loot.yml
Normal file
94
Resources/Prototypes/Procedural/salvage_loot.yml
Normal file
@@ -0,0 +1,94 @@
|
||||
# Loot tables
|
||||
#- type: weightedRandom
|
||||
# id: SalvageLowValue
|
||||
# weights:
|
||||
# Common
|
||||
# CrateSalvageAssortedGoodies: 1.0
|
||||
# Uncommon
|
||||
# TODO:
|
||||
# Rare
|
||||
|
||||
- type: weightedRandom
|
||||
id: SalvageHighValue
|
||||
weights:
|
||||
# Common
|
||||
CrateMaterialPlasteel: 1.0
|
||||
CrateMaterialWood: 1.0
|
||||
CrateMaterialPlastic: 1.0
|
||||
CrateSalvageEquipment: 1.0
|
||||
CrateMaterialSteel: 1.0
|
||||
CrateMaterialGlass: 1.0
|
||||
# Uncommon
|
||||
SuperCapacitorStockPart: 0.25
|
||||
PhasicScanningModuleStockPart: 0.25
|
||||
PicoManipulatorStockPart: 0.25
|
||||
UltraHighPowerMicroLaserStockPart: 0.25
|
||||
SuperMatterBinStockPart: 0.25
|
||||
# Rare
|
||||
QuadraticCapacitorStockPart: 0.10
|
||||
TriphasicScanningModuleStockPart: 0.10
|
||||
FemtoManipulatorStockPart: 0.10
|
||||
QuadUltraMicroLaserStockPart: 0.10
|
||||
BluespaceMatterBinStockPart: 0.10
|
||||
|
||||
# Crates
|
||||
#- type: salvageLoot
|
||||
# id: LowValue
|
||||
# desc: Commodities
|
||||
# blacklist:
|
||||
# - Mining
|
||||
# loots:
|
||||
# - !type:DungeonClusterLoot
|
||||
# lootTable: SalvageLowValue
|
||||
# clusters: 3
|
||||
# clusterAmount: 3
|
||||
|
||||
- type: salvageLoot
|
||||
id: HighValue
|
||||
desc: High-value commodities
|
||||
blacklist:
|
||||
- Mining
|
||||
loots:
|
||||
- !type:DungeonClusterLoot
|
||||
lootTable: SalvageHighValue
|
||||
clusters: 5
|
||||
clusterAmount: 1
|
||||
|
||||
# Ores
|
||||
# - Low value
|
||||
- type: salvageLoot
|
||||
id: OreTin
|
||||
desc: Veins of steel
|
||||
loots:
|
||||
- !type:BiomeTemplateLoot
|
||||
proto: OreTin
|
||||
|
||||
# - Medium value
|
||||
- type: salvageLoot
|
||||
id: OreGold
|
||||
desc: Veins of gold ore
|
||||
loots:
|
||||
- !type:BiomeTemplateLoot
|
||||
proto: OreGold
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreSilver
|
||||
desc: Veins of silver ore
|
||||
loots:
|
||||
- !type:BiomeTemplateLoot
|
||||
proto: OreSilver
|
||||
|
||||
# - High value
|
||||
- type: salvageLoot
|
||||
id: OrePlasma
|
||||
desc: Veins of plasma ore
|
||||
loots:
|
||||
- !type:BiomeTemplateLoot
|
||||
proto: OrePlasma
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreUranium
|
||||
desc: Veins of uranium ore
|
||||
loots:
|
||||
- !type:BiomeTemplateLoot
|
||||
proto: OreUranium
|
||||
0
Resources/Prototypes/Procedural/salvage_misc.yml
Normal file
0
Resources/Prototypes/Procedural/salvage_misc.yml
Normal file
102
Resources/Prototypes/Procedural/salvage_mods.yml
Normal file
102
Resources/Prototypes/Procedural/salvage_mods.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
# Markers
|
||||
- type: entity
|
||||
id: SalvageShuttleMarker
|
||||
parent: FTLPoint
|
||||
|
||||
# Biome mods -> at least 1 required
|
||||
- type: salvageBiomeMod
|
||||
id: Grasslands
|
||||
biome: Grasslands
|
||||
|
||||
- type: salvageBiomeMod
|
||||
id: Lava
|
||||
cost: 2
|
||||
biome: Lava
|
||||
|
||||
- type: salvageBiomeMod
|
||||
id: Snow
|
||||
biome: Snow
|
||||
|
||||
- type: salvageBiomeMod
|
||||
id: Caves
|
||||
cost: 1
|
||||
biome: Caves
|
||||
|
||||
#- type: salvageBiomeMod
|
||||
# id: Space
|
||||
# cost: 1
|
||||
# weather: false
|
||||
# biome: null
|
||||
|
||||
# Temperature mods -> not required
|
||||
# Also whitelist it
|
||||
|
||||
# Weather mods -> not required
|
||||
- type: salvageWeatherMod
|
||||
id: SnowfallHeavy
|
||||
weather: SnowfallHeavy
|
||||
cost: 1
|
||||
|
||||
- type: salvageWeatherMod
|
||||
id: Rain
|
||||
weather: Rain
|
||||
|
||||
# Light mods -> required
|
||||
# At some stage with sub-biomes this will probably be moved onto the biome itself
|
||||
- type: salvageLightMod
|
||||
id: Daylight
|
||||
desc: Daylight
|
||||
color: "#D8B059"
|
||||
biomes:
|
||||
- Grasslands
|
||||
|
||||
- type: salvageLightMod
|
||||
id: Lavalight
|
||||
desc: Daylight
|
||||
color: "#A34931"
|
||||
biomes:
|
||||
- Lava
|
||||
|
||||
- type: salvageLightMod
|
||||
id: Evening
|
||||
desc: Evening
|
||||
color: "#2b3143"
|
||||
|
||||
- type: salvageLightMod
|
||||
id: Night
|
||||
desc: Night time
|
||||
color: null
|
||||
cost: 1
|
||||
|
||||
# Time mods -> at least 1 required
|
||||
- type: salvageTimeMod
|
||||
id: StandardTime
|
||||
|
||||
- type: salvageTimeMod
|
||||
id: RushTime
|
||||
desc: Rush
|
||||
minDuration: 480
|
||||
maxDuration: 540
|
||||
cost: 1
|
||||
|
||||
# Misc mods
|
||||
- type: salvageMod
|
||||
id: LongDistance
|
||||
desc: Long distance
|
||||
|
||||
# Dungeons
|
||||
# For now just simple 1-dungeon setups
|
||||
- type: salvageDungeonMod
|
||||
id: Experiment
|
||||
proto: Experiment
|
||||
biomeMods:
|
||||
- Caves
|
||||
#- LowDesert
|
||||
- Snow
|
||||
- Grasslands
|
||||
|
||||
- type: salvageDungeonMod
|
||||
id: LavaBrig
|
||||
proto: LavaBrig
|
||||
biomeMods:
|
||||
- Lava
|
||||
Reference in New Issue
Block a user