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:
Ed
2024-11-01 18:08:57 +03:00
committed by GitHub
parent 7124eb5335
commit 3b520ac69c
19 changed files with 564 additions and 235 deletions

View File

@@ -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"}!");
}
}
}

View File

@@ -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!");
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}