Skip to content

Commit

Permalink
Merge branch 'Anchor' of https://github.com/fenndragon/floofstation1
Browse files Browse the repository at this point in the history
…into Anchor
  • Loading branch information
fenndragon committed Dec 21, 2024
2 parents 0ec437d + 8cb00df commit a78411a
Show file tree
Hide file tree
Showing 16 changed files with 376 additions and 13 deletions.
35 changes: 31 additions & 4 deletions Content.Server/Chemistry/TileReactions/CleanTileReaction.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
Expand All @@ -7,6 +8,9 @@
using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using System.Linq;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FootPrint;


namespace Content.Server.Chemistry.TileReactions;

Expand Down Expand Up @@ -42,11 +46,9 @@ FixedPoint2 ITileReaction.TileReact(TileRef tile, ReagentPrototype reagent, Fixe

foreach (var entity in entities)
{
if (!puddleQuery.TryGetComponent(entity, out var puddle) ||
!solutionContainerSystem.TryGetSolution(entity, puddle.SolutionName, out var puddleSolution, out _))
{
// Floof - separated this into a separate function to incroporate cleaning footprints
if (!TryGetCleanableSolution(entity, entMan, solutionContainerSystem, out var puddleSolution))
continue;
}

var purgeable = solutionContainerSystem.SplitSolutionWithout(puddleSolution.Value, purgeAmount, ReplacementReagent, reagent.ID);

Expand All @@ -60,4 +62,29 @@ FixedPoint2 ITileReaction.TileReact(TileRef tile, ReagentPrototype reagent, Fixe

return (reactVolume / CleanAmountMultiplier - purgeAmount) * CleanAmountMultiplier;
}

// Floof
private bool TryGetCleanableSolution(
EntityUid entity,
IEntityManager entMan,
SharedSolutionContainerSystem solutionContainerSystem,
[NotNullWhen(true)] out Entity<SolutionComponent>? solution)
{
solution = default;
if (entMan.TryGetComponent<PuddleComponent>(entity, out var puddle) &&
solutionContainerSystem.TryGetSolution(entity, puddle.SolutionName, out var puddleSolution, out _))
{
solution = puddleSolution;
return true;
}

if (entMan.TryGetComponent<FootPrintComponent>(entity, out var footPrint) &&
solutionContainerSystem.TryGetSolution(entity, footPrint.SolutionName, out var footPrintSolution, out _))
{
solution = footPrintSolution;
return true;
}

return false;
}
}
141 changes: 141 additions & 0 deletions Content.Server/FloofStation/GameTicking/StationEventCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.Mind;
using Content.Server.StationEvents;
using Content.Server.StationEvents.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;

namespace Content.Server.FloofStation.GameTicking;

/// <summary>
/// Represents an abstract condition required for a station event to be chosen from the random event pool.
/// </summary>
/// <remarks>
/// Implementations should avoid performing expensive checks.
/// Any data that may be expensive to compute should instead be precomputed and stored in <see cref="Dependencies"/>
/// </remarks>
[Serializable, ImplicitDataDefinitionForInheritors]
public abstract partial class StationEventCondition
{
/// <summary>
/// If true, the event will only be run if this condition is NOT met.
/// </summary>
[DataField]
public bool Inverted = false;

public abstract bool IsMet(EntityPrototype proto, StationEventComponent component, Dependencies dependencies);

/// <summary>
/// Entity system and other dependencies used by station event conditions.
/// GameTicker allocates an instance of this before passing it to all events.
/// </summary>
public sealed class Dependencies(IEntityManager entMan, GameTicker ticker, EventManagerSystem eventManager)
{
public ISawmill Log = Logger.GetSawmill("station-event-conditions");

public IEntityManager EntMan => entMan;
public GameTicker Ticker => ticker;
public EventManagerSystem EventManager => eventManager;

public MindSystem Minds = default!;
public SharedIdCardSystem IdCard = default!;

[Dependency] public IPrototypeManager ProtoMan = default!;
[Dependency] public IRobustRandom Random = default!;
[Dependency] public IPlayerManager PlayerManager = default!;

/// <summary>
/// The list of all players along with their jobs.
/// </summary>
public List<(ICommonSession session, EntityUid uid, ProtoId<JobPrototype> job)> Players = new();
public Dictionary<ProtoId<JobPrototype>, int> JobCounts = new();
public Dictionary<ProtoId<DepartmentPrototype>, int> DeptCounts = new();

// Lookups
private readonly Dictionary<string, ProtoId<JobPrototype>> _jobTitleToPrototype = new();
private readonly Dictionary<ProtoId<JobPrototype>, List<ProtoId<DepartmentPrototype>>> _jobToDepts = new();

/// <summary>
/// Called once after the instantiation of the class.
/// </summary>
public void Initialize()
{
IoCManager.InjectDependencies(this);

// We cannot use entity system dependencies outside of ESC context.
IdCard = EntMan.System<SharedIdCardSystem>();
Minds = EntMan.System<MindSystem>();

// Build the inverse lookups - SharedJobSystem contains methods that iterate over all of those lists each time,
// Resulting in an O(n^2 * m) performance cost for each update() call.
foreach (var job in ProtoMan.EnumeratePrototypes<JobPrototype>())
{
_jobTitleToPrototype[job.LocalizedName] = job.ID;

var depts = ProtoMan.EnumeratePrototypes<DepartmentPrototype>()
.Where(it => it.Roles.Contains(job.ID))
.Select(it => new ProtoId<DepartmentPrototype>(it.ID))
.ToList();

_jobToDepts[job.ID] = depts;
}
}

/// <summary>
/// Called once shortly before passing this object to IsMet() to collect the necessary data about the round.
/// </summary>
public void Update()
{
JobCounts.Clear();
DeptCounts.Clear();

// Collect data about the jobs of the players in the round
Players.Clear();
foreach (var session in PlayerManager.Sessions)
{
if (session.AttachedEntity is not {} player
|| session.Status is SessionStatus.Zombie or SessionStatus.Disconnected
|| !Minds.TryGetMind(session, out var mind, out var mindComponent))
continue;

ProtoId<JobPrototype> job = default;
// 1: Try to get the job from the ID the person holds
if (IdCard.TryFindIdCard(player, out var idCard) && idCard.Comp.JobTitle is {} jobTitle)
_jobTitleToPrototype.TryGetValue(jobTitle, out job);

// 2: If failed, try to fetch it from the mind component instead
if (job == default
&& EntMan.TryGetComponent<JobComponent>(mind, out var jobComp)
&& jobComp.Prototype is {} mindJobProto
)
job = mindJobProto;

// If both have failed, skip the player
if (job == default)
continue;

// Update the info
Players.Add((session, player, job));
JobCounts[job] = JobCounts.GetValueOrDefault(job, 0) + 1;
// Increment the number of players in each dept this job belongs to
if (_jobToDepts.TryGetValue(job, out var depts))
{
foreach (var dept in depts)
DeptCounts[dept] = DeptCounts.GetValueOrDefault(dept, 0) + 1;
}
}

#if DEBUG
Log.Debug($"Event conditions data: Job counts: {string.Join(", ", JobCounts)}");
Log.Debug($"Dept counts: {string.Join(", ", DeptCounts)}");
#endif
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Content.Server.StationEvents.Components;
using Content.Shared.InteractionVerbs;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;

namespace Content.Server.FloofStation.GameTicking;

/// <summary>
/// A condition that requires a number of players to be present in a specific department.
/// </summary>
/// <example><code>
/// - !type:DepartmentCountCondition
/// department: Security
/// range: {min: 5}
/// </code></example>
[Serializable]
public sealed partial class DepartmentCountCondition : StationEventCondition
{
[DataField(required: true)]
public ProtoId<DepartmentPrototype> Department;

[DataField(required: true)]
public InteractionVerbPrototype.RangeSpecifier Range;

public override bool IsMet(EntityPrototype proto, StationEventComponent component, Dependencies dependencies)
{
var count = dependencies.DeptCounts.GetValueOrDefault(Department, 0);
return Range.IsInRange(count);
}
}

/// <summary>
/// Same as <see cref="DepartmentCountCondition"/>, but for specific jobs.
/// </summary>
[Serializable]
public sealed partial class JobCountCondition : StationEventCondition
{
[DataField(required: true)]
public ProtoId<JobPrototype> Job;

[DataField(required: true)]
public InteractionVerbPrototype.RangeSpecifier Range;

public override bool IsMet(EntityPrototype proto, StationEventComponent component, Dependencies dependencies)
{
var count = dependencies.JobCounts.GetValueOrDefault(Job, 0);
return Range.IsInRange(count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Linq;
using Content.Server.StationEvents.Components;
using Robust.Shared.Prototypes;

namespace Content.Server.FloofStation.GameTicking;

/// <summary>
/// Combines a number of other conditions in a boolean AND or a boolean OR.
/// </summary>
/// <example>
/// <code>
/// - !type:ComplexCondition
/// requireAll: true
/// conditions:
/// - !type:SomeCondition1
/// ...
/// - !type:SomeCondition2
/// ...
/// </code>
/// </example>
[Serializable]
public sealed partial class ComplexCondition : StationEventCondition
{
/// <summary>
/// If true, this condition acts as a boolean AND. If false, it acts as a boolean OR.
/// </summary>
[DataField]
public bool RequireAll = false;

[DataField(required: true)]
public List<StationEventCondition> Conditions = new();

public override bool IsMet(EntityPrototype proto, StationEventComponent component, Dependencies dependencies) =>
RequireAll
? Conditions.All(it => it.Inverted ^ it.IsMet(proto, component, dependencies))
: Conditions.Any(it => it.Inverted ^ it.IsMet(proto, component, dependencies));
}
16 changes: 11 additions & 5 deletions Content.Server/FootPrint/FootPrintsSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Gravity;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
Expand All @@ -9,6 +10,7 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Forensics;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;

namespace Content.Server.FootPrint;
Expand All @@ -22,7 +24,7 @@ public sealed class FootPrintsSystem : EntitySystem
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!; // Floof

private EntityQuery<TransformComponent> _transformQuery;
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery;
Expand All @@ -49,10 +51,12 @@ private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, Co

private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args)
{
if (component.PrintsColor.A <= .2f) // avoid creating footsteps that are invisible
// Floof: clear stored DNAs if footprints are now invisible
if (component.PrintsColor.A <= .3f)
component.DNAs.Clear();

if (component.PrintsColor.A <= .2f
if (component.PrintsColor.A <= .3f // avoid creating footsteps that are invisible
|| TryComp<PhysicsComponent>(uid, out var physics) && physics.BodyStatus != BodyStatus.OnGround // Floof: do not create footprints if the entity is flying
|| !_transformQuery.TryComp(uid, out var transform)
|| !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds)
|| !_map.TryFindGridAt(_transform.GetMapCoordinates((uid, transform)), out var gridUid, out _))
Expand All @@ -66,17 +70,19 @@ private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent
if (!(distance > stepSize))
return;

// Floof section
var entities = _lookup.GetEntitiesIntersecting(uid, LookupFlags.All);
foreach (var entityUid in entities.Where(entityUid => HasComp<PuddleFootPrintsComponent>(entityUid)))
return; //are we on a puddle? we exit, ideally we would exchange liquid and DNA with the puddle but meh, too lazy to do that now.
return; // are we on a puddle? we exit, ideally we would exchange liquid and DNA with the puddle but meh, too lazy to do that now.

component.RightStep = !component.RightStep;

var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging));
var footPrintComponent = EnsureComp<FootPrintComponent>(entity);

var forensics = EntityManager.EnsureComponent<ForensicsComponent>(entity);
forensics.DNAs.UnionWith(component.DNAs);
if (TryComp<ForensicsComponent>(uid, out var ownerForensics)) // Floof edit: transfer owner DNA into the footsteps
forensics.DNAs.UnionWith(ownerForensics.DNAs);

footPrintComponent.PrintOwner = uid;
Dirty(entity, footPrintComponent);
Expand Down
6 changes: 6 additions & 0 deletions Content.Server/Medical/DefibrillatorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Content.Server.Ghost;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Server.Traits.Assorted;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
Expand Down Expand Up @@ -213,6 +214,11 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
InGameICChatType.Speak, true);
}
else if (HasComp<UnrevivableComponent>(target))
{
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-unrevivable"),
InGameICChatType.Speak, true);
}
else
{
if (_mobState.IsDead(target, mob))
Expand Down
11 changes: 11 additions & 0 deletions Content.Server/StationEvents/Components/StationEventComponent.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Content.Server.FloofStation.GameTicking;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

Expand Down Expand Up @@ -82,4 +83,14 @@ public sealed partial class StationEventComponent : Component
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan? EndTime;

// Floof section - custom conditions

/// <summary>
/// A list of conditions that must be met for the event to run.
/// </summary>
[DataField]
public List<StationEventCondition>? Conditions;

// Floof section end
}
Loading

0 comments on commit a78411a

Please sign in to comment.