Files
crystall-punk-14/Content.Server/_CP14/Demiplane/CP14DemiplanSystem.Generation.cs
Ed 4aea4b853c Minor procedural fixes pack (#837)
* demiplanE

* Update EntityTest.cs

* Update DungeonSystem.Rooms.cs

* Update EntityTest.cs
2025-02-03 17:09:09 +03:00

395 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Linq;
using System.Threading;
using Content.Server._CP14.Demiplane.Components;
using Content.Server._CP14.Demiplane.Jobs;
using Content.Server.GameTicking;
using Content.Shared._CP14.Demiplane.Components;
using Content.Shared._CP14.Demiplane.Prototypes;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Robust.Shared.CPUJob.JobQueues;
using Robust.Shared.CPUJob.JobQueues.Queues;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.Map.Components;
namespace Content.Server._CP14.Demiplane;
public sealed partial class CP14DemiplaneSystem
{
private readonly JobQueue _expeditionQueue = new();
private readonly List<(CP14SpawnRandomDemiplaneJob Job, CancellationTokenSource CancelToken)> _expeditionJobs = new();
private const double JobMaxTime = 0.002;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
private void InitGeneration()
{
SubscribeLocalEvent<CP14DemiplaneGeneratorDataComponent, MapInitEvent>(GeneratorMapInit);
SubscribeLocalEvent<CP14DemiplaneGeneratorDataComponent, UseInHandEvent>(GeneratorUsedInHand);
SubscribeLocalEvent<CP14DemiplaneGeneratorDataComponent, GetVerbsEvent<ExamineVerb>>(OnVerbExamine);
}
private void OnVerbExamine(Entity<CP14DemiplaneGeneratorDataComponent> ent, ref GetVerbsEvent<ExamineVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
var markup = GetDemiplanExamine(ent.Comp);
_examine.AddDetailedExamineVerb(
args,
ent.Comp,
markup,
Loc.GetString("cp14-demiplan-examine"),
"/Textures/Interface/VerbIcons/dot.svg.192dpi.png"); //TODO custom icon
}
private FormattedMessage GetDemiplanExamine(CP14DemiplaneGeneratorDataComponent comp)
{
var msg = new FormattedMessage();
if (!_proto.TryIndex(comp.Location, out var indexedLocation))
return msg;
msg.AddMarkupOrThrow(
indexedLocation.Name is not null/* && _random.Prob(indexedLocation.ExamineProb)*/
? Loc.GetString("cp14-demiplane-examine-title", ("location", Loc.GetString(indexedLocation.Name)))
: Loc.GetString("cp14-demiplane-examine-title-unknown"));
List<LocId> modifierNames = new();
foreach (var modifier in comp.SelectedModifiers)
{
if (!_proto.TryIndex(modifier, out var indexedModifier))
continue;
//if (!_random.Prob(indexedModifier.ExamineProb)) //temp disable
// continue;
if (indexedModifier.Name is null)
continue;
if (modifierNames.Contains(indexedModifier.Name.Value))
continue;
modifierNames.Add(indexedModifier.Name.Value);
}
if (modifierNames.Count > 0)
{
msg.AddMarkupOrThrow("\n" + Loc.GetString("cp14-demiplane-examine-modifiers"));
foreach (var name in modifierNames)
{
msg.AddMarkupOrThrow("\n- " + Loc.GetString(name));
}
}
return msg;
}
private void UpdateGeneration(float frameTime)
{
_expeditionQueue.Process();
foreach (var (job, cancelToken) in _expeditionJobs.ToArray())
{
switch (job.Status)
{
case JobStatus.Finished:
_expeditionJobs.Remove((job, cancelToken));
break;
}
}
}
/// <summary>
/// Generates a new random demiplane based on the specified parameters
/// </summary>
public void SpawnRandomDemiplane(ProtoId<CP14DemiplaneLocationPrototype> location, List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers, out Entity<CP14DemiplaneComponent>? demiplane, out MapId mapId)
{
var mapUid = _mapSystem.CreateMap(out mapId, runMapInit: false);
var demiComp = EntityManager.EnsureComponent<CP14DemiplaneComponent>(mapUid);
demiplane = (mapUid, demiComp);
var cancelToken = new CancellationTokenSource();
var job = new CP14SpawnRandomDemiplaneJob(
JobMaxTime,
EntityManager,
_logManager,
_mapManager,
_proto,
_dungeon,
_metaData,
_mapSystem,
mapUid,
mapId,
location,
modifiers,
_random.Next(-10000, 10000),
cancelToken.Token);
_expeditionJobs.Add((job, cancelToken));
_expeditionQueue.EnqueueJob(job);
}
private void GeneratorUsedInHand(Entity<CP14DemiplaneGeneratorDataComponent> generator, ref UseInHandEvent args)
{
//block the opening of demiplanes after the end of a round
if (_gameTicker.RunLevel != GameRunLevel.InRound)
{
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open-end-round"), generator, args.User);
return;
}
//We cant open demiplane in another demiplane or if parent is not Map
if (HasComp<CP14DemiplaneComponent>(Transform(generator).MapUid) || !HasComp<MapGridComponent>(_transform.GetParentUid(args.User)))
{
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open", ("name", MetaData(generator).EntityName)), generator, args.User);
return;
}
if (generator.Comp.Location is null)
return;
//an attempt to open demiplanes can be intercepted by other systems that substitute a map instead of generating the planned demiplane.
Entity<CP14DemiplaneComponent>? demiplane = null;
var ev = new CP14DemiplaneGenerationCatchAttemptEvent();
RaiseLocalEvent(ev);
if (ev.Demiplane is null)
{
SpawnRandomDemiplane(generator.Comp.Location.Value, generator.Comp.SelectedModifiers, out demiplane, out var mapId);
}
else
{
demiplane = ev.Demiplane;
}
_statistic.TrackAdd(generator.Comp.Statistic, 1);
if (demiplane is null)
return;
//Admin log needed
EnsureComp<CP14DemiplaneDestroyWithoutStabilizationComponent>(demiplane.Value);
//Ура, щиткод и магические переменные!
var tempRift = EntityManager.Spawn("CP14DemiplaneTimedRadiusPassway");
var tempRift2 = EntityManager.Spawn("CP14DemiplanRiftCore");
_transform.SetCoordinates(tempRift, Transform(args.User).Coordinates);
_transform.SetCoordinates(tempRift2, Transform(args.User).Coordinates);
var connection = EnsureComp<CP14DemiplaneRiftComponent>(tempRift);
var connection2 = EnsureComp<CP14DemiplaneRiftComponent>(tempRift2);
AddDemiplaneRandomExitPoint(demiplane.Value, (tempRift, connection));
AddDemiplaneRandomExitPoint(demiplane.Value, (tempRift2, connection2));
#if !DEBUG
QueueDel(generator); //wtf its crash debug build!
#endif
}
private void GeneratorMapInit(Entity<CP14DemiplaneGeneratorDataComponent> generator, ref MapInitEvent args)
{
//Location generation
HashSet<CP14DemiplaneLocationPrototype> suitableConfigs = new();
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14DemiplaneLocationPrototype>())
{
suitableConfigs.Add(locationConfig);
}
CP14DemiplaneLocationPrototype? selectedConfig = null;
while (suitableConfigs.Count > 0)
{
var randomConfig = _random.Pick(suitableConfigs);
if (!generator.Comp.TiersContent.ContainsKey(randomConfig.Tier))
{
suitableConfigs.Remove(randomConfig);
continue;
}
if (!_random.Prob(generator.Comp.TiersContent[randomConfig.Tier]))
{
suitableConfigs.Remove(randomConfig);
continue;
}
selectedConfig = randomConfig;
break;
}
if (selectedConfig is null)
{
// We dont should be here
Log.Warning("Expedition mission generation failed: No suitable location configs.");
QueueDel(generator);
return;
}
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
if (passed)
{
foreach (var reqTag in modifier.RequiredTags)
{
if (!selectedConfig.Tags.Contains(reqTag))
{
passed = false;
break;
}
}
}
//Tier filter
if (passed)
{
var innerPassed = false;
foreach (var tier in modifier.Tiers)
{
if (generator.Comp.TiersContent.ContainsKey(tier))
{
innerPassed = true;
break;
}
}
if (!innerPassed)
{
passed = false;
}
}
// Tier weight filter
if (passed)
{
var maxProb = 0f;
foreach (var tier in modifier.Tiers)
{
if (generator.Comp.TiersContent.ContainsKey(tier))
maxProb = Math.Max(maxProb, generator.Comp.TiersContent[tier]);
}
if (!_random.Prob(maxProb))
{
passed = false;
}
}
//Random prob filter
if (passed)
{
if (!_random.Prob(modifier.GenerationProb))
{
passed = false;
}
}
if (passed)
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
}
//Limits calculation
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> limits = new();
foreach (var limit in generator.Comp.Limits)
{
limits.Add(limit.Key, limit.Value);
}
while (suitableModifiersWeights.Count > 0)
{
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
//Fill demiplane under limits
var passed = true;
foreach (var category in selectedModifier.Categories)
{
if (!limits.ContainsKey(category.Key))
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
if (limits[category.Key] - category.Value < 0)
{
suitableModifiersWeights.Remove(selectedModifier);
passed = false;
break;
}
}
if (!passed)
continue;
generator.Comp.SelectedModifiers.Add(selectedModifier);
foreach (var category in selectedModifier.Categories)
{
limits[category.Key] -= category.Value;
}
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!");
}
}
public sealed class CP14DemiplaneGenerationCatchAttemptEvent : EntityEventArgs
{
public bool Handled = false;
public Entity<CP14DemiplaneComponent>? Demiplane;
}