diff --git a/Content.Server/Objectives/Components/StealAreaComponent.cs b/Content.Server/Objectives/Components/StealAreaComponent.cs
new file mode 100644
index 0000000000..26e752f2f2
--- /dev/null
+++ b/Content.Server/Objectives/Components/StealAreaComponent.cs
@@ -0,0 +1,23 @@
+using Content.Server.Objectives.Systems;
+using Content.Server.Thief.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+///
+/// An abstract component that allows other systems to count adjacent objects as "stolen" when controlling other systems
+///
+[RegisterComponent, Access(typeof(StealConditionSystem), typeof(ThiefBeaconSystem))]
+public sealed partial class StealAreaComponent : Component
+{
+ [DataField]
+ public bool Enabled = true;
+
+ [DataField]
+ public float Range = 1f;
+
+ ///
+ /// all the minds that will be credited with stealing from this area.
+ ///
+ [DataField]
+ public HashSet Owners = new();
+}
diff --git a/Content.Server/Objectives/Components/StealConditionComponent.cs b/Content.Server/Objectives/Components/StealConditionComponent.cs
index f8d68063f8..cdade8cc8d 100644
--- a/Content.Server/Objectives/Components/StealConditionComponent.cs
+++ b/Content.Server/Objectives/Components/StealConditionComponent.cs
@@ -22,11 +22,18 @@ public sealed partial class StealConditionComponent : Component
[DataField]
public bool VerifyMapExistence = true;
+ ///
+ /// If true, counts objects that are close to steal areas.
+ ///
+ [DataField]
+ public bool CheckStealAreas = false;
+
///
/// If the target may be alive but has died, it will not be counted
///
[DataField]
public bool CheckAlive = false;
+
///
/// The minimum number of items you need to steal to fulfill a objective
///
diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs
index 42a296ab92..2c9244cf7d 100644
--- a/Content.Server/Objectives/Systems/StealConditionSystem.cs
+++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs
@@ -21,16 +21,15 @@ public sealed class StealConditionSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
private EntityQuery _containerQuery;
- private EntityQuery _metaQuery;
public override void Initialize()
{
base.Initialize();
_containerQuery = GetEntityQuery();
- _metaQuery = GetEntityQuery();
SubscribeLocalEvent(OnAssigned);
SubscribeLocalEvent(OnAfterAssign);
@@ -96,25 +95,33 @@ public sealed class StealConditionSystem : EntitySystem
if (!_containerQuery.TryGetComponent(mind.OwnedEntity, out var currentManager))
return 0;
- var stack = new Stack();
+ var containerStack = new Stack();
var count = 0;
+ //check stealAreas
+ if (condition.CheckStealAreas)
+ {
+ var areasQuery = AllEntityQuery();
+ while (areasQuery.MoveNext(out var uid, out var area))
+ {
+ if (!area.Owners.Contains(mind.Owner))
+ continue;
+
+ var nearestEnt = _lookup.GetEntitiesInRange(uid, area.Range);
+ foreach (var ent in nearestEnt)
+ {
+ CheckEntity(ent, condition, ref containerStack, ref count);
+ }
+ }
+ }
+
//check pulling object
if (TryComp(mind.OwnedEntity, out var pull)) //TO DO: to make the code prettier? don't like the repetition
{
var pulledEntity = pull.Pulling;
if (pulledEntity != null)
{
- // check if this is the item
- count += CheckStealTarget(pulledEntity.Value, condition);
-
- //we don't check the inventories of sentient entity
- if (!HasComp(pulledEntity))
- {
- // if it is a container check its contents
- if (_containerQuery.TryGetComponent(pulledEntity, out var containerManager))
- stack.Push(containerManager);
- }
+ CheckEntity(pulledEntity.Value, condition, ref containerStack, ref count);
}
}
@@ -131,16 +138,30 @@ public sealed class StealConditionSystem : EntitySystem
// if it is a container check its contents
if (_containerQuery.TryGetComponent(entity, out var containerManager))
- stack.Push(containerManager);
+ containerStack.Push(containerManager);
}
}
- } while (stack.TryPop(out currentManager));
+ } while (containerStack.TryPop(out currentManager));
var result = count / (float) condition.CollectionSize;
result = Math.Clamp(result, 0, 1);
return result;
}
+ private void CheckEntity(EntityUid entity, StealConditionComponent condition, ref Stack containerStack, ref int counter)
+ {
+ // check if this is the item
+ counter += CheckStealTarget(entity, condition);
+
+ //we don't check the inventories of sentient entity
+ if (!TryComp(entity, out var pullMind))
+ {
+ // if it is a container check its contents
+ if (_containerQuery.TryGetComponent(entity, out var containerManager))
+ containerStack.Push(containerManager);
+ }
+ }
+
private int CheckStealTarget(EntityUid entity, StealConditionComponent condition)
{
// check if this is the target
diff --git a/Content.Server/Thief/Components/ThiefBeaconComponent.cs b/Content.Server/Thief/Components/ThiefBeaconComponent.cs
new file mode 100644
index 0000000000..65db79f861
--- /dev/null
+++ b/Content.Server/Thief/Components/ThiefBeaconComponent.cs
@@ -0,0 +1,17 @@
+using Content.Server.Thief.Systems;
+using Robust.Shared.Audio;
+
+namespace Content.Server.Thief.Components;
+
+///
+/// working together with StealAreaComponent, allows the thief to count objects near the beacon as stolen when setting up.
+///
+[RegisterComponent, Access(typeof(ThiefBeaconSystem))]
+public sealed partial class ThiefBeaconComponent : Component
+{
+ [DataField]
+ public SoundSpecifier LinkSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg");
+
+ [DataField]
+ public SoundSpecifier UnlinkSound = new SoundPathSpecifier("/Audio/Machines/beep.ogg");
+}
diff --git a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs
new file mode 100644
index 0000000000..e004212124
--- /dev/null
+++ b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs
@@ -0,0 +1,95 @@
+using Content.Server.Mind;
+using Content.Server.Objectives.Components;
+using Content.Server.Roles;
+using Content.Server.Thief.Components;
+using Content.Shared.Examine;
+using Content.Shared.Foldable;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Thief.Systems;
+
+///
+///
+///
+public sealed class ThiefBeaconSystem : EntitySystem
+{
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent>(OnGetInteractionVerbs);
+ SubscribeLocalEvent(OnFolded);
+ SubscribeLocalEvent(OnExamined);
+ }
+
+ private void OnGetInteractionVerbs(Entity beacon, ref GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract || args.Hands is null)
+ return;
+
+ if (TryComp(beacon, out var foldable) && foldable.IsFolded)
+ return;
+
+ var mind = _mind.GetMind(args.User);
+ if (!HasComp(mind))
+ return;
+
+ var user = args.User;
+ args.Verbs.Add(new()
+ {
+ Act = () =>
+ {
+ SetCoordinate(beacon, mind.Value);
+ },
+ Message = Loc.GetString("thief-fulton-verb-message"),
+ Text = Loc.GetString("thief-fulton-verb-text"),
+ });
+ }
+
+ private void OnFolded(Entity beacon, ref FoldedEvent args)
+ {
+ if (args.IsFolded)
+ ClearCoordinate(beacon);
+ }
+
+ private void OnExamined(Entity beacon, ref ExaminedEvent args)
+ {
+ if (!TryComp(beacon, out var area))
+ return;
+
+ args.PushText(Loc.GetString(area.Owners.Count == 0
+ ? "thief-fulton-examined-unset"
+ : "thief-fulton-examined-set"));
+ }
+
+ private void SetCoordinate(Entity beacon, EntityUid mind)
+ {
+ if (!TryComp(beacon, out var area))
+ return;
+
+ _audio.PlayPvs(beacon.Comp.LinkSound, beacon);
+ _popup.PopupEntity(Loc.GetString("thief-fulton-set"), beacon);
+ area.Owners.Clear(); //We only reconfigure the beacon for ourselves, we don't need multiple thieves to steal from the same beacon.
+ area.Owners.Add(mind);
+ }
+
+ private void ClearCoordinate(Entity beacon)
+ {
+ if (!TryComp(beacon, out var area))
+ return;
+
+ if (area.Owners.Count == 0)
+ return;
+
+ _audio.PlayPvs(beacon.Comp.UnlinkSound, beacon);
+ _popup.PopupEntity(Loc.GetString("thief-fulton-clear"), beacon);
+ area.Owners.Clear();
+ }
+}
diff --git a/Resources/Locale/en-US/thief/beacon.ftl b/Resources/Locale/en-US/thief/beacon.ftl
new file mode 100644
index 0000000000..9fbbcaf419
--- /dev/null
+++ b/Resources/Locale/en-US/thief/beacon.ftl
@@ -0,0 +1,8 @@
+thief-fulton-set = Delivery coordinates are set.
+thief-fulton-clear = Delivery coordinates cleared.
+
+thief-fulton-examined-set = Coordinates entered. Bluespace teleportation of the nearest objects will be performed when the evacuation shuttle departs.
+thief-fulton-examined-unset = Beacon coordinates are not set.
+
+thief-fulton-verb-text = Set coordinates
+thief-fulton-verb-message = Set the coordinates of your thief's hideout, where all nearby items will be sent at the end of the round.
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml b/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml
new file mode 100644
index 0000000000..c35e66127d
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml
@@ -0,0 +1,38 @@
+- type: entity
+ id: ThiefBeacon
+ name: thieving beacon
+ description: A device that will teleport everything around it to the thief's vault at the end of the shift.
+ components:
+ - type: ThiefBeacon
+ - type: StealArea
+ - type: Item
+ size: Normal
+ - type: Physics
+ bodyType: Dynamic
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.25,-0.4,0.25,0.1"
+ density: 20
+ mask:
+ - Impassable
+ - type: Foldable
+ folded: true
+ - type: Clickable
+ - type: InteractionOutline
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.FoldedVisuals.State:
+ foldedLayer:
+ True: { state: folded_extraction }
+ False: { state: extraction_point }
+ - type: Sprite
+ sprite: Objects/Tools/thief_beacon.rsi
+ drawdepth: SmallObjects
+ noRot: true
+ layers:
+ - state: extraction_point
+ map: [ "foldedLayer" ]
\ No newline at end of file
diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml
index bb74c92da3..e62aa9fdf6 100644
--- a/Resources/Prototypes/Objectives/objectiveGroups.yml
+++ b/Resources/Prototypes/Objectives/objectiveGroups.yml
@@ -52,8 +52,8 @@
- type: weightedRandom
id: ThiefBigObjectiveGroups
weights:
- ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added
- ThiefObjectiveGroupAnimal: 2
+ ThiefObjectiveGroupStructure: 1
+ ThiefObjectiveGroupAnimal: 1
- type: weightedRandom
id: ThiefObjectiveGroupCollection
@@ -91,7 +91,6 @@
weights:
NuclearBombStealObjective: 0.5
FaxMachineCaptainStealObjective: 1
- VehicleSecwayStealObjective: 1
ChemDispenserStealObjective: 1
XenoArtifactStealObjective: 1
FreezerHeaterStealObjective: 1
diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml
index c29641081b..672f9b2ba7 100644
--- a/Resources/Prototypes/Objectives/thief.yml
+++ b/Resources/Prototypes/Objectives/thief.yml
@@ -18,30 +18,29 @@
- type: StealCondition
verifyMapExistence: false
descriptionText: objective-condition-thief-description
+ checkStealAreas: true
- type: entity
abstract: true
- parent: [BaseThiefObjective, BaseStealObjective]
+ parent: [BaseThiefObjective, BaseThiefStealObjective]
id: BaseThiefStealCollectionObjective
components:
- type: StealCondition
verifyMapExistence: true
- descriptionText: objective-condition-thief-description
- type: entity
abstract: true
- parent: [BaseThiefObjective, BaseStealObjective]
+ parent: [BaseThiefObjective, BaseThiefStealObjective]
id: BaseThiefStealStructureObjective
components:
- type: StealCondition
verifyMapExistence: true
- descriptionText: objective-condition-thief-description
- type: Objective
difficulty: 2 # it's hard to hide
- type: entity
abstract: true
- parent: [BaseThiefObjective, BaseStealObjective]
+ parent: [BaseThiefObjective, BaseThiefStealObjective]
id: BaseThiefStealAnimalObjective
components:
- type: StealCondition
diff --git a/Resources/Prototypes/Roles/Antags/Thief.yml b/Resources/Prototypes/Roles/Antags/Thief.yml
index d85d366f6b..f0d0c12470 100644
--- a/Resources/Prototypes/Roles/Antags/Thief.yml
+++ b/Resources/Prototypes/Roles/Antags/Thief.yml
@@ -10,4 +10,5 @@
storage:
back:
- ToolboxThief
- - ClothingHandsChameleonThief
\ No newline at end of file
+ - ClothingHandsChameleonThief
+ - ThiefBeacon
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point.png b/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point.png
new file mode 100644
index 0000000000..b986a6ae28
Binary files /dev/null and b/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point.png differ
diff --git a/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point_light.png b/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point_light.png
new file mode 100644
index 0000000000..f2e9f984d2
Binary files /dev/null and b/Resources/Textures/Objects/Tools/thief_beacon.rsi/extraction_point_light.png differ
diff --git a/Resources/Textures/Objects/Tools/thief_beacon.rsi/folded_extraction.png b/Resources/Textures/Objects/Tools/thief_beacon.rsi/folded_extraction.png
new file mode 100644
index 0000000000..43ead68bb2
Binary files /dev/null and b/Resources/Textures/Objects/Tools/thief_beacon.rsi/folded_extraction.png differ
diff --git a/Resources/Textures/Objects/Tools/thief_beacon.rsi/meta.json b/Resources/Textures/Objects/Tools/thief_beacon.rsi/meta.json
new file mode 100644
index 0000000000..ba7e003575
--- /dev/null
+++ b/Resources/Textures/Objects/Tools/thief_beacon.rsi/meta.json
@@ -0,0 +1,32 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/austation/austation/commit/e2a4fefd01e702f48d3d4cc8d6a2686d54d104fa and edited by TheShuEd",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "folded_extraction"
+ },
+ {
+ "name": "extraction_point",
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
+ {
+ "name": "extraction_point_light",
+ "delays": [
+ [
+ 0.5,
+ 0.5
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file