Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions Content.Server/_Mono/POI/POICaptureSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
using Content.Server.Radio.EntitySystems;
using Content.Shared._Mono.POI.Components;
using Content.Shared.Access.Components;
using Content.Shared._NF.Shipyard.Components;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Popups;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;

namespace Content.Server._Mono.POI;

/// <summary>
/// Handles active POI capture progress.
/// Runs once per second and only checks active captures.
/// </summary>
public sealed class POICaptureSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;


private TimeSpan _nextUpdate;


public override void Initialize()
{
base.Initialize();

_nextUpdate = _gameTiming.CurTime;
}


public override void Update(float frameTime)
{
if (_gameTiming.CurTime < _nextUpdate)
return;


_nextUpdate = _gameTiming.CurTime + TimeSpan.FromSeconds(1);


var query = EntityQueryEnumerator<POICaptureComponent>();

while (query.MoveNext(out var uid, out var capture))
{
TickCapture(uid, capture);
}
}


private void TickCapture(
EntityUid uid,
POICaptureComponent capture)
{
if (capture.CapturingEntity == null)
{
CancelCapture(uid);
return;
}


if (!TryComp<CapturablePOIComponent>(uid, out var poi))
{
CancelCapture(uid);
return;
}


//
// Initial capture announcement
//
if (capture.LastBroadcastPercent == -1)
{
var playerName = "Unknown";
var factionName = "Unknown";


if (capture.CapturingEntity is { } capturer)
playerName = Name(capturer);


if (capture.CapturingIdCard is { } idCard &&
TryComp<IdCardComponent>(idCard, out var card))
{
if (_prototype.TryIndex(card.Faction, out var faction))
factionName = faction.Name;
else
factionName = card.Faction;
}


_radio.SendRadioMessage(
uid,
$"{playerName} for faction {factionName} is attempting to capture {Name(uid)}. Stand by.",
"Traffic",
uid);


capture.LastBroadcastPercent = 0;
}


var elapsed =
_gameTiming.CurTime - capture.CaptureStart;


var progress =
(float)(elapsed.TotalSeconds /
capture.CaptureDuration.TotalSeconds) * 100f;


poi.CaptureProgress = Math.Clamp(progress, 0f, 100f);
poi.IsBeingCaptured = true;


Dirty(uid, poi);


//
// Progress announcements every 20%
//
var broadcast =
(int)(poi.CaptureProgress / 20) * 20;


if (broadcast > capture.LastBroadcastPercent)
{
capture.LastBroadcastPercent = broadcast;


_radio.SendRadioMessage(
uid,
$"{Name(uid)} capture progress: {broadcast}%.",
"Traffic",
uid);
}


if (poi.CaptureProgress >= 100f)
{
CompleteCapture(uid, poi, capture);
}
}


private void CompleteCapture(
EntityUid uid,
CapturablePOIComponent poi,
POICaptureComponent capture)
{
poi.OwnerFaction = "Rogue";

poi.CaptureProgress = 0;
poi.IsBeingCaptured = false;


Dirty(uid, poi);


var playerName = "Unknown";
var factionName = "Unknown";


if (capture.CapturingEntity is { } capturer)
playerName = Name(capturer);


if (capture.CapturingIdCard is { } idCard &&
TryComp<IdCardComponent>(idCard, out var card))
{
if (_prototype.TryIndex(card.Faction, out var faction))
factionName = faction.Name;
else
factionName = card.Faction;


//
// Remove previous ownership deeds
//
RemoveExistingDeeds(uid);


//
// Assign new ownership deed
//
var deed = EnsureComp<ShuttleDeedComponent>(idCard);

deed.ShuttleUid = uid;
deed.ShuttleName = Name(uid);
deed.ShuttleOwner = card.FullName ?? "Unknown";
deed.DeedHolder = idCard;
}


_radio.SendRadioMessage(
uid,
$"{Name(uid)} has been captured by {playerName} for faction {factionName}.",
"Traffic",
uid);


_popup.PopupEntity(
$"{Name(uid)} capture complete. Ownership transferred.",
uid);


RemComp<POICaptureComponent>(uid);
}


/// <summary>
/// Removes any existing deeds that reference this POI.
/// Ensures only one ID card owns the captured location.
/// </summary>
private void RemoveExistingDeeds(EntityUid poiUid)
{
var query = EntityQueryEnumerator<ShuttleDeedComponent>();

while (query.MoveNext(out var deedUid, out var deed))
{
if (deed.ShuttleUid != poiUid)
continue;

RemComp<ShuttleDeedComponent>(deedUid);
}
}


