Demiplan modifiers (#526)
* simple modifiers * modifier tag filtering * Update CP14DemiplanSystem.Generation.cs * Update rocks.yml * move loot to modifier * move enemies to modificators * fix filtering * modifier rariry * reward and difficulty limits * rebalance and optimize calculation * Update test.yml * wizden PR copy * move all alchemy reagents to modifiers
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
@@ -20,44 +21,50 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
// Doesn't use dungeon data because layers and we don't need top-down support at the moment.
|
||||
|
||||
var emptyTiles = false;
|
||||
var replaceEntities = new Dictionary<Vector2i, EntityUid>();
|
||||
var availableTiles = new List<Vector2i>();
|
||||
var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
|
||||
|
||||
foreach (var node in dungeon.AllTiles)
|
||||
while (tiles.MoveNext(out var tileRef))
|
||||
{
|
||||
// Empty tile, skip if relevant.
|
||||
if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty))
|
||||
continue;
|
||||
var tile = tileRef.Value.GridIndices;
|
||||
|
||||
// Check if it's a valid spawn, if so then use it.
|
||||
var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, node);
|
||||
var found = false;
|
||||
|
||||
// We use existing entities as a mark to spawn in place
|
||||
// OR
|
||||
// We check for any existing entities to see if we can spawn there.
|
||||
while (enumerator.MoveNext(out var uid))
|
||||
//Tile mask filtering
|
||||
if (gen.TileMask is not null)
|
||||
{
|
||||
// We can't replace so just stop here.
|
||||
if (gen.Replacement == null)
|
||||
break;
|
||||
if (!gen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Value.Tile.TypeId]).ID))
|
||||
continue;
|
||||
|
||||
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
|
||||
|
||||
if (prototype?.ID == gen.Replacement)
|
||||
{
|
||||
replaceEntities[node] = uid.Value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
//If entity mask null - we ignore the tiles that have anything on them.
|
||||
if (gen.EntityMask is null && !_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
//Entity mask filtering
|
||||
if (gen.EntityMask is not null)
|
||||
{
|
||||
var found = false;
|
||||
var enumerator2 = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
|
||||
while (enumerator2.MoveNext(out var uid))
|
||||
{
|
||||
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
|
||||
|
||||
if (prototype?.ID is null)
|
||||
continue;
|
||||
|
||||
if (!gen.EntityMask.Contains(prototype.ID))
|
||||
continue;
|
||||
|
||||
replaceEntities[tile] = uid.Value;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add it to valid nodes.
|
||||
availableTiles.Add(node);
|
||||
availableTiles.Add(tile);
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
@@ -139,7 +146,7 @@ public sealed partial class DungeonJob
|
||||
|
||||
if (groupSize > 0)
|
||||
{
|
||||
_sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!");
|
||||
_sawmill.Warning($"Found remaining group size for ore veins of {gen.Entity.Id ?? "null"}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server._CP14.Demiplane.Components;
|
||||
using Content.Server._CP14.Demiplane.Jobs;
|
||||
@@ -42,7 +43,7 @@ public sealed partial class CP14DemiplaneSystem
|
||||
/// <summary>
|
||||
/// Generates a new random demiplane based on the specified parameters
|
||||
/// </summary>
|
||||
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, out Entity<CP14DemiplaneComponent> demiplan, out MapId mapId)
|
||||
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers, out Entity<CP14DemiplaneComponent> demiplan, out MapId mapId)
|
||||
{
|
||||
var mapUid = _mapSystem.CreateMap(out mapId, runMapInit: false);
|
||||
var demiComp = EntityManager.EnsureComponent<CP14DemiplaneComponent>(mapUid);
|
||||
@@ -61,6 +62,7 @@ public sealed partial class CP14DemiplaneSystem
|
||||
mapUid,
|
||||
mapId,
|
||||
location,
|
||||
modifiers,
|
||||
_random.Next(-10000, 10000),
|
||||
cancelToken.Token);
|
||||
|
||||
@@ -70,7 +72,7 @@ public sealed partial class CP14DemiplaneSystem
|
||||
|
||||
private void GeneratorUsedInHand(Entity<CP14DemiplaneGeneratorDataComponent> generator, ref UseInHandEvent args)
|
||||
{
|
||||
if (generator.Comp.LocationConfig is null)
|
||||
if (generator.Comp.Location is null)
|
||||
return;
|
||||
|
||||
//We cant open demiplan in another demiplan
|
||||
@@ -80,7 +82,7 @@ public sealed partial class CP14DemiplaneSystem
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnRandomDemiplane(generator.Comp.LocationConfig.Value, out var demiplane, out var mapId);
|
||||
SpawnRandomDemiplane(generator.Comp.Location.Value, generator.Comp.Modifiers, out var demiplane, out var mapId);
|
||||
|
||||
//Admin log needed
|
||||
//TEST
|
||||
@@ -116,12 +118,93 @@ public sealed partial class CP14DemiplaneSystem
|
||||
}
|
||||
|
||||
var selectedConfig = _random.Pick(suitableConfigs);
|
||||
generator.Comp.LocationConfig = selectedConfig;
|
||||
generator.Comp.Location = selectedConfig;
|
||||
|
||||
//Modifier generation
|
||||
Dictionary<CP14DemiplaneModifierPrototype, float> suitableModifiersWeights = new();
|
||||
foreach (var modifier in _proto.EnumeratePrototypes<CP14DemiplaneModifierPrototype>())
|
||||
{
|
||||
var passed = true;
|
||||
//Tag blacklist filter
|
||||
foreach (var configTag in selectedConfig.Tags)
|
||||
{
|
||||
if (modifier.BlacklistTags.Count != 0 && modifier.BlacklistTags.Contains(configTag))
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Tag required filter
|
||||
foreach (var reqTag in modifier.RequiredTags)
|
||||
{
|
||||
if (!selectedConfig.Tags.Contains(reqTag))
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (passed)
|
||||
{
|
||||
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
|
||||
}
|
||||
}
|
||||
|
||||
var difficulty = 0f;
|
||||
var reward = 0f;
|
||||
while (generator.Comp.Modifiers.Count < generator.Comp.MaxModifiers && suitableModifiersWeights.Count > 0)
|
||||
{
|
||||
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
|
||||
if (difficulty + selectedModifier.Difficulty > generator.Comp.DifficultyLimit)
|
||||
{
|
||||
suitableModifiersWeights.Remove(selectedModifier);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reward + selectedModifier.Reward > generator.Comp.RewardLimit)
|
||||
{
|
||||
suitableModifiersWeights.Remove(selectedModifier);
|
||||
continue;
|
||||
}
|
||||
|
||||
generator.Comp.Modifiers.Add(selectedModifier);
|
||||
reward += selectedModifier.Reward;
|
||||
difficulty += selectedModifier.Difficulty;
|
||||
|
||||
if (selectedModifier.Unique)
|
||||
suitableModifiersWeights.Remove(selectedModifier);
|
||||
}
|
||||
|
||||
//Scenario generation
|
||||
|
||||
//ETC generation
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Optimization moment: avoid re-indexing for weight selection
|
||||
/// </summary>
|
||||
private static CP14DemiplaneModifierPrototype ModifierPick(Dictionary<CP14DemiplaneModifierPrototype, float> weights, IRobustRandom random)
|
||||
{
|
||||
var picks = weights;
|
||||
var sum = picks.Values.Sum();
|
||||
var accumulated = 0f;
|
||||
|
||||
var rand = random.NextFloat() * sum;
|
||||
|
||||
foreach (var (key, weight) in picks)
|
||||
{
|
||||
accumulated += weight;
|
||||
|
||||
if (accumulated >= rand)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't happen
|
||||
throw new InvalidOperationException($"Invalid weighted pick in CP14DemiplanSystem.Generation!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,17 @@ namespace Content.Server._CP14.Demiplane.Components;
|
||||
public sealed partial class CP14DemiplaneGeneratorDataComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<CP14DemiplaneLocationPrototype>? LocationConfig;
|
||||
public ProtoId<CP14DemiplaneLocationPrototype>? Location;
|
||||
|
||||
//Generation settings
|
||||
[DataField]
|
||||
public List<ProtoId<CP14DemiplaneModifierPrototype>> Modifiers = new();
|
||||
|
||||
[DataField]
|
||||
public float DifficultyLimit = 1;
|
||||
|
||||
[DataField]
|
||||
public float RewardLimit = 1;
|
||||
|
||||
[DataField]
|
||||
public int MaxModifiers = 6;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
private readonly SharedMapSystem _map;
|
||||
|
||||
private readonly ProtoId<CP14DemiplaneLocationPrototype> _config;
|
||||
private readonly List<ProtoId<CP14DemiplaneModifierPrototype>> _modifiers;
|
||||
private readonly int _seed;
|
||||
|
||||
public readonly EntityUid DemiplaneMapUid;
|
||||
@@ -43,6 +44,7 @@ public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
EntityUid demiplaneMapUid,
|
||||
MapId demiplaneMapId,
|
||||
ProtoId<CP14DemiplaneLocationPrototype> config,
|
||||
List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers,
|
||||
int seed,
|
||||
CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
@@ -55,6 +57,7 @@ public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
DemiplaneMapUid = demiplaneMapUid;
|
||||
_demiplaneMapId = demiplaneMapId;
|
||||
_config = config;
|
||||
_modifiers = modifiers;
|
||||
_seed = seed;
|
||||
|
||||
_sawmill = logManager.GetSawmill("cp14_expedition_job");
|
||||
@@ -70,18 +73,33 @@ public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
_metaData.SetEntityName(DemiplaneMapUid, "TODO: MAP Expedition name generation");
|
||||
_metaData.SetEntityName(grid, "TODO: GRID Expedition name generation");
|
||||
|
||||
//Spawn island config
|
||||
//Setup demiplane config
|
||||
var expeditionConfig = _prototypeManager.Index(_config);
|
||||
var locationConfig = _prototypeManager.Index(expeditionConfig.LocationConfig);
|
||||
_dungeon.GenerateDungeon(locationConfig,
|
||||
var indexedLocation = _prototypeManager.Index(expeditionConfig.LocationConfig);
|
||||
|
||||
//Add map components
|
||||
_entManager.AddComponents(DemiplaneMapUid, expeditionConfig.Components);
|
||||
|
||||
//Apply modifiers
|
||||
foreach (var modifier in _modifiers)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(modifier, out var indexedModifier))
|
||||
continue;
|
||||
|
||||
indexedLocation.Layers.AddRange(indexedModifier.Layers);
|
||||
_entManager.AddComponents(DemiplaneMapUid, indexedModifier.Components);
|
||||
}
|
||||
|
||||
_mapManager.DoMapInitialize(_demiplaneMapId);
|
||||
_mapManager.SetMapPaused(_demiplaneMapId, false);
|
||||
|
||||
//Spawn modified config
|
||||
_dungeon.GenerateDungeon(indexedLocation,
|
||||
grid,
|
||||
grid,
|
||||
Vector2i.Zero,
|
||||
_seed); //Not async, because dont work with biomespawner boilerplate
|
||||
|
||||
//Add map components
|
||||
_entManager.AddComponents(DemiplaneMapUid, expeditionConfig.Components);
|
||||
|
||||
//Setup gravity
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(DemiplaneMapUid);
|
||||
gravity.Enabled = true;
|
||||
@@ -94,11 +112,6 @@ public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
_entManager.System<AtmosphereSystem>().SetMapAtmosphere(DemiplaneMapUid, false, mixture);
|
||||
|
||||
_mapManager.DoMapInitialize(_demiplaneMapId);
|
||||
_mapManager.SetMapPaused(_demiplaneMapId, false);
|
||||
|
||||
//Dungeon
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user