Merge branch 'master' into ed-04-08-2025-upstream-sync
This commit is contained in:
@@ -223,6 +223,12 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
case CP14RoomsDunGen cp14RoomsDunGen:
|
||||
await PostGen(cp14RoomsDunGen, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case CP14BiomeDunGen cp14BiomesDunGen:
|
||||
await PostGen(cp14BiomesDunGen, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case CP14ReserveGrid cp14ReserveGrid:
|
||||
await PostGen(cp14ReserveGrid, reservedTiles);
|
||||
break;
|
||||
//CP14 zone end
|
||||
case AutoCablingDunGen cabling:
|
||||
await PostGen(cabling, dungeons[^1], reservedTiles, random);
|
||||
|
||||
@@ -137,6 +137,23 @@ public sealed partial class ShuttleSystem
|
||||
var parallax = EnsureComp<ParallaxComponent>(mapUid);
|
||||
parallax.Parallax = ftlMap.Parallax;
|
||||
|
||||
//CP14 FTL map tweaks
|
||||
var mapLight = EnsureComp<MapLightComponent>(mapUid);
|
||||
mapLight.AmbientLightColor = ftlMap.AmbientColor;
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
|
||||
_atmos.SetMapAtmosphere(mapUid, false, mixture);
|
||||
|
||||
var gravity = EnsureComp<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
gravity.Inherent = true;
|
||||
//CP14 FTL map tweaks ends
|
||||
|
||||
return mapUid;
|
||||
}
|
||||
|
||||
@@ -465,7 +482,7 @@ public sealed partial class ShuttleSystem
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var body = _physicsQuery.GetComponent(uid);
|
||||
var comp = entity.Comp1;
|
||||
//DoTheDinosaur(xform); //CP14 without stunning
|
||||
DoTheDinosaur(xform);
|
||||
_dockSystem.SetDockBolts(entity, false);
|
||||
|
||||
_physics.SetLinearVelocity(uid, Vector2.Zero, body: body);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Buckle.Systems;
|
||||
using Content.Server.Parallax;
|
||||
@@ -63,6 +64,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
[Dependency] private readonly ThrusterSystem _thruster = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
|
||||
private EntityQuery<BuckleComponent> _buckleQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
@@ -104,7 +106,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
return;
|
||||
|
||||
EnsureComp<ShuttleComponent>(ev.EntityUid);
|
||||
EnsureComp<ImplicitRoofComponent>(ev.EntityUid);
|
||||
//EnsureComp<ImplicitRoofComponent>(ev.EntityUid); //CP14 - grids (ships) without roofs!
|
||||
}
|
||||
|
||||
private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args)
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane;
|
||||
|
||||
public sealed partial class CP14DemiplaneSystem
|
||||
{
|
||||
private void InitConnections()
|
||||
{
|
||||
SubscribeLocalEvent<CP14DemiplaneRiftComponent, MapInitEvent>(OnRiftInit);
|
||||
SubscribeLocalEvent<CP14DemiplaneRiftComponent, ComponentShutdown>(OnRiftShutdown);
|
||||
}
|
||||
|
||||
private void OnRiftInit(Entity<CP14DemiplaneRiftComponent> rift, ref MapInitEvent args)
|
||||
{
|
||||
var map = Transform(rift).MapUid;
|
||||
if (_demiplaneQuery.TryComp(map, out var demiplane)) // In demiplane
|
||||
{
|
||||
if (rift.Comp.TryAutoLinkToMap)
|
||||
rift.Comp.Demiplane = map.Value;
|
||||
|
||||
if (rift.Comp.ActiveTeleport)
|
||||
AddDemiplaneRandomEntryPoint((map.Value, demiplane), rift);
|
||||
}
|
||||
else if (rift.Comp.Demiplane is not null) //We out of demiplane
|
||||
{
|
||||
if (_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
|
||||
{
|
||||
if (rift.Comp.ActiveTeleport)
|
||||
AddDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRiftShutdown(Entity<CP14DemiplaneRiftComponent> rift, ref ComponentShutdown args)
|
||||
{
|
||||
if (rift.Comp.Demiplane is null)
|
||||
return;
|
||||
|
||||
if (!_demiplaneQuery.TryComp(rift.Comp.Demiplane, out var riftDemiplane))
|
||||
return;
|
||||
|
||||
RemoveDemiplaneRandomEntryPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
|
||||
RemoveDemiplaneRandomExitPoint((rift.Comp.Demiplane.Value, riftDemiplane), rift);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///Add a position in the real world where you can get out of this demiplane
|
||||
/// </summary>
|
||||
private void AddDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent> demiplane,
|
||||
Entity<CP14DemiplaneRiftComponent> exitPoint)
|
||||
{
|
||||
demiplane.Comp.ExitPoints.Add(exitPoint);
|
||||
exitPoint.Comp.Demiplane = demiplane;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removing the demiplane exit point, one of which the player can exit to
|
||||
/// </summary>
|
||||
private void RemoveDemiplaneRandomExitPoint(Entity<CP14DemiplaneComponent>? demiplane,
|
||||
EntityUid exitPoint)
|
||||
{
|
||||
if (!TryComp<CP14DemiplaneRiftComponent>(exitPoint, out var riftComp))
|
||||
return;
|
||||
|
||||
if (demiplane is not null && demiplane.Value.Comp.ExitPoints.Contains(exitPoint))
|
||||
{
|
||||
demiplane.Value.Comp.ExitPoints.Remove(exitPoint);
|
||||
riftComp.Demiplane = null;
|
||||
}
|
||||
|
||||
if (riftComp.DeleteAfterDisconnect && exitPoint.Valid)
|
||||
QueueDel(exitPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a position within the demiplane that can be entered into the demiplane
|
||||
/// </summary>
|
||||
private void AddDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent> demiplane,
|
||||
Entity<CP14DemiplaneRiftComponent> entryPoint)
|
||||
{
|
||||
demiplane.Comp.EntryPoints.Add(entryPoint);
|
||||
entryPoint.Comp.Demiplane = demiplane;
|
||||
}
|
||||
|
||||
private void RemoveDemiplaneRandomEntryPoint(Entity<CP14DemiplaneComponent>? demiplane,
|
||||
EntityUid entryPoint)
|
||||
{
|
||||
if (!TryComp<CP14DemiplaneRiftComponent>(entryPoint, out var riftComp))
|
||||
return;
|
||||
|
||||
if (demiplane is not null && demiplane.Value.Comp.EntryPoints.Contains(entryPoint))
|
||||
{
|
||||
demiplane.Value.Comp.EntryPoints.Remove(entryPoint);
|
||||
riftComp.Demiplane = null;
|
||||
}
|
||||
|
||||
if (riftComp.DeleteAfterDisconnect && entryPoint.Valid)
|
||||
QueueDel(entryPoint);
|
||||
}
|
||||
|
||||
public bool TryGetDemiplaneEntryPoint(Entity<CP14DemiplaneComponent> demiplane, out EntityUid? entryPoint)
|
||||
{
|
||||
entryPoint = null;
|
||||
|
||||
if (demiplane.Comp.EntryPoints.Count == 0)
|
||||
return false;
|
||||
|
||||
entryPoint = _random.Pick(demiplane.Comp.EntryPoints);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetDemiplaneExitPoint(Entity<CP14DemiplaneComponent> demiplane,
|
||||
out EntityUid? exitPoint)
|
||||
{
|
||||
exitPoint = null;
|
||||
|
||||
if (demiplane.Comp.ExitPoints.Count == 0)
|
||||
return false;
|
||||
|
||||
exitPoint = _random.Pick(demiplane.Comp.ExitPoints);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using Content.Server._CP14.WeatherControl;
|
||||
using Content.Server.Weather;
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane;
|
||||
|
||||
public sealed partial class CP14DemiplaneSystem
|
||||
{
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
private void InitDestruction()
|
||||
{
|
||||
SubscribeLocalEvent<CP14DemiplaneTimedDestructionComponent, ComponentAdd>(OnDestructionStarted);
|
||||
}
|
||||
|
||||
public void StartDestructDemiplane(Entity<CP14DemiplaneComponent> demiplane)
|
||||
{
|
||||
if (!TryComp<MapComponent>(demiplane, out var map))
|
||||
return;
|
||||
|
||||
if (HasComp<CP14DemiplaneTimedDestructionComponent>(demiplane))
|
||||
return;
|
||||
|
||||
EnsureComp<CP14DemiplaneTimedDestructionComponent>(demiplane);
|
||||
|
||||
if (HasComp<CP14WeatherControllerComponent>(demiplane))
|
||||
{
|
||||
RemCompDeferred<CP14WeatherControllerComponent>(demiplane);
|
||||
}
|
||||
|
||||
if (!_proto.TryIndex<WeatherPrototype>("CP14DemiplaneDestructionStorm", out var indexedWeather))
|
||||
return;
|
||||
|
||||
_weather.SetWeather(map.MapId, indexedWeather, null);
|
||||
}
|
||||
|
||||
private void OnDestructionStarted(Entity<CP14DemiplaneTimedDestructionComponent> ent, ref ComponentAdd args)
|
||||
{
|
||||
ent.Comp.EndTime = _timing.CurTime + ent.Comp.TimeToDestruction;
|
||||
ent.Comp.SelectedSong = new SoundPathSpecifier(_audio.GetSound(ent.Comp.Sound));
|
||||
}
|
||||
|
||||
private void UpdateDestruction(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneTimedDestructionComponent, CP14DemiplaneComponent>();
|
||||
while (query.MoveNext(out var uid, out var destruction, out var demiplane))
|
||||
{
|
||||
var remaining = destruction.EndTime - _timing.CurTime;
|
||||
|
||||
if (destruction.SelectedSong is null)
|
||||
continue;
|
||||
|
||||
var audioLength = _audio.GetAudioLength(destruction.SelectedSong.Path.ToString());
|
||||
|
||||
if (destruction.Stream is null && remaining < audioLength)
|
||||
{
|
||||
var audio = _audio.PlayPvs(destruction.Sound, uid);
|
||||
destruction.Stream = audio?.Entity;
|
||||
_audio.SetMapAudio(audio);
|
||||
Dirty(uid, destruction);
|
||||
DemiplaneAnnounce(uid, Loc.GetString("cp14-demiplane-countdown", ("duration", audioLength.Minutes)));
|
||||
}
|
||||
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
{
|
||||
_audio.Stop(destruction.Stream);
|
||||
DeleteDemiplane((uid, demiplane));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane;
|
||||
|
||||
public sealed partial class CP14DemiplaneSystem
|
||||
{
|
||||
private void InitEchoes()
|
||||
{
|
||||
SubscribeLocalEvent<EntitySpokeEvent>(OnSpeak);
|
||||
}
|
||||
|
||||
private void OnSpeak(EntitySpokeEvent ev)
|
||||
{
|
||||
var map = Transform(ev.Source).MapUid;
|
||||
|
||||
if (!_demiplaneQuery.TryComp(map, out var demiplane))
|
||||
return;
|
||||
|
||||
//Get random exit, and send message there
|
||||
if (demiplane.ExitPoints.Count == 0)
|
||||
return;
|
||||
var exit = _random.Pick(demiplane.ExitPoints);
|
||||
|
||||
_chat.TrySendInGameICMessage(exit,
|
||||
ev.Message,
|
||||
InGameICChatType.Whisper,
|
||||
ChatTransmitRange.Normal,
|
||||
true,
|
||||
checkRadioPrefix: false,
|
||||
nameOverride: Loc.GetString("cp14-demiplane-echoes"),
|
||||
ignoreActionBlocker: false);
|
||||
}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
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<CP14DemiplaneRandomGeneratorComponent, MapInitEvent>(GeneratorMapInit);
|
||||
SubscribeLocalEvent<CP14DemiplaneUsingOpenComponent, UseInHandEvent>(GeneratorUsedInHand);
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneDataComponent, GetVerbsEvent<ExamineVerb>>(OnVerbExamine);
|
||||
}
|
||||
|
||||
private void GeneratorMapInit(Entity<CP14DemiplaneRandomGeneratorComponent> generator, ref MapInitEvent args)
|
||||
{
|
||||
if (!TryComp<CP14DemiplaneDataComponent>(generator, out var data))
|
||||
return;
|
||||
|
||||
CP14DemiplaneLocationPrototype? selectedConfig = null;
|
||||
//Location generation
|
||||
if (data.Location is null || generator.Comp.OverrideLocation)
|
||||
{
|
||||
selectedConfig = GenerateDemiplaneLocation(generator.Comp.Level);
|
||||
data.Location = selectedConfig;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_proto.TryIndex(data.Location, out selectedConfig))
|
||||
return;
|
||||
}
|
||||
|
||||
//Modifier generation
|
||||
var newModifiers = GenerateDemiplaneModifiers(
|
||||
generator.Comp.Level,
|
||||
selectedConfig,
|
||||
generator.Comp.Limits);
|
||||
|
||||
foreach (var mod in newModifiers)
|
||||
{
|
||||
data.SelectedModifiers.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
private void GeneratorUsedInHand(Entity<CP14DemiplaneUsingOpenComponent> ent, ref UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<CP14DemiplaneDataComponent>(ent, out var generator))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
UseGenerator((ent, generator), args.User);
|
||||
|
||||
QueueDel(ent);
|
||||
}
|
||||
|
||||
//Ed: I hate this function.
|
||||
private void UseGenerator(Entity<CP14DemiplaneDataComponent> generator, EntityUid? user = null)
|
||||
{
|
||||
//block the opening of demiplanes after the end of a round
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
if (user is not null)
|
||||
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open-end-round"), generator, user.Value);
|
||||
return;
|
||||
}
|
||||
//We cant open demiplane in another demiplane or if parent is not Map
|
||||
if (_demiplaneQuery.HasComp(Transform(generator).MapUid))
|
||||
{
|
||||
if (user is not null)
|
||||
_popup.PopupEntity(Loc.GetString("cp14-demiplan-cannot-open", ("name", MetaData(generator).EntityName)), generator, user.Value);
|
||||
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);
|
||||
if (demiplane is not null && TryComp<CP14DemiplaneMapNodeBlockerComponent>(generator, out var blocker))
|
||||
{
|
||||
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(demiplane.Value, out var blockerMap);
|
||||
blockerMap.Position = blocker.Position;
|
||||
blockerMap.Station = blocker.Station;
|
||||
blockerMap.IncreaseNodeDifficulty = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
demiplane = ev.Demiplane;
|
||||
}
|
||||
|
||||
if (demiplane is null)
|
||||
return;
|
||||
|
||||
//Admin log needed
|
||||
EnsureComp<CP14DemiplaneDestroyWithoutStabilizationComponent>(demiplane.Value);
|
||||
|
||||
//Rifts spawning
|
||||
foreach (var rift in generator.Comp.AutoRifts)
|
||||
{
|
||||
var spawnedRift = EntityManager.Spawn(rift);
|
||||
_transform.SetCoordinates(spawnedRift, Transform(generator).Coordinates);
|
||||
_transform.AttachToGridOrMap(spawnedRift);
|
||||
var connection = EnsureComp<CP14DemiplaneRiftComponent>(spawnedRift);
|
||||
AddDemiplaneRandomExitPoint(demiplane.Value, (spawnedRift, connection));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVerbExamine(Entity<CP14DemiplaneDataComponent> 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(CP14DemiplaneDataComponent 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,
|
||||
_proto,
|
||||
_dungeon,
|
||||
_metaData,
|
||||
_mapSystem,
|
||||
mapUid,
|
||||
mapId,
|
||||
location,
|
||||
modifiers,
|
||||
_random.Next(-10000, 10000),
|
||||
cancelToken.Token);
|
||||
|
||||
_expeditionJobs.Add((job, cancelToken));
|
||||
_expeditionQueue.EnqueueJob(job);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a suitable demiplane location for the specified difficulty level.
|
||||
/// </summary>
|
||||
public CP14DemiplaneLocationPrototype GenerateDemiplaneLocation(int level)
|
||||
{
|
||||
CP14DemiplaneLocationPrototype? selectedConfig = null;
|
||||
|
||||
HashSet<CP14DemiplaneLocationPrototype> suitableConfigs = new();
|
||||
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14DemiplaneLocationPrototype>())
|
||||
{
|
||||
suitableConfigs.Add(locationConfig);
|
||||
}
|
||||
|
||||
while (suitableConfigs.Count > 0)
|
||||
{
|
||||
var randomConfig = _random.Pick(suitableConfigs);
|
||||
|
||||
//LevelRange filter
|
||||
if (level < randomConfig.Levels.Min || level > randomConfig.Levels.Max)
|
||||
{
|
||||
suitableConfigs.Remove(randomConfig);
|
||||
continue;
|
||||
}
|
||||
|
||||
selectedConfig = randomConfig;
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedConfig is null)
|
||||
throw new Exception($"No suitable demiplane location config found for level {level}!");
|
||||
|
||||
return selectedConfig;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a set of modifiers under the specified difficulty level that are appropriate for the specified demiplane location
|
||||
/// </summary>
|
||||
public List<CP14DemiplaneModifierPrototype> GenerateDemiplaneModifiers(
|
||||
int level,
|
||||
CP14DemiplaneLocationPrototype location,
|
||||
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>,float> modifierLimits)
|
||||
{
|
||||
List<CP14DemiplaneModifierPrototype> selectedModifiers = new();
|
||||
|
||||
//Modifier generation
|
||||
Dictionary<CP14DemiplaneModifierPrototype, float> suitableModifiersWeights = new();
|
||||
foreach (var modifier in _proto.EnumeratePrototypes<CP14DemiplaneModifierPrototype>())
|
||||
{
|
||||
var passed = true;
|
||||
|
||||
//Random prob filter
|
||||
if (passed)
|
||||
{
|
||||
if (!_random.Prob(modifier.GenerationProb))
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Levels filter
|
||||
if (passed)
|
||||
{
|
||||
if (level < modifier.Levels.Min || level > modifier.Levels.Max)
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Tag blacklist filter
|
||||
foreach (var configTag in location.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 (!location.Tags.Contains(reqTag))
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (passed)
|
||||
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
|
||||
}
|
||||
|
||||
|
||||
//Limits calculation
|
||||
Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> limits = new();
|
||||
foreach (var limit in modifierLimits)
|
||||
{
|
||||
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;
|
||||
|
||||
selectedModifiers.Add(selectedModifier);
|
||||
|
||||
foreach (var category in selectedModifier.Categories)
|
||||
{
|
||||
limits[category.Key] -= category.Value;
|
||||
}
|
||||
|
||||
if (selectedModifier.Unique)
|
||||
suitableModifiersWeights.Remove(selectedModifier);
|
||||
}
|
||||
|
||||
return selectedModifiers;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.SSDIndicator;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane;
|
||||
|
||||
public sealed partial class CP14DemiplaneSystem
|
||||
{
|
||||
private readonly TimeSpan _checkFrequency = TimeSpan.FromSeconds(15f);
|
||||
private TimeSpan _nextCheckTime = TimeSpan.Zero;
|
||||
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
|
||||
private void InitStabilization()
|
||||
{
|
||||
_nextCheckTime = _timing.CurTime + _checkFrequency;
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneDestroyWithoutStabilizationComponent, MapInitEvent>(OnStabilizationMapInit);
|
||||
}
|
||||
|
||||
private void OnStabilizationMapInit(Entity<CP14DemiplaneDestroyWithoutStabilizationComponent> ent,
|
||||
ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.EndProtectionTime = _timing.CurTime + ent.Comp.ProtectedSpawnTime;
|
||||
}
|
||||
|
||||
private void UpdateStabilization(float frameTime)
|
||||
{
|
||||
if (_timing.CurTime < _nextCheckTime)
|
||||
return;
|
||||
|
||||
_nextCheckTime = _timing.CurTime + _checkFrequency;
|
||||
|
||||
HashSet<EntityUid> stabilizedMaps = new();
|
||||
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneStabilizerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var stabilizer, out var transform))
|
||||
{
|
||||
var map = transform.MapUid;
|
||||
|
||||
if (map is null)
|
||||
continue;
|
||||
|
||||
if (!stabilizer.Enabled)
|
||||
continue;
|
||||
|
||||
if (stabilizer.RequireAlive && !(_mobState.IsAlive(uid) || _mobState.IsCritical(uid)))
|
||||
continue;
|
||||
|
||||
if (stabilizedMaps.Contains(map.Value))
|
||||
continue;
|
||||
if (TryComp(uid, out SSDIndicatorComponent? ssd) && ssd.IsSSD)
|
||||
continue;
|
||||
stabilizedMaps.Add(map.Value);
|
||||
}
|
||||
|
||||
var query2 = EntityQueryEnumerator<CP14DemiplaneComponent, CP14DemiplaneDestroyWithoutStabilizationComponent>();
|
||||
while (query2.MoveNext(out var uid, out var demiplane, out var stabilization))
|
||||
{
|
||||
if (_timing.CurTime < stabilization.EndProtectionTime)
|
||||
continue;
|
||||
|
||||
if (!stabilizedMaps.Contains(uid))
|
||||
DeleteDemiplane((uid, demiplane));
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteAllDemiplanes(bool safe = true)
|
||||
{
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var demiplane))
|
||||
{
|
||||
DeleteDemiplane((uid, demiplane), safe);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDemiplane(Entity<CP14DemiplaneComponent> demiplane, bool safe = false)
|
||||
{
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneForceExtractComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var extract, out var xform))
|
||||
{
|
||||
if (!extract.Enabled)
|
||||
continue;
|
||||
|
||||
if (!TryTeleportOutDemiplane(demiplane, uid))
|
||||
continue;
|
||||
|
||||
if (!safe)
|
||||
_damageable.TryChangeDamage(uid, extract.ExtractDamage);
|
||||
}
|
||||
|
||||
QueueDel(demiplane);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
using Content.Server._CP14.Demiplane.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Flash;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared._CP14.Demiplane;
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane;
|
||||
|
||||
public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly FlashSystem _flash = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
private EntityQuery<CP14DemiplaneComponent> _demiplaneQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_demiplaneQuery = GetEntityQuery<CP14DemiplaneComponent>();
|
||||
|
||||
InitGeneration();
|
||||
InitConnections();
|
||||
InitStabilization();
|
||||
InitEchoes();
|
||||
InitDestruction();
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneComponent, ComponentShutdown>(OnDemiplanShutdown);
|
||||
SubscribeLocalEvent<CP14SpawnOutOfDemiplaneComponent, MapInitEvent>(OnSpawnOutOfDemiplane);
|
||||
}
|
||||
|
||||
private void OnSpawnOutOfDemiplane(Entity<CP14SpawnOutOfDemiplaneComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
//Check if entity is in demiplane
|
||||
var map = Transform(ent).MapUid;
|
||||
if (!_demiplaneQuery.TryComp(map, out var demiplane))
|
||||
return;
|
||||
|
||||
//Get random exit demiplane point and spawn entity there
|
||||
if (demiplane.ExitPoints.Count == 0)
|
||||
return;
|
||||
|
||||
var exit = _random.Pick(demiplane.ExitPoints);
|
||||
var coordinates = Transform(exit).Coordinates;
|
||||
|
||||
var proto = ent.Comp.Proto;
|
||||
|
||||
if (proto is null)
|
||||
proto = MetaData(ent).EntityPrototype?.ID;
|
||||
|
||||
Spawn(proto, coordinates);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateGeneration(frameTime);
|
||||
UpdateStabilization(frameTime);
|
||||
UpdateDestruction(frameTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the entity inside the demiplane, to one of the random entry points.
|
||||
/// </summary>
|
||||
/// <param name="demiplane">The demiplane the entity will be teleported to</param>
|
||||
/// <param name="entity">The entity to be teleported</param>
|
||||
/// <returns></returns>
|
||||
public override bool TryTeleportIntoDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
|
||||
{
|
||||
if (entity is null)
|
||||
return false;
|
||||
|
||||
if (!TryGetDemiplaneEntryPoint(demiplane, out var entryPoint) || entryPoint is null)
|
||||
{
|
||||
Log.Error($"{entity} cant get in demiplane {demiplane}: no active entry points!");
|
||||
return false;
|
||||
}
|
||||
|
||||
TeleportEntityToCoordinate(entity.Value, Transform(entryPoint.Value).Coordinates, demiplane.Comp.ArrivalSound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple teleportation, with common special effects for all the game's teleportation mechanics
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <param name="sound"></param>
|
||||
public void TeleportEntityToCoordinate(EntityUid? entity, EntityCoordinates coordinates, SoundSpecifier? sound = null)
|
||||
{
|
||||
if (entity is null)
|
||||
return;
|
||||
|
||||
_flash.Flash(entity.Value, null, null, TimeSpan.FromSeconds(3f), 0.5f);
|
||||
_transform.SetCoordinates(entity.Value, coordinates);
|
||||
_audio.PlayGlobal(sound, entity.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports an entity from the demiplane to the real world, to one of the random exit points in the real world.
|
||||
/// </summary>
|
||||
/// <param name="demiplane">The demiplane from which the entity will be teleported</param>
|
||||
/// <param name="entity">An entity that will be teleported into the real world. This entity must be in the demiplane, otherwise the function will not work.</param>
|
||||
/// <returns></returns>
|
||||
public override bool TryTeleportOutDemiplane(Entity<CP14DemiplaneComponent> demiplane, EntityUid? entity)
|
||||
{
|
||||
if (entity is null)
|
||||
return false;
|
||||
|
||||
if (Transform(entity.Value).MapUid != demiplane.Owner)
|
||||
return false;
|
||||
|
||||
if (!TryGetDemiplaneExitPoint(demiplane, out var connection) || connection is null)
|
||||
{
|
||||
Log.Error($"{entity} cant get out of demiplane {demiplane}: no active connections!");
|
||||
return false;
|
||||
}
|
||||
|
||||
TeleportEntityToCoordinate(entity.Value, Transform(connection.Value).Coordinates, demiplane.Comp.DepartureSound);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDemiplanShutdown(Entity<CP14DemiplaneComponent> demiplane, ref ComponentShutdown args)
|
||||
{
|
||||
//We stop asynchronous generation of a demiplane early if for some reason this demiplane is deleted before generation is complete
|
||||
foreach (var (job, cancelToken) in _expeditionJobs.ToArray())
|
||||
{
|
||||
if (job.DemiplaneMapUid == demiplane.Owner)
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
_expeditionJobs.Remove((job, cancelToken));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var exit in demiplane.Comp.ExitPoints)
|
||||
{
|
||||
RemoveDemiplaneRandomExitPoint(demiplane, exit);
|
||||
}
|
||||
|
||||
foreach (var entry in demiplane.Comp.EntryPoints)
|
||||
{
|
||||
RemoveDemiplaneRandomEntryPoint(demiplane, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void DemiplaneAnnounce(EntityUid mapUid, string text)
|
||||
{
|
||||
var mapId = Comp<MapComponent>(mapUid).MapId;
|
||||
|
||||
_chatManager.ChatMessageToManyFiltered(
|
||||
Filter.BroadcastMap(mapId),
|
||||
ChatChannel.Radio,
|
||||
text,
|
||||
text,
|
||||
_mapManager.GetMapEntityId(mapId),
|
||||
false,
|
||||
true,
|
||||
null);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server._CP14.DemiplaneTraveling;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Demiplane Core - stores a position on the demiplane map to mark it as “passed” when all conditions are met
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14DemiplaneSystem), typeof(CP14StationDemiplaneMapSystem))]
|
||||
public sealed partial class CP14DemiplaneCoreComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? Demiplane;
|
||||
|
||||
[DataField]
|
||||
public EntityUid? Station;
|
||||
|
||||
[DataField]
|
||||
public Vector2i Position;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server._CP14.DemiplaneTraveling;
|
||||
using Content.Shared._CP14.Demiplane.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores all the information needed to generate a new demiplane
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14DemiplaneSystem), typeof(CP14StationDemiplaneMapSystem))]
|
||||
public sealed partial class CP14DemiplaneDataComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<CP14DemiplaneLocationPrototype>? Location;
|
||||
|
||||
[DataField]
|
||||
public List<ProtoId<CP14DemiplaneModifierPrototype>> SelectedModifiers = new();
|
||||
|
||||
[DataField]
|
||||
public List<EntProtoId> AutoRifts = new() { "CP14DemiplaneTimedRadiusPassway", "CP14DemiplanRiftCore" };
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server._CP14.DemiplaneTraveling;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The existence of an entity with this component will block the discovery of a particular coordinate in the demiplane navigation map.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14StationDemiplaneMapSystem), typeof(CP14DemiplaneSystem))]
|
||||
public sealed partial class CP14DemiplaneMapNodeBlockerComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? Station = null;
|
||||
|
||||
[DataField]
|
||||
public Vector2i Position = new (0,0);
|
||||
|
||||
[DataField]
|
||||
public int IncreaseNodeDifficulty = 0;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared._CP14.Demiplane.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Fills the DemiplaneDataComponent with random modifiers and location
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
|
||||
public sealed partial class CP14DemiplaneRandomGeneratorComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool OverrideLocation = false;
|
||||
|
||||
/// <summary>
|
||||
/// Demiplane Difficulty Level. By design, the plan so far is for a framework of 1 to 10, but technically could support more.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public int Level = 1;
|
||||
|
||||
[DataField(required: true)]
|
||||
public Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float> Limits = new();
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Open demiplane from using in hand
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
|
||||
public sealed partial class CP14DemiplaneUsingOpenComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an entity on demiplane exit points when that entity appears.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14SpawnOutOfDemiplaneComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If null, the ProtoId of this entity is taken from the entity itself.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? Proto;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared._CP14.Demiplane.Prototypes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Procedural;
|
||||
using Robust.Shared.CPUJob.JobQueues;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Demiplane.Jobs;
|
||||
|
||||
public sealed class CP14SpawnRandomDemiplaneJob : Job<bool>
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
//private readonly IGameTiming _timing;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
//private readonly AnchorableSystem _anchorable;
|
||||
private readonly DungeonSystem _dungeon;
|
||||
private readonly MetaDataSystem _metaData;
|
||||
//private readonly SharedTransformSystem _xforms;
|
||||
private readonly SharedMapSystem _map;
|
||||
|
||||
private readonly ProtoId<CP14DemiplaneLocationPrototype> _config;
|
||||
private readonly List<ProtoId<CP14DemiplaneModifierPrototype>> _modifiers;
|
||||
private readonly int _seed;
|
||||
|
||||
public readonly EntityUid DemiplaneMapUid;
|
||||
private readonly MapId _demiplaneMapId;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
public CP14SpawnRandomDemiplaneJob(
|
||||
double maxTime,
|
||||
IEntityManager entManager,
|
||||
ILogManager logManager,
|
||||
IPrototypeManager protoManager,
|
||||
DungeonSystem dungeon,
|
||||
MetaDataSystem metaData,
|
||||
SharedMapSystem map,
|
||||
EntityUid demiplaneMapUid,
|
||||
MapId demiplaneMapId,
|
||||
ProtoId<CP14DemiplaneLocationPrototype> config,
|
||||
List<ProtoId<CP14DemiplaneModifierPrototype>> modifiers,
|
||||
int seed,
|
||||
CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_prototypeManager = protoManager;
|
||||
_dungeon = dungeon;
|
||||
_metaData = metaData;
|
||||
_map = map;
|
||||
DemiplaneMapUid = demiplaneMapUid;
|
||||
_demiplaneMapId = demiplaneMapId;
|
||||
_config = config;
|
||||
_modifiers = modifiers;
|
||||
_seed = seed;
|
||||
|
||||
_sawmill = logManager.GetSawmill("cp14_demiplane_job");
|
||||
}
|
||||
|
||||
protected override async Task<bool> Process()
|
||||
{
|
||||
_sawmill.Debug($"Spawning demiplane `{_config.Id}` with seed {_seed}");
|
||||
var gridComp = _entManager.EnsureComponent<MapGridComponent>(DemiplaneMapUid);
|
||||
|
||||
MetaDataComponent? metadata = null;
|
||||
DungeonConfigPrototype dungeonConfig = new();
|
||||
|
||||
_metaData.SetEntityName(DemiplaneMapUid, $"Demiplane {_config.Id} - {_seed}");
|
||||
|
||||
//Setup demiplane config
|
||||
var expeditionConfig = _prototypeManager.Index(_config);
|
||||
var indexedLocation = _prototypeManager.Index(expeditionConfig.LocationConfig);
|
||||
|
||||
dungeonConfig.Layers.AddRange(indexedLocation.Layers);
|
||||
dungeonConfig.ReserveTiles = indexedLocation.ReserveTiles;
|
||||
|
||||
//Add map components
|
||||
_entManager.AddComponents(DemiplaneMapUid, expeditionConfig.Components);
|
||||
|
||||
//Apply modifiers
|
||||
foreach (var modifier in _modifiers)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(modifier, out var indexedModifier))
|
||||
continue;
|
||||
|
||||
if (indexedModifier.Layers != null)
|
||||
dungeonConfig.Layers.AddRange(indexedModifier.Layers);
|
||||
if (indexedModifier.Components != null)
|
||||
_entManager.AddComponents(DemiplaneMapUid, indexedModifier.Components);
|
||||
|
||||
_sawmill.Debug($"Added modifier: {_seed} - {modifier.Id}");
|
||||
}
|
||||
|
||||
//Setup gravity
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(DemiplaneMapUid);
|
||||
gravity.Enabled = true;
|
||||
_entManager.Dirty(DemiplaneMapUid, gravity, metadata);
|
||||
|
||||
// Setup default atmos
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
_entManager.System<AtmosphereSystem>().SetMapAtmosphere(DemiplaneMapUid, false, mixture);
|
||||
|
||||
_map.InitializeMap(_demiplaneMapId);
|
||||
_map.SetPaused(_demiplaneMapId, false);
|
||||
|
||||
//Spawn modified config
|
||||
_dungeon.GenerateDungeon(dungeonConfig,
|
||||
DemiplaneMapUid,
|
||||
gridComp,
|
||||
Vector2i.Zero,
|
||||
_seed); //TODO: Transform to Async
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Server._CP14.Demiplane;
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server._CP14.DemiplaneAdmin;
|
||||
|
||||
public sealed partial class CP14DemiplaneAdminSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneGenerationCatchAttemptEvent>(OnAdminDemiplaneCatch);
|
||||
}
|
||||
|
||||
private void OnAdminDemiplaneCatch(CP14DemiplaneGenerationCatchAttemptEvent ev)
|
||||
{
|
||||
if (ev.Handled)
|
||||
return;
|
||||
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneRiftCatcherComponent, MapComponent, CP14DemiplaneComponent>();
|
||||
while (query.MoveNext(out var uid, out var catcher, out var map, out var demiplane))
|
||||
{
|
||||
ev.Demiplane = (uid, demiplane);
|
||||
ev.Handled = true;
|
||||
RemCompDeferred(uid, catcher);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server._CP14.DemiplaneAdmin;
|
||||
|
||||
/// <summary>
|
||||
/// This demiplane can be added to a map by the admins, which will redirect the next opened demiplane key to that map
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14DemiplaneRiftCatcherComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
using Content.Server._CP14.Demiplane;
|
||||
using Content.Server._CP14.RoundEnd;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._CP14.Demiplane;
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Content.Shared._CP14.DemiplaneTraveling;
|
||||
using Content.Shared._CP14.Religion.Components;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._CP14.DemiplaneTraveling;
|
||||
|
||||
public sealed partial class CP14DemiplaneTravelingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CP14DemiplaneSystem _demiplan = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneRadiusTimedPasswayComponent, MapInitEvent>(RadiusMapInit);
|
||||
SubscribeLocalEvent<CP14MonolithTimedPasswayComponent, MapInitEvent>(MonolithMapInit);
|
||||
SubscribeLocalEvent<CP14DemiplaneRiftOpenedComponent, CP14DemiplanPasswayUseDoAfter>(OnOpenRiftInteractDoAfter);
|
||||
}
|
||||
|
||||
// !!!SHITCODE WARNING!!!
|
||||
// This whole module is saturated with shitcode, code duplication and other delights. Why? Because.
|
||||
//TODO: Refactor this shitcode
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
DemiplaneTeleportUpdate();
|
||||
|
||||
var query = EntityQueryEnumerator<CP14MonolithTimedPasswayComponent>();
|
||||
while (query.MoveNext(out var uid, out var passWay))
|
||||
{
|
||||
if (_timing.CurTime < passWay.NextTimeTeleport)
|
||||
continue;
|
||||
|
||||
passWay.NextTimeTeleport = _timing.CurTime + passWay.Delay;
|
||||
|
||||
//Get all teleporting entities
|
||||
HashSet<EntityUid> teleportedEnts = new();
|
||||
var nearestEnts = _lookup.GetEntitiesInRange(uid, passWay.Radius);
|
||||
foreach (var ent in nearestEnts)
|
||||
{
|
||||
if (HasComp<GhostComponent>(ent))
|
||||
continue;
|
||||
if (HasComp<CP14ReligionEntityComponent>(ent)) //TODO: make some generic way to whitelist entities from teleporting
|
||||
continue;
|
||||
|
||||
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
|
||||
continue;
|
||||
|
||||
teleportedEnts.Add(ent);
|
||||
}
|
||||
|
||||
while (teleportedEnts.Count > passWay.MaxEntities)
|
||||
{
|
||||
teleportedEnts.Remove(_random.Pick(teleportedEnts));
|
||||
}
|
||||
|
||||
//Aaaand teleport it
|
||||
var monoliths = EntityQueryEnumerator<CP14MagicContainerRoundFinisherComponent>();
|
||||
while (monoliths.MoveNext(out var monolithUid, out var monolith))
|
||||
{
|
||||
var coord = Transform(monolithUid).Coordinates;
|
||||
|
||||
//Shitcode select first one
|
||||
foreach (var ent in teleportedEnts)
|
||||
{
|
||||
if (TryComp<PullerComponent>(ent, out var puller))
|
||||
_demiplan.TeleportEntityToCoordinate(puller.Pulling, coord);
|
||||
|
||||
_demiplan.TeleportEntityToCoordinate(ent, coord);
|
||||
_audio.PlayPvs(passWay.ArrivalSound, ent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_audio.PlayPvs(passWay.DepartureSound, Transform(uid).Coordinates);
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void DemiplaneTeleportUpdate()
|
||||
{
|
||||
//Radius passway
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneRadiusTimedPasswayComponent, CP14DemiplaneRiftComponent>();
|
||||
while (query.MoveNext(out var uid, out var passWay, out var rift))
|
||||
{
|
||||
if (_timing.CurTime < passWay.NextTimeTeleport)
|
||||
continue;
|
||||
|
||||
passWay.NextTimeTeleport = _timing.CurTime + passWay.Delay;
|
||||
|
||||
//Get all teleporting entities
|
||||
HashSet<EntityUid> teleportedEnts = new();
|
||||
var nearestEnts = _lookup.GetEntitiesInRange(uid, passWay.Radius);
|
||||
foreach (var ent in nearestEnts)
|
||||
{
|
||||
if (HasComp<GhostComponent>(ent))
|
||||
continue;
|
||||
if (HasComp<CP14ReligionEntityComponent>(ent)) //TODO: make some generic way to whitelist entities from teleporting
|
||||
continue;
|
||||
|
||||
if (!_mind.TryGetMind(ent, out var mindId, out var mind))
|
||||
continue;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(ent, uid))
|
||||
continue;
|
||||
|
||||
// Talking swords are not party members, and should not be teleported separately from their wielders
|
||||
if (HasComp<ItemComponent>(ent))
|
||||
continue;
|
||||
|
||||
teleportedEnts.Add(ent);
|
||||
}
|
||||
|
||||
while (teleportedEnts.Count > passWay.MaxEntities)
|
||||
{
|
||||
teleportedEnts.Remove(_random.Pick(teleportedEnts));
|
||||
}
|
||||
|
||||
//Aaaand teleport it
|
||||
var map = Transform(uid).MapUid;
|
||||
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan))
|
||||
{
|
||||
if (!_demiplan.TryGetDemiplaneExitPoint((map.Value, demiplan), out _))
|
||||
break;
|
||||
|
||||
foreach (var ent in teleportedEnts) //We in demiplan, tp OUT
|
||||
{
|
||||
if (TryComp<PullerComponent>(ent, out var puller))
|
||||
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), puller.Pulling);
|
||||
|
||||
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), ent);
|
||||
_audio.PlayPvs(passWay.ArrivalSound, ent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rift.Demiplane is not null &&
|
||||
TryComp<CP14DemiplaneComponent>(rift.Demiplane.Value, out var riftDemiplane))
|
||||
{
|
||||
if (!_demiplan.TryGetDemiplaneEntryPoint((rift.Demiplane.Value, riftDemiplane), out _))
|
||||
break;
|
||||
|
||||
foreach (var ent in teleportedEnts) //We out demiplan, tp IN
|
||||
{
|
||||
if (TryComp<PullerComponent>(ent, out var puller))
|
||||
_demiplan.TryTeleportIntoDemiplane((rift.Demiplane.Value, riftDemiplane), puller.Pulling);
|
||||
|
||||
_demiplan.TryTeleportIntoDemiplane((rift.Demiplane.Value, riftDemiplane), ent);
|
||||
_audio.PlayPvs(passWay.ArrivalSound, ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_audio.PlayPvs(passWay.DepartureSound, Transform(uid).Coordinates);
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void RadiusMapInit(Entity<CP14DemiplaneRadiusTimedPasswayComponent> radiusPassWay, ref MapInitEvent args)
|
||||
{
|
||||
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
|
||||
}
|
||||
|
||||
private void MonolithMapInit(Entity<CP14MonolithTimedPasswayComponent> radiusPassWay, ref MapInitEvent args)
|
||||
{
|
||||
radiusPassWay.Comp.NextTimeTeleport = _timing.CurTime + radiusPassWay.Comp.Delay;
|
||||
}
|
||||
|
||||
private void OnOpenRiftInteractDoAfter(Entity<CP14DemiplaneRiftOpenedComponent> passWay,
|
||||
ref CP14DemiplanPasswayUseDoAfter args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled)
|
||||
return;
|
||||
|
||||
var used = false;
|
||||
var map = Transform(passWay).MapUid;
|
||||
if (TryComp<CP14DemiplaneComponent>(map, out var demiplan))
|
||||
{
|
||||
if (TryComp<PullerComponent>(args.User, out var puller))
|
||||
_demiplan.TryTeleportOutDemiplane((map.Value, demiplan), puller.Pulling);
|
||||
|
||||
used = _demiplan.TryTeleportOutDemiplane((map.Value, demiplan), args.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryComp<CP14DemiplaneRiftComponent>(passWay, out var exitPoint) && exitPoint.Demiplane is not null &&
|
||||
TryComp<CP14DemiplaneComponent>(exitPoint.Demiplane.Value, out var exitDemiplane))
|
||||
{
|
||||
if (TryComp<PullerComponent>(args.User, out var puller))
|
||||
_demiplan.TryTeleportIntoDemiplane((exitPoint.Demiplane.Value, exitDemiplane), puller.Pulling);
|
||||
|
||||
used = _demiplan.TryTeleportIntoDemiplane((exitPoint.Demiplane.Value, exitDemiplane), args.User);
|
||||
}
|
||||
}
|
||||
|
||||
if (used)
|
||||
{
|
||||
_audio.PlayPvs(passWay.Comp.DepartureSound, Transform(passWay).Coordinates);
|
||||
_audio.PlayPvs(passWay.Comp.ArrivalSound, args.User);
|
||||
if (passWay.Comp.MaxUse > 0)
|
||||
{
|
||||
passWay.Comp.MaxUse--;
|
||||
if (passWay.Comp.MaxUse == 0)
|
||||
QueueDel(passWay);
|
||||
}
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server._CP14.Demiplane;
|
||||
using Content.Server._CP14.Demiplane.Components;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._CP14.Demiplane.Components;
|
||||
using Content.Shared._CP14.Demiplane.Prototypes;
|
||||
using Content.Shared._CP14.DemiplaneTraveling;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.DemiplaneTraveling;
|
||||
|
||||
public sealed partial class CP14StationDemiplaneMapSystem : CP14SharedStationDemiplaneMapSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly CP14DemiplaneSystem _demiplane = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14StationDemiplaneMapComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, CP14DemiplaneMapEjectMessage>(DemiplaneEjectAttempt);
|
||||
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, CP14DemiplaneMapRevokeMessage>(DemiplaneRevokeAttempt);
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneNavigationMapComponent, BeforeActivatableUIOpenEvent>(OnBeforeActivatableUiOpen);
|
||||
SubscribeLocalEvent<CP14DemiplaneMapNodeBlockerComponent, ComponentShutdown>(OnNodeBlockerShutdown);
|
||||
|
||||
SubscribeLocalEvent<CP14DemiplaneCoreComponent, MapInitEvent>(OnCoreInit);
|
||||
SubscribeLocalEvent<CP14DemiplaneCoreComponent, DestructionEventArgs>(OnCoreShutdown);
|
||||
}
|
||||
|
||||
private void OnCoreShutdown(Entity<CP14DemiplaneCoreComponent> ent, ref DestructionEventArgs args)
|
||||
{
|
||||
if (TryComp<CP14DemiplaneComponent>(ent.Comp.Demiplane, out var demiplane))
|
||||
{
|
||||
_demiplane.StartDestructDemiplane((ent.Comp.Demiplane.Value, demiplane));
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CP14StationDemiplaneMapComponent>();
|
||||
while (query.MoveNext(out var uid, out var stationMap))
|
||||
{
|
||||
if (stationMap.Nodes.TryGetValue(ent.Comp.Position, out var node))
|
||||
{
|
||||
node.Completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCoreInit(Entity<CP14DemiplaneCoreComponent> core, ref MapInitEvent args)
|
||||
{
|
||||
core.Comp.Demiplane = Transform(core).MapUid;
|
||||
if (!TryComp<CP14DemiplaneMapNodeBlockerComponent>(core.Comp.Demiplane, out var demiBlocker))
|
||||
return;
|
||||
|
||||
core.Comp.Station = demiBlocker.Station;
|
||||
core.Comp.Position = demiBlocker.Position;
|
||||
|
||||
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(core, out var coreBlocker);
|
||||
|
||||
coreBlocker.Position = core.Comp.Position;
|
||||
coreBlocker.Station = core.Comp.Station;
|
||||
coreBlocker.IncreaseNodeDifficulty = 0;
|
||||
}
|
||||
|
||||
private void OnNodeBlockerShutdown(Entity<CP14DemiplaneMapNodeBlockerComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<CP14StationDemiplaneMapComponent>(ent.Comp.Station, out var stationMap))
|
||||
return;
|
||||
|
||||
if (!stationMap.Nodes.TryGetValue(ent.Comp.Position, out var node))
|
||||
return;
|
||||
|
||||
if (ent.Comp.IncreaseNodeDifficulty == 0)
|
||||
return;
|
||||
|
||||
node.AdditionalLevel += ent.Comp.IncreaseNodeDifficulty;
|
||||
GenerateNodeData(node, clearOldModifiers: true);
|
||||
}
|
||||
|
||||
private void DemiplaneEjectAttempt(Entity<CP14DemiplaneNavigationMapComponent> ent, ref CP14DemiplaneMapEjectMessage args)
|
||||
{
|
||||
var station = _station.GetOwningStation(ent, Transform(ent));
|
||||
|
||||
if (!TryComp<CP14StationDemiplaneMapComponent>(station, out var stationMap))
|
||||
return;
|
||||
|
||||
if (!stationMap.Nodes.TryGetValue(args.Position, out var node))
|
||||
return;
|
||||
|
||||
if (!node.InFrontierZone)
|
||||
return;
|
||||
|
||||
//Eject!
|
||||
var key = SpawnAttachedTo(ent.Comp.KeyProto, Transform(ent).Coordinates);
|
||||
_audio.PlayPvs(ent.Comp.EjectSound, Transform(key).Coordinates);
|
||||
|
||||
if (TryComp<CP14DemiplaneDataComponent>(key, out var demiData))
|
||||
{
|
||||
demiData.Location = node.LocationConfig;
|
||||
demiData.SelectedModifiers.AddRange(node.Modifiers);
|
||||
}
|
||||
|
||||
EnsureComp<CP14DemiplaneMapNodeBlockerComponent>(key, out var blockerComp);
|
||||
blockerComp.Position = args.Position;
|
||||
blockerComp.Station = station;
|
||||
}
|
||||
|
||||
private void DemiplaneRevokeAttempt(Entity<CP14DemiplaneNavigationMapComponent> ent, ref CP14DemiplaneMapRevokeMessage args)
|
||||
{
|
||||
var station = _station.GetOwningStation(ent, Transform(ent));
|
||||
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneMapNodeBlockerComponent>();
|
||||
while (query.MoveNext(out var uid, out var blocker))
|
||||
{
|
||||
if (blocker.Station != station)
|
||||
continue;
|
||||
|
||||
if (blocker.Position != args.Position)
|
||||
continue;
|
||||
|
||||
if (!TryComp<CP14StationDemiplaneMapComponent>(station, out var demiStation))
|
||||
continue;
|
||||
|
||||
if (!demiStation.Nodes.TryGetValue(args.Position, out var node))
|
||||
continue;
|
||||
|
||||
SpawnAttachedTo("CP14ImpactEffectMagicSplitting", Transform(ent).Coordinates);
|
||||
|
||||
//If it's a demiplane, initiate closure. If not, just delete it.
|
||||
if (TryComp<CP14DemiplaneComponent>(uid, out var demiplane))
|
||||
{
|
||||
_demiplane.StartDestructDemiplane((uid, demiplane));
|
||||
_popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-map"), ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnAttachedTo("CP14ImpactEffectMagicSplitting", Transform(uid).Coordinates);
|
||||
_popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-item"), ent);
|
||||
_popup.PopupEntity(Loc.GetString("cp14-demiplane-revoke-item"), uid);
|
||||
_damageable.TryChangeDamage(uid, ent.Comp.RevokeDamage, true);
|
||||
_destructible.DestroyEntity(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeforeActivatableUiOpen(Entity<CP14DemiplaneNavigationMapComponent> ent,
|
||||
ref BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
var station = _station.GetOwningStation(ent, Transform(ent));
|
||||
|
||||
if (!TryComp<CP14StationDemiplaneMapComponent>(station, out var stationMap))
|
||||
return;
|
||||
|
||||
UpdateNodesStatus((station.Value, stationMap));
|
||||
|
||||
_userInterface.SetUiState(ent.Owner,
|
||||
CP14DemiplaneMapUiKey.Key,
|
||||
new CP14DemiplaneMapUiState(stationMap.Nodes, stationMap.Edges));
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<CP14StationDemiplaneMapComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
GenerateDemiplaneMap(ent);
|
||||
}
|
||||
|
||||
private void GenerateDemiplaneMap(Entity<CP14StationDemiplaneMapComponent> ent)
|
||||
{
|
||||
ent.Comp.Nodes.Clear();
|
||||
ent.Comp.Edges.Clear();
|
||||
|
||||
var allSpecials = _proto.EnumeratePrototypes<CP14SpecialDemiplanePrototype>().ToList();
|
||||
_random.Shuffle(allSpecials);
|
||||
|
||||
var grid = new Dictionary<Vector2i, CP14DemiplaneMapNode>();
|
||||
|
||||
//Spawn start room at 0 0
|
||||
var startPos = new Vector2i(0, 0);
|
||||
var startNode = new CP14DemiplaneMapNode(0, startPos, true);
|
||||
grid[startPos] = startNode;
|
||||
|
||||
//Spawn special rooms
|
||||
var specialCount = _random.Next(ent.Comp.Specials.Min, ent.Comp.Specials.Max + 1);
|
||||
var placedSpecials = 0;
|
||||
var specialPositions = new List<Vector2i>();
|
||||
|
||||
foreach (var special in allSpecials)
|
||||
{
|
||||
if (placedSpecials >= specialCount)
|
||||
break;
|
||||
|
||||
var specialLevel = special.Levels.Next(_random);
|
||||
|
||||
var possiblePositions = new List<Vector2i>();
|
||||
for (var x = -specialLevel; x <= specialLevel; x++)
|
||||
{
|
||||
var y = specialLevel - Math.Abs(x);
|
||||
if (y != 0)
|
||||
possiblePositions.Add(new Vector2i(x, y));
|
||||
possiblePositions.Add(new Vector2i(x, -y));
|
||||
}
|
||||
|
||||
_random.Shuffle(possiblePositions);
|
||||
|
||||
var specialPos = new Vector2i(0, 0);
|
||||
foreach (var pos in possiblePositions)
|
||||
{
|
||||
if (!grid.ContainsKey(pos))
|
||||
{
|
||||
specialPos = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (grid.ContainsKey(specialPos))
|
||||
continue;
|
||||
|
||||
var specialNode = new CP14DemiplaneMapNode(
|
||||
specialLevel,
|
||||
new Vector2(specialPos.X, specialPos.Y),
|
||||
false,
|
||||
locationConfig: special.Location,
|
||||
modifiers: [..special.Modifiers]
|
||||
);
|
||||
grid[specialPos] = specialNode;
|
||||
specialPositions.Add(specialPos);
|
||||
placedSpecials++;
|
||||
}
|
||||
|
||||
// Build meandering paths to each special room and add edges
|
||||
foreach (var specialPos in specialPositions)
|
||||
{
|
||||
var current = startPos;
|
||||
|
||||
while (current != specialPos)
|
||||
{
|
||||
var delta = specialPos - current;
|
||||
var options = new List<Vector2i>();
|
||||
|
||||
if (delta.X != 0)
|
||||
options.Add(new Vector2i(Math.Sign(delta.X), 0));
|
||||
if (delta.Y != 0)
|
||||
options.Add(new Vector2i(0, Math.Sign(delta.Y)));
|
||||
|
||||
// Add the possibility of a "mistaken" step to the side
|
||||
if (_random.Prob(0.3f)) // 30% chance to take a side step
|
||||
{
|
||||
if (delta.X != 0 && delta.Y != 0)
|
||||
{
|
||||
options.Add(new Vector2i(0, Math.Sign(delta.Y)) * -1);
|
||||
options.Add(new Vector2i(Math.Sign(delta.X), 0) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
_random.Shuffle(options);
|
||||
var step = options[0];
|
||||
var next = current + step;
|
||||
|
||||
if (!grid.TryGetValue(next, out var nextNode))
|
||||
{
|
||||
nextNode = new CP14DemiplaneMapNode(Math.Abs(next.X) + Math.Abs(next.Y),
|
||||
new Vector2(next.X, next.Y),
|
||||
false);
|
||||
grid[next] = nextNode;
|
||||
}
|
||||
|
||||
ent.Comp.Edges.Add((current, next));
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
//Fill nodes with random data
|
||||
foreach (var node in grid.Values)
|
||||
{
|
||||
GenerateNodeData(node);
|
||||
}
|
||||
|
||||
// Random visual offset
|
||||
foreach (var node in grid.Values)
|
||||
{
|
||||
var x = node.UiPosition.X + _random.NextFloat(-0.2f, 0.2f);
|
||||
var y = node.UiPosition.Y + _random.NextFloat(-0.2f, 0.2f);
|
||||
node.UiPosition = new Vector2(x, y);
|
||||
}
|
||||
|
||||
//Add all rooms into component
|
||||
ent.Comp.Nodes = grid;
|
||||
}
|
||||
|
||||
private void GenerateNodeData(CP14DemiplaneMapNode node, bool clearOldModifiers = false)
|
||||
{
|
||||
if (node.Level == 0)
|
||||
return;
|
||||
|
||||
var location = _demiplane.GenerateDemiplaneLocation(node.Level);
|
||||
node.LocationConfig ??= location;
|
||||
|
||||
var limits = new Dictionary<ProtoId<CP14DemiplaneModifierCategoryPrototype>, float>
|
||||
{
|
||||
{ "Danger", (node.Level + node.AdditionalLevel) * 0.2f },
|
||||
{ "GhostRoleDanger", 1f },
|
||||
{ "Reward", Math.Max(node.Level * 0.2f, 0.5f) },
|
||||
{ "Ore", Math.Max(node.Level * 0.2f, 0.5f) },
|
||||
{ "Fun", 1f },
|
||||
{ "Weather", 1f },
|
||||
{ "MapLight", 1f },
|
||||
};
|
||||
var mods = _demiplane.GenerateDemiplaneModifiers(node.Level, location, limits);
|
||||
|
||||
if (clearOldModifiers)
|
||||
node.Modifiers.Clear();
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
node.Modifiers.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodesStatus(Entity<CP14StationDemiplaneMapComponent> ent)
|
||||
{
|
||||
foreach (var node in ent.Comp.Nodes)
|
||||
{
|
||||
node.Value.InFrontierZone = NodeInFronrierZone(ent.Comp.Nodes, ent.Comp.Edges, node.Key);
|
||||
node.Value.InUsing = false;
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CP14DemiplaneMapNodeBlockerComponent>();
|
||||
while (query.MoveNext(out var uid, out var blocker))
|
||||
{
|
||||
if (!TryComp<CP14StationDemiplaneMapComponent>(blocker.Station, out var stationMap))
|
||||
continue;
|
||||
|
||||
if (!stationMap.Nodes.TryGetValue(blocker.Position, out var node))
|
||||
continue;
|
||||
|
||||
node.InUsing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server._CP14.GameTicking.Rules.Components;
|
||||
using Content.Server._CP14.Procedural;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server._CP14.GameTicking.Rules;
|
||||
|
||||
public sealed class CP14ExpeditionToWindlandsRule : GameRuleSystem<CP14ExpeditionToWindlandsRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ShuttleSystem _shuttles = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly CP14LocationGenerationSystem _generation = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = _logManager.GetSawmill("cp14_expedition_to_windlands_rule");
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid,
|
||||
CP14ExpeditionToWindlandsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var station = _station.GetStations().First();
|
||||
if (!TryComp<StationDataComponent>(station, out var stationData))
|
||||
{
|
||||
_sawmill.Error($"Station {station} does not have a StationDataComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
var largestStationGrid = _station.GetLargestGrid(stationData);
|
||||
|
||||
if (largestStationGrid is null)
|
||||
{
|
||||
_sawmill.Error($"Station {station} does not have a grid.");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureComp<ShuttleComponent>(largestStationGrid.Value, out var shuttleComp);
|
||||
|
||||
var windlands = _mapSystem.CreateMap(out var mapId, runMapInit: false);
|
||||
|
||||
_generation.GenerateLocation(windlands, mapId, component.Location, component.Modifiers);
|
||||
_shuttles.FTLToCoordinates(largestStationGrid.Value, shuttleComp, new EntityCoordinates(windlands, Vector2.Zero), 0f, 0f, component.FloatingTime);
|
||||
}
|
||||
}
|
||||
116
Content.Server/_CP14/GameTicking/Rules/CP14CrashingShipRule.cs
Normal file
116
Content.Server/_CP14/GameTicking/Rules/CP14CrashingShipRule.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Linq;
|
||||
using Content.Server._CP14.GameTicking.Rules.Components;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._CP14.GameTicking.Rules;
|
||||
|
||||
public sealed class CP14CrashingShipRule : GameRuleSystem<CP14CrashingShipRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmill = _logManager.GetSawmill("cp14_crashing_ship_rule");
|
||||
|
||||
SubscribeLocalEvent<CP14CrashingShipComponent, FTLCompletedEvent>(OnFTLCompleted);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateExplosions(frameTime);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid,
|
||||
CP14CrashingShipRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var station = _station.GetStations().First();
|
||||
if (!TryComp<StationDataComponent>(station, out var stationData))
|
||||
{
|
||||
_sawmill.Error($"Station {station} does not have a StationDataComponent.");
|
||||
return;
|
||||
}
|
||||
|
||||
var largestStationGrid = _station.GetLargestGrid(stationData);
|
||||
|
||||
if (largestStationGrid is null)
|
||||
{
|
||||
_sawmill.Error($"Station {station} does not have a grid.");
|
||||
return;
|
||||
}
|
||||
|
||||
component.StartExplosionTime += _timing.CurTime;
|
||||
component.Ship = largestStationGrid.Value;
|
||||
}
|
||||
|
||||
private void OnFTLCompleted(Entity<CP14CrashingShipComponent> ent, ref FTLCompletedEvent args)
|
||||
{
|
||||
SpawnRandomExplosion(ent, ent.Comp.FinalExplosionProto, 10);
|
||||
RemCompDeferred<CP14CrashingShipComponent>(ent);
|
||||
}
|
||||
|
||||
private void UpdateExplosions(float frameTime)
|
||||
{
|
||||
var ruleQuery = EntityQueryEnumerator<CP14CrashingShipRuleComponent>();
|
||||
while (ruleQuery.MoveNext(out var uid, out var rule))
|
||||
{
|
||||
if (!rule.PendingExplosions)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < rule.StartExplosionTime)
|
||||
continue;
|
||||
|
||||
if (rule.Ship is null)
|
||||
continue;
|
||||
|
||||
AddComp<CP14CrashingShipComponent>(rule.Ship.Value);
|
||||
rule.PendingExplosions = false;
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CP14CrashingShipComponent>();
|
||||
while (query.MoveNext(out var uid, out var ship))
|
||||
{
|
||||
if (_timing.CurTime < ship.NextExplosionTime)
|
||||
continue;
|
||||
|
||||
ship.NextExplosionTime = _timing.CurTime + TimeSpan.FromSeconds(_random.Next(2, 10));
|
||||
SpawnRandomExplosion((uid, ship), ship.ExplosionProto, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SpawnRandomExplosion(Entity<CP14CrashingShipComponent> grid, EntProtoId explosionProto, int count)
|
||||
{
|
||||
var station = _station.GetOwningStation(grid);
|
||||
|
||||
if (station is null)
|
||||
return;
|
||||
|
||||
TryFindRandomTileOnStation((station.Value, Comp<StationDataComponent>(station.Value)),
|
||||
out var tile,
|
||||
out var targetGrid,
|
||||
out var targetCoords);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Spawn(explosionProto, targetCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
///When attached to shuttle, start firebombing it until FTL ends.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14CrashingShipRule))]
|
||||
public sealed partial class CP14CrashingShipComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan NextExplosionTime = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId ExplosionProto = "CP14ShipExplosion";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId FinalExplosionProto = "CP14ShipExplosionBig";
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Content.Server._CP14.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A rule that assigns common goals to different roles. Common objectives are generated once at the beginning of a round and are shared between players.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14CrashingShipRule))]
|
||||
public sealed partial class CP14CrashingShipRuleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? Ship;
|
||||
|
||||
[DataField]
|
||||
public bool PendingExplosions = true;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan StartExplosionTime = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A rule that assigns common goals to different roles. Common objectives are generated once at the beginning of a round and are shared between players.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14ExpeditionToWindlandsRule))]
|
||||
public sealed partial class CP14ExpeditionToWindlandsRuleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<CP14ProceduralLocationPrototype> Location = "T1GrasslandIsland";
|
||||
|
||||
[DataField]
|
||||
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = [];
|
||||
|
||||
[DataField]
|
||||
public float FloatingTime = 120;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to cards on which procedural generation occurs, and removed when generation is successfully completed.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14ActiveJobGenerationComponent : Component
|
||||
{
|
||||
}
|
||||
109
Content.Server/_CP14/Procedural/CP14LocationGenerationSystem.cs
Normal file
109
Content.Server/_CP14/Procedural/CP14LocationGenerationSystem.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Threading;
|
||||
using Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Content.Shared.Procedural;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.CPUJob.JobQueues;
|
||||
using Robust.Shared.CPUJob.JobQueues.Queues;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.Procedural;
|
||||
|
||||
public sealed class CP14LocationGenerationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private const double JobMaxTime = 0.002;
|
||||
private readonly JobQueue _expeditionQueue = new();
|
||||
private readonly List<(CP14SpawnProceduralLocationJob Job, CancellationTokenSource CancelToken)> _jobs = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14ActiveJobGenerationComponent, ComponentShutdown>(OnGenerationShutdown);
|
||||
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_expeditionQueue.Process();
|
||||
|
||||
foreach (var (job, cancelToken) in _jobs.ToArray())
|
||||
{
|
||||
switch (job.Status)
|
||||
{
|
||||
case JobStatus.Finished:
|
||||
if (job.JobName is not null)
|
||||
{
|
||||
var ev = new CP14LocationGeneratedEvent(job.JobName);
|
||||
RaiseLocalEvent(ev);
|
||||
}
|
||||
RemComp<CP14ActiveJobGenerationComponent>(job.MapUid);
|
||||
|
||||
_jobs.Remove((job, cancelToken));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new procedural location on the specified map and coordinates.
|
||||
/// Essentially, this is a wrapper for _dungeon.GenerateDungeon, which collects the necessary settings for the
|
||||
/// dungeon based on the location and modifiers.
|
||||
/// </summary>
|
||||
public void GenerateLocation(EntityUid mapUid, MapId mapId, ProtoId<CP14ProceduralLocationPrototype> location, List<ProtoId<CP14ProceduralModifierPrototype>> modifiers, Vector2i position = new(), int? seed = null, string? jobName = null)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
|
||||
EnsureComp<CP14ActiveJobGenerationComponent>(mapUid);
|
||||
|
||||
var job = new CP14SpawnProceduralLocationJob(
|
||||
JobMaxTime,
|
||||
EntityManager,
|
||||
_logManager,
|
||||
_proto,
|
||||
_dungeon,
|
||||
_mapSystem,
|
||||
mapUid,
|
||||
mapId,
|
||||
position,
|
||||
seed ?? _random.Next(-10000, 10000),
|
||||
location,
|
||||
modifiers,
|
||||
jobName,
|
||||
cancelToken.Token);
|
||||
|
||||
_jobs.Add((job, cancelToken));
|
||||
_expeditionQueue.EnqueueJob(job);
|
||||
}
|
||||
|
||||
|
||||
private void OnGenerationShutdown(Entity<CP14ActiveJobGenerationComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
//We stop asynchronous generation of a location early if for some reason this location is deleted before generation is complete
|
||||
foreach (var (job, cancelToken) in _jobs.ToArray())
|
||||
{
|
||||
if (job.MapUid == ent.Owner)
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
_jobs.Remove((job, cancelToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class CP14LocationGeneratedEvent(string jobName) : EntityEventArgs
|
||||
{
|
||||
public string JobName = jobName;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Robust.Shared.CPUJob.JobQueues;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Procedural;
|
||||
|
||||
public sealed class CP14SpawnProceduralLocationJob(
|
||||
double maxTime,
|
||||
IEntityManager entManager,
|
||||
ILogManager logManager,
|
||||
IPrototypeManager protoManager,
|
||||
DungeonSystem dungeon,
|
||||
SharedMapSystem map,
|
||||
EntityUid mapUid,
|
||||
MapId mapId,
|
||||
Vector2i position,
|
||||
int seed,
|
||||
ProtoId<CP14ProceduralLocationPrototype> config,
|
||||
List<ProtoId<CP14ProceduralModifierPrototype>> modifiers,
|
||||
string? jobName = null,
|
||||
CancellationToken cancellation = default)
|
||||
: Job<bool>(maxTime, cancellation)
|
||||
{
|
||||
public readonly EntityUid MapUid = mapUid;
|
||||
public string? JobName = jobName;
|
||||
|
||||
private readonly ISawmill _sawmill = logManager.GetSawmill("cp14_procedural_location_job");
|
||||
|
||||
protected override async Task<bool> Process()
|
||||
{
|
||||
_sawmill.Debug($"Spawning procedural location `{config.Id}` with seed {seed}");
|
||||
var gridComp = entManager.EnsureComponent<MapGridComponent>(MapUid);
|
||||
|
||||
MetaDataComponent? metadata = null;
|
||||
DungeonConfigPrototype dungeonConfig = new();
|
||||
|
||||
//Boilerplate: reserve all old grid tiles
|
||||
dungeonConfig.Layers.Add(new CP14ReserveGrid());
|
||||
|
||||
//Setup location config
|
||||
var locationConfig = protoManager.Index(config);
|
||||
var indexedLocation = protoManager.Index(locationConfig.LocationConfig);
|
||||
|
||||
dungeonConfig.Layers.AddRange(indexedLocation.Layers);
|
||||
dungeonConfig.ReserveTiles = indexedLocation.ReserveTiles;
|
||||
|
||||
//Add map components
|
||||
entManager.AddComponents(MapUid, locationConfig.Components);
|
||||
|
||||
//Apply modifiers
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (!protoManager.TryIndex(modifier, out var indexedModifier))
|
||||
continue;
|
||||
|
||||
if (indexedModifier.Layers != null)
|
||||
dungeonConfig.Layers.AddRange(indexedModifier.Layers);
|
||||
if (indexedModifier.Components != null)
|
||||
entManager.AddComponents(MapUid, indexedModifier.Components);
|
||||
|
||||
_sawmill.Debug($"Added modifier: {seed} - {modifier.Id}");
|
||||
}
|
||||
|
||||
//Setup gravity
|
||||
var gravity = entManager.EnsureComponent<GravityComponent>(MapUid);
|
||||
gravity.Enabled = true;
|
||||
entManager.Dirty(MapUid, gravity, metadata);
|
||||
|
||||
// Setup default atmos
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int)Gas.Oxygen] = 21.824779f;
|
||||
moles[(int)Gas.Nitrogen] = 82.10312f;
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
entManager.System<AtmosphereSystem>().SetMapAtmosphere(MapUid, false, mixture);
|
||||
|
||||
if (!map.IsInitialized(mapId))
|
||||
map.InitializeMap(mapId);
|
||||
map.SetPaused(mapId, false);
|
||||
|
||||
//Spawn modified config
|
||||
await WaitAsyncTask(dungeon.GenerateDungeonAsync(dungeonConfig,
|
||||
MapUid,
|
||||
gridComp,
|
||||
position,
|
||||
seed));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Content.Shared.Procedural.PostGeneration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BiomeDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(CP14BiomeDunGen dunGen,
|
||||
List<Dungeon> dungeons,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
{
|
||||
if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
|
||||
return;
|
||||
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
|
||||
var seed = random.Next();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var dun in dungeons)
|
||||
{
|
||||
foreach (var node in dun.AllTiles)
|
||||
{
|
||||
var tileRef = _maps.GetTileRef(_gridUid, _grid, node);
|
||||
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId]).ID))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId]).ID))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Need to set per-tile to override data.
|
||||
if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
|
||||
{
|
||||
_maps.SetTile(_gridUid, _grid, node, tile.Value);
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
|
||||
{
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
_decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _);
|
||||
}
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Tile, seed, (_gridUid, _grid), out var entityProto))
|
||||
{
|
||||
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
|
||||
var xform = xformQuery.Get(ent);
|
||||
|
||||
if (!xform.Comp.Anchored)
|
||||
{
|
||||
_transform.AnchorEntity(ent, xform);
|
||||
}
|
||||
|
||||
// TODO: Engine bug with SpawnAtPosition
|
||||
DebugTools.Assert(xform.Comp.Anchored);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
private async Task PostGen(CP14ReserveGrid dunGen,
|
||||
HashSet<Vector2i> reservedTiles)
|
||||
{
|
||||
var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
|
||||
while (tiles.MoveNext(out var tileRef))
|
||||
{
|
||||
var node = tileRef.Value.GridIndices;
|
||||
|
||||
reservedTiles.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld;
|
||||
|
||||
public sealed partial class CP14GlobalWorldSystem
|
||||
{
|
||||
private void GenerateGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
|
||||
{
|
||||
ent.Comp.Nodes.Clear();
|
||||
ent.Comp.Edges.Clear();
|
||||
|
||||
//For first - check station integration, and put station into (0,0) global map position
|
||||
if (TryComp<CP14StationGlobalWorldIntegrationComponent>(ent, out var integration) &&
|
||||
TryComp<StationDataComponent>(ent, out var stationData))
|
||||
{
|
||||
var largestStationGrid = _station.GetLargestGrid(stationData);
|
||||
|
||||
Debug.Assert(largestStationGrid is not null);
|
||||
|
||||
var mapId = _transform.GetMapId(largestStationGrid.Value);
|
||||
var zeroNode =
|
||||
new CP14GlobalWorldNode
|
||||
{
|
||||
MapUid = mapId,
|
||||
LocationConfig = integration.Location,
|
||||
Modifiers = integration.Modifiers,
|
||||
Level = 0,
|
||||
};
|
||||
GenerateNodeData(zeroNode);
|
||||
ent.Comp.Nodes.Add(
|
||||
Vector2i.Zero,
|
||||
zeroNode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var zeroNode = new CP14GlobalWorldNode();
|
||||
GenerateNodeData(zeroNode);
|
||||
ent.Comp.Nodes.Add(Vector2i.Zero, zeroNode);
|
||||
}
|
||||
|
||||
//Generate nodes with random data until limits
|
||||
while (ent.Comp.Nodes.Count < ent.Comp.LocationCount + 1)
|
||||
{
|
||||
// Get a random existing node
|
||||
var randomNode = _random.Pick(ent.Comp.Nodes);
|
||||
var randomNodePosition = randomNode.Key;
|
||||
|
||||
// Find a random empty adjacent position
|
||||
var directions = new[] { new Vector2i(1, 0), new Vector2i(-1, 0), new Vector2i(0, 1), new Vector2i(0, -1) };
|
||||
var emptyPositions = directions
|
||||
.Select(dir => randomNodePosition + dir)
|
||||
.Where(pos => !ent.Comp.Nodes.ContainsKey(pos))
|
||||
.ToList();
|
||||
|
||||
if (emptyPositions.Count == 0)
|
||||
continue;
|
||||
|
||||
var newPosition = emptyPositions[Random.Shared.Next(emptyPositions.Count)];
|
||||
|
||||
// Add the new node and connect it with an edge
|
||||
var newNode = new CP14GlobalWorldNode
|
||||
{
|
||||
Level = Math.Abs(newPosition.X) + Math.Abs(newPosition.Y),
|
||||
};
|
||||
GenerateNodeData(newNode);
|
||||
ent.Comp.Nodes.Add(newPosition, newNode);
|
||||
ent.Comp.Edges.Add((randomNodePosition, newPosition));
|
||||
|
||||
//Add connections to each other
|
||||
if (_proto.TryIndex(newNode.LocationConfig, out var indexedNewNodeLocation) && _proto.TryIndex(randomNode.Value.LocationConfig, out var indexedRandomNodeLocation))
|
||||
{
|
||||
newNode.Modifiers.Add(indexedNewNodeLocation.Connection);
|
||||
randomNode.Value.Modifiers.Add(indexedRandomNodeLocation.Connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateNodeData(CP14GlobalWorldNode node,
|
||||
bool overrideLocation = false,
|
||||
bool clearOldModifiers = false)
|
||||
{
|
||||
if (node.LocationConfig is null || overrideLocation)
|
||||
{
|
||||
var location = SelectLocation(node.Level);
|
||||
node.LocationConfig ??= location;
|
||||
}
|
||||
|
||||
if (!_proto.TryIndex(node.LocationConfig, out var indexedLocation))
|
||||
throw new Exception($"No location config found for node at level {node.Level}!");
|
||||
|
||||
var limits = new Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float>
|
||||
{
|
||||
{ "Danger", Math.Max(node.Level * 0.2f, 0.5f) },
|
||||
{ "GhostRoleDanger", 1f },
|
||||
{ "Reward", Math.Max(node.Level * 0.3f, 0.5f) },
|
||||
{ "Ore", Math.Max(node.Level * 0.5f, 1f) },
|
||||
{ "Fun", 1f },
|
||||
{ "Weather", 1f },
|
||||
{ "MapLight", 1f },
|
||||
};
|
||||
var mods = SelectModifiers(node.Level, indexedLocation, limits);
|
||||
|
||||
if (clearOldModifiers)
|
||||
node.Modifiers.Clear();
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
node.Modifiers.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a suitable location for the specified difficulty level.
|
||||
/// </summary>
|
||||
public CP14ProceduralLocationPrototype SelectLocation(int level)
|
||||
{
|
||||
CP14ProceduralLocationPrototype? selectedConfig = null;
|
||||
|
||||
HashSet<CP14ProceduralLocationPrototype> suitableConfigs = new();
|
||||
foreach (var locationConfig in _proto.EnumeratePrototypes<CP14ProceduralLocationPrototype>())
|
||||
{
|
||||
suitableConfigs.Add(locationConfig);
|
||||
}
|
||||
|
||||
while (suitableConfigs.Count > 0)
|
||||
{
|
||||
var randomConfig = _random.Pick(suitableConfigs);
|
||||
|
||||
var passed = true;
|
||||
|
||||
//Random prob filter
|
||||
if (passed)
|
||||
{
|
||||
if (!_random.Prob(randomConfig.GenerationProb))
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Levels filter
|
||||
if (passed)
|
||||
{
|
||||
if (level < randomConfig.Levels.Min || level > randomConfig.Levels.Max)
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!passed)
|
||||
{
|
||||
suitableConfigs.Remove(randomConfig);
|
||||
continue;
|
||||
}
|
||||
|
||||
selectedConfig = randomConfig;
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedConfig is null)
|
||||
throw new Exception($"No suitable procedural location config found for level {level}!");
|
||||
|
||||
return selectedConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a set of modifiers under the specified difficulty level that are appropriate for the specified location
|
||||
/// </summary>
|
||||
public List<CP14ProceduralModifierPrototype> SelectModifiers(
|
||||
int level,
|
||||
CP14ProceduralLocationPrototype location,
|
||||
Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float> modifierLimits)
|
||||
{
|
||||
List<CP14ProceduralModifierPrototype> selectedModifiers = new();
|
||||
|
||||
//Modifier generation
|
||||
Dictionary<CP14ProceduralModifierPrototype, float> suitableModifiersWeights = new();
|
||||
foreach (var modifier in _proto.EnumeratePrototypes<CP14ProceduralModifierPrototype>())
|
||||
{
|
||||
var passed = true;
|
||||
|
||||
//Random prob filter
|
||||
if (passed)
|
||||
{
|
||||
if (!_random.Prob(modifier.GenerationProb))
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Levels filter
|
||||
if (passed)
|
||||
{
|
||||
if (level < modifier.Levels.Min || level > modifier.Levels.Max)
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//Tag blacklist filter
|
||||
foreach (var configTag in location.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 (!location.Tags.Contains(reqTag))
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (passed)
|
||||
suitableModifiersWeights.Add(modifier, modifier.GenerationWeight);
|
||||
}
|
||||
|
||||
|
||||
//Limits calculation
|
||||
Dictionary<ProtoId<CP14ProceduralModifierCategoryPrototype>, float> limits = new();
|
||||
foreach (var limit in modifierLimits)
|
||||
{
|
||||
limits.Add(limit.Key, limit.Value);
|
||||
}
|
||||
|
||||
|
||||
while (suitableModifiersWeights.Count > 0)
|
||||
{
|
||||
var selectedModifier = ModifierPick(suitableModifiersWeights, _random);
|
||||
|
||||
//Fill location 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;
|
||||
|
||||
selectedModifiers.Add(selectedModifier);
|
||||
|
||||
foreach (var category in selectedModifier.Categories)
|
||||
{
|
||||
limits[category.Key] -= category.Value;
|
||||
}
|
||||
|
||||
if (selectedModifier.Unique)
|
||||
suitableModifiersWeights.Remove(selectedModifier);
|
||||
}
|
||||
|
||||
return selectedModifiers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimization moment: avoid re-indexing for weight selection
|
||||
/// </summary>
|
||||
private static CP14ProceduralModifierPrototype ModifierPick(
|
||||
Dictionary<CP14ProceduralModifierPrototype, 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!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Events;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Teleportation.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld;
|
||||
|
||||
public sealed partial class CP14GlobalWorldSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ShuttleSystem _shuttles = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly CP14LocationGenerationSystem _generation = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly LinkedEntitySystem _link = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = _logManager.GetSawmill("cp14_global_world");
|
||||
|
||||
SubscribeLocalEvent<CP14StationGlobalWorldComponent, StationPostInitEvent>(OnIntegratedPostInit);
|
||||
SubscribeLocalEvent<CP14LocationGeneratedEvent>(OnLocationGenerated);
|
||||
SubscribeLocalEvent<CP14StationGlobalWorldComponent, CP14GlobalWorldGeneratedEvent>(OnGlobalWorldGenerated);
|
||||
}
|
||||
|
||||
private void OnLocationGenerated(CP14LocationGeneratedEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<CP14StationGlobalWorldComponent>();
|
||||
while (query.MoveNext(out var ent, out var comp))
|
||||
{
|
||||
//Theres no support for multiple GlobalWorld
|
||||
if (comp.LocationInGeneration.Contains(args.JobName))
|
||||
{
|
||||
comp.LocationInGeneration.Remove(args.JobName);
|
||||
_sawmill.Debug($"Location {args.JobName} generated successfully. Remaining: {comp.LocationInGeneration.Count}");
|
||||
}
|
||||
|
||||
if (comp.LocationInGeneration.Count == 0)
|
||||
{
|
||||
//All locations are generated, we can now spawn the global world map
|
||||
_sawmill.Debug("All locations generated, spawning global world map.");
|
||||
var ev = new CP14GlobalWorldGeneratedEvent();
|
||||
RaiseLocalEvent(ent, ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIntegratedPostInit(Entity<CP14StationGlobalWorldComponent> ent, ref StationPostInitEvent args)
|
||||
{
|
||||
GenerateGlobalWorldMap(ent);
|
||||
SpawnGlobalWorldMap(ent);
|
||||
}
|
||||
|
||||
private void OnGlobalWorldGenerated(Entity<CP14StationGlobalWorldComponent> ent, ref CP14GlobalWorldGeneratedEvent ev)
|
||||
{
|
||||
ConnectGlobalWorldMap(ent);
|
||||
}
|
||||
|
||||
private void SpawnGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
|
||||
{
|
||||
foreach (var (position, node) in ent.Comp.Nodes)
|
||||
{
|
||||
if (node.LocationConfig is null)
|
||||
continue;
|
||||
|
||||
if (node.MapUid is null)
|
||||
{
|
||||
_map.CreateMap(out var newMapId, runMapInit: false);
|
||||
node.MapUid = newMapId;
|
||||
}
|
||||
|
||||
var mapId = node.MapUid.Value;
|
||||
var mapUid = _map.GetMap(mapId);
|
||||
var jobName = $"job_GW_{position}";
|
||||
ent.Comp.LocationInGeneration.Add(jobName);
|
||||
|
||||
_generation.GenerateLocation(
|
||||
mapUid,
|
||||
mapId,
|
||||
node.LocationConfig.Value,
|
||||
node.Modifiers,
|
||||
jobName: jobName
|
||||
);
|
||||
|
||||
// Avoid renaming the settlement
|
||||
if (position != Vector2i.Zero)
|
||||
{
|
||||
var newName = $"{position} - {node.LocationConfig.Value}";
|
||||
_meta.SetEntityName(mapUid, newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectGlobalWorldMap(Entity<CP14StationGlobalWorldComponent> ent)
|
||||
{
|
||||
foreach (var edge in ent.Comp.Edges)
|
||||
{
|
||||
var firstNodeMapUid = ent.Comp.Nodes[edge.Item1].MapUid;
|
||||
var secondNodeMapUid = ent.Comp.Nodes[edge.Item2].MapUid;
|
||||
|
||||
EntityUid? firstConnector = null;
|
||||
EntityUid? secondConnector = null;
|
||||
|
||||
//Get random connector from map
|
||||
var query = EntityQueryEnumerator<CP14GlobalWorldConnectorComponent>();
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
{
|
||||
if (_transform.GetMapId(uid) == firstNodeMapUid)
|
||||
firstConnector = uid;
|
||||
|
||||
if (_transform.GetMapId(uid) == secondNodeMapUid)
|
||||
secondConnector = uid;
|
||||
|
||||
}
|
||||
|
||||
if (firstConnector == null || secondConnector == null)
|
||||
{
|
||||
_sawmill.Error($"Failed to find connectors for edge {edge.Item1} - {edge.Item2}. " +
|
||||
$"First: {firstConnector}, Second: {secondConnector}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var firstPortal = Spawn("CP14LocationPassway", Transform(firstConnector.Value).Coordinates);
|
||||
var secondPortal = Spawn("CP14LocationPassway", Transform(secondConnector.Value).Coordinates);
|
||||
|
||||
_link.TryLink(firstPortal, secondPortal, true);
|
||||
|
||||
Del(firstConnector);
|
||||
Del(secondConnector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CP14GlobalWorldGeneratedEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The GlobalWorld system connects worlds by creating connecting portals at the location of these markers.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
|
||||
public sealed partial class CP14GlobalWorldConnectorComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the surrounding procedural world on the game map, surrounding the mapped settlement.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
|
||||
public sealed partial class CP14StationGlobalWorldComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<Vector2i, CP14GlobalWorldNode> Nodes = new();
|
||||
|
||||
[DataField]
|
||||
public HashSet<(Vector2i, Vector2i)> Edges = new();
|
||||
|
||||
[DataField]
|
||||
public int LocationCount = 5;
|
||||
|
||||
/// <summary>
|
||||
/// A list of jobName names that are waiting for generation to complete. This is a hack, but I don't know a better way to do it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> LocationInGeneration = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class CP14GlobalWorldNode()
|
||||
{
|
||||
public MapId? MapUid;
|
||||
public int Level = 0;
|
||||
|
||||
public ProtoId<CP14ProceduralLocationPrototype>? LocationConfig;
|
||||
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = new();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared._CP14.Procedural.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._CP14.Procedural.GlobalWorld.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the surrounding procedural world on the game map, surrounding the mapped settlement.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14GlobalWorldSystem))]
|
||||
public sealed partial class CP14StationGlobalWorldIntegrationComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<CP14ProceduralLocationPrototype> Location;
|
||||
|
||||
[DataField]
|
||||
public List<ProtoId<CP14ProceduralModifierPrototype>> Modifiers = [];
|
||||
|
||||
[DataField]
|
||||
public Vector2i GenerationOffset = Vector2i.Zero;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
using Content.Server._CP14.Demiplane;
|
||||
|
||||
namespace Content.Server._CP14.RoundEnd;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14DemiplaneSystem))]
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14MagicContainerRoundFinisherComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Content.Server._CP14.Demiplane;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Shared._CP14.MagicEnergy;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -15,7 +13,6 @@ public sealed partial class CP14RoundEndSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly CP14DemiplaneSystem _demiplane = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
@@ -49,7 +46,6 @@ public sealed partial class CP14RoundEndSystem : EntitySystem
|
||||
if (_roundEndMoment > _timing.CurTime)
|
||||
return;
|
||||
|
||||
_demiplane.DeleteAllDemiplanes();
|
||||
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cp14-round-end"),
|
||||
announcementSound: new SoundPathSpecifier("/Audio/_CP14/Announce/event_boom.ogg"));
|
||||
_roundEnd.EndRound();
|
||||
|
||||
Reference in New Issue
Block a user