private void CancelCapture(EntityUid uid)
{
if (TryComp<CapturablePOIComponent>(uid, out var poi))
{
poi.CaptureProgress = 0;
poi.IsBeingCaptured = false;

Dirty(uid, poi);
}


RemComp<POICaptureComponent>(uid);
}
}
14 changes: 12 additions & 2 deletions Content.Shared/Access/Components/IdCardComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Content.Shared._Mono.Company;
using Content.Shared._Mono.POI.Systems;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Access.Systems;
using Content.Shared.PDA;
using Content.Shared.Roles;
Expand All @@ -11,7 +13,7 @@ namespace Content.Shared.Access.Components;

[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), typeof(CapturableShuttleConsoleSystem), Other = AccessPermissions.ReadWrite)]
public sealed partial class IdCardComponent : Component
{
[DataField]
Expand Down Expand Up @@ -46,12 +48,20 @@ public sealed partial class IdCardComponent : Component
public List<ProtoId<DepartmentPrototype>> JobDepartments = new();

/// <summary>
/// The company name associated with this ID card
/// The company name associated with this ID card.
/// </summary>
[DataField]
[AutoNetworkedField]
public ProtoId<CompanyPrototype> CompanyName = "None";

/// <summary>
/// The NPC faction associated with this ID card.
/// Used for POI capture, IFF and faction-based systems.
/// </summary>
[DataField]
[AutoNetworkedField]
public ProtoId<NpcFactionPrototype> Faction = "CC";

/// <summary>
/// Determines if accesses from this card should be logged by <see cref="AccessReaderComponent"/>
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion Content.Shared/NPC/Prototypes/NpcFactionPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ public sealed partial class NpcFactionPrototype : IPrototype
/// Mono - Is this faction checked for default hostility?
/// </summary>
[DataField]
public bool DefaultHostileIncluded = true;

public bool DefaultHostileIncluded = true;
/// <summary>
/// Display name of the faction.
/// </summary>
[DataField]
public string Name { get; private set; } = string.Empty;
}

/// <summary>
Expand Down
58 changes: 58 additions & 0 deletions Content.Shared/_Mono/POI/Components/CapturablePOIComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Robust.Shared.GameStates;

namespace Content.Shared._Mono.POI.Components;

/// <summary>
/// Marks an entity as a capturable point of interest.
/// Stores the permanent ownership state of the POI.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CapturablePOIComponent : Component
{
/// <summary>
/// Current owning faction/company of this POI.
/// Example:
/// TSF
/// USSP
/// Pirates
/// Rogue
/// </summary>
[DataField]
[AutoNetworkedField]
public string? OwnerFaction;


/// <summary>
/// Current owner name.
/// Used for ship deed ownership text.
/// </summary>
[DataField]
[AutoNetworkedField]
public string? OwnerName;


/// <summary>
/// Display name of the captured entity.
/// Used for shuttle deed naming.
/// </summary>
[DataField]
[AutoNetworkedField]
public string? POIName;


/// <summary>
/// Current capture progress percentage.
/// 0-100.
/// </summary>
[DataField]
[AutoNetworkedField]
public float CaptureProgress;


/// <summary>
/// True while a capture is active.
/// </summary>
[DataField]
[AutoNetworkedField]
public bool IsBeingCaptured;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Content.Shared._Mono.POI.Systems;
using Robust.Shared.GameStates;

namespace Content.Shared._Mono.POI.Components;

/// <summary>
/// Component for a capturable POI console.
/// Stores configuration and the currently inserted ID card.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(CapturableShuttleConsoleSystem))]
public sealed partial class CapturableShuttleConsoleComponent : Component
{
/// <summary>
/// Time required to complete a capture in seconds.
/// </summary>
[DataField]
public float CaptureTime = 6f;

/// <summary>
/// Percentage interval between capture progress announcements.
/// </summary>
[DataField]
public int BroadcastInterval = 2;

/// <summary>
/// Name of the ID card container slot.
/// Must match the slot name in the YAML.
/// </summary>
[DataField]
public string IdSlot = "id_slot";

/// <summary>
/// The ID card currently inserted into the console.
/// Set by EntInsertedIntoContainerMessage and
/// cleared by EntRemovedFromContainerMessage.
/// </summary>
public EntityUid? InsertedId;
}
Loading
Loading