Merge remote-tracking branch 'refs/remotes/wizards/master'

# Conflicts:
#	Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
This commit is contained in:
VigersRay 2024-07-15 01:24:12 +03:00
commit fe8edf03d4
30 changed files with 565 additions and 443 deletions

View file

@ -21,6 +21,7 @@ public sealed partial class AccessLevelControl : GridContainer
public AccessLevelControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("accesslevelcontrol");
}

View file

@ -4,6 +4,7 @@ using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;

View file

@ -160,9 +160,9 @@ public sealed class HTNPlanJob : Job<HTNPlan>
{
var compound = _protoManager.Index<HTNCompoundPrototype>(compoundId.Task);
for (var i = mtrIndex; i < compound.Branches.Count; i++)
for (; mtrIndex < compound.Branches.Count; mtrIndex++)
{
var branch = compound.Branches[i];
var branch = compound.Branches[mtrIndex];
var isValid = true;
foreach (var con in branch.Preconditions)

View file

@ -1,4 +1,5 @@
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

View file

@ -62,21 +62,22 @@ public sealed class SmartEquipSystem : EntitySystem
if (playerSession.AttachedEntity is not { Valid: true } uid || !Exists(uid))
return;
if (!_actionBlocker.CanInteract(uid, null))
return;
// early out if we don't have any hands or a valid inventory slot
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
return;
var handItem = hands.ActiveHand.HeldEntity;
// can the user interact, and is the item interactable? e.g. virtual items
if (!_actionBlocker.CanInteract(uid, handItem))
return;
if (!TryComp<InventoryComponent>(uid, out var inventory) || !_inventory.HasSlot(uid, equipmentSlot, inventory))
{
_popup.PopupClient(Loc.GetString("smart-equip-missing-equipment-slot", ("slotName", equipmentSlot)), uid, uid);
return;
}
var handItem = hands.ActiveHand.HeldEntity;
// early out if we have an item and cant drop it at all
if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
{

View file

@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Popups;
@ -43,6 +44,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
SubscribeLocalEvent<VirtualItemComponent, BeingUnequippedAttemptEvent>(OnBeingUnequippedAttempt);
SubscribeLocalEvent<VirtualItemComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
SubscribeLocalEvent<VirtualItemComponent, GettingInteractedWithAttemptEvent>(OnGettingInteractedWithAttemptEvent);
}
/// <summary>
@ -72,6 +74,12 @@ public abstract class SharedVirtualItemSystem : EntitySystem
args.Handled = true;
}
private void OnGettingInteractedWithAttemptEvent(Entity<VirtualItemComponent> ent, ref GettingInteractedWithAttemptEvent args)
{
// No interactions with a virtual item, please.
args.Cancelled = true;
}
#region Hands
/// <summary>

View file

@ -0,0 +1,12 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
return !Inverted;
}
}

View file

@ -0,0 +1,39 @@
using System.Numerics;
using Robust.Shared.Map;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed partial class GridInRangeRule : RulesRule
{
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform))
{
return false;
}
if (xform.GridUid != null)
{
return !Inverted;
}
var transform = entManager.System<SharedTransformSystem>();
var mapManager = IoCManager.Resolve<IMapManager>();
var worldPos = transform.GetWorldPosition(xform);
var gridRange = new Vector2(Range, Range);
foreach (var _ in mapManager.FindGridsIntersecting(xform.MapID, new Box2(worldPos - gridRange, worldPos + gridRange)))
{
return !Inverted;
}
return false;
}
}

View file

@ -0,0 +1,18 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed partial class InSpaceRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.GridUid != null)
{
return Inverted;
}
return !Inverted;
}
}

View file

@ -0,0 +1,77 @@
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of door electronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var reader = entManager.System<AccessReaderSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform, xformQuery);
var count = 0;
// TODO: Update this when we get the callback version
var entities = new HashSet<Entity<AccessReaderComponent>>();
lookup.GetEntitiesInRange(xform.MapID, worldPos, Range, entities);
foreach (var comp in entities)
{
if (!reader.AreAccessTagsAllowed(Access, comp) ||
Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < Count)
continue;
found = true;
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View file

@ -0,0 +1,71 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[DataField]
public bool Anchored;
[DataField]
public int Count;
[DataField(required: true)]
public ComponentRegistry Components = default!;
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
var inRange = new HashSet<Entity<IComponent>>();
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform);
var count = 0;
foreach (var compType in Components.Values)
{
inRange.Clear();
lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, Range, inRange);
foreach (var comp in inRange)
{
if (Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < Count)
continue;
found = true;
break;
}
if (found)
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View file

@ -0,0 +1,58 @@
using Content.Shared.Whitelist;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public EntityWhitelist Whitelist = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var whitelistSystem = entManager.System<EntityWhitelistSystem>();
var found = false;
var worldPos = transform.GetWorldPosition(xform);
var count = 0;
foreach (var ent in lookup.GetEntitiesInRange(xform.MapID, worldPos, Range))
{
if (whitelistSystem.IsWhitelistFail(Whitelist, ent))
continue;
count++;
if (count < Count)
continue;
found = true;
break;
}
if (!found)
return Inverted;
return !Inverted;
}
}

View file

@ -0,0 +1,79 @@
using Content.Shared.Maps;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
public sealed partial class NearbyTilesPercentRule : RulesRule
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField]
public bool IgnoreAnchored;
[DataField(required: true)]
public float Percent;
[DataField(required: true)]
public List<ProtoId<ContentTileDefinition>> Tiles = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
!entManager.TryGetComponent<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var tileDef = IoCManager.Resolve<ITileDefinitionManager>();
var physicsQuery = entManager.GetEntityQuery<PhysicsComponent>();
var tileCount = 0;
var matchingTileCount = 0;
foreach (var tile in grid.GetTilesIntersecting(new Circle(transform.GetWorldPosition(xform),
Range)))
{
// Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
if (IgnoreAnchored)
{
var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
var found = false;
while (gridEnum.MoveNext(out var ancUid))
{
if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
!physics.CanCollide)
{
continue;
}
found = true;
break;
}
if (found)
continue;
}
tileCount++;
if (!Tiles.Contains(tileDef[tile.Tile.TypeId].ID))
continue;
matchingTileCount++;
}
if (tileCount == 0 || matchingTileCount / (float) tileCount < Percent)
return Inverted;
return !Inverted;
}
}

View file

@ -0,0 +1,19 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed partial class OnMapGridRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
xform.GridUid != xform.MapUid ||
xform.MapUid == null)
{
return Inverted;
}
return !Inverted;
}
}

View file

@ -0,0 +1,39 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Rules-based item selection. Can be used for any sort of conditional selection
/// Every single condition needs to be true for this to be selected.
/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract partial class RulesRule
{
[DataField]
public bool Inverted;
public abstract bool Check(EntityManager entManager, EntityUid uid);
}
public sealed class RulesSystem : EntitySystem
{
public bool IsTrue(EntityUid uid, RulesPrototype rules)
{
foreach (var rule in rules.Rules)
{
if (!rule.Check(EntityManager, uid))
return false;
}
return true;
}
}

View file

@ -1,141 +0,0 @@
using Content.Shared.Access;
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Random;
/// <summary>
/// Rules-based item selection. Can be used for any sort of conditional selection
/// Every single condition needs to be true for this to be selected.
/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract partial class RulesRule
{
}
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed partial class InSpaceRule : RulesRule
{
}
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("whitelist", required: true)]
public EntityWhitelist Whitelist = new();
[DataField("range")]
public float Range = 10f;
}
public sealed partial class NearbyTilesPercentRule : RulesRule
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField("ignoreAnchored")] public bool IgnoreAnchored;
[DataField("percent", required: true)]
public float Percent;
[DataField("tiles", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
public List<string> Tiles = new();
[DataField("range")]
public float Range = 10f;
}
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
}
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed partial class GridInRangeRule : RulesRule
{
[DataField("range")]
public float Range = 10f;
[DataField("inverted")]
public bool Inverted = false;
}
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed partial class OnMapGridRule : RulesRule
{
}
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of doorelectronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("access", required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField("range")]
public float Range = 10f;
}
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored;
[DataField("count")] public int Count;
[DataField("components", required: true)]
public ComponentRegistry Components = default!;
[DataField("range")]
public float Range = 10f;
}

View file

@ -1,247 +0,0 @@
using System.Numerics;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
namespace Content.Shared.Random;
public sealed class RulesSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
[Dependency] private readonly AccessReaderSystem _reader = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
public bool IsTrue(EntityUid uid, RulesPrototype rules)
{
var inRange = new HashSet<Entity<IComponent>>();
foreach (var rule in rules.Rules)
{
switch (rule)
{
case AlwaysTrueRule:
break;
case GridInRangeRule griddy:
{
if (!TryComp(uid, out TransformComponent? xform))
{
return false;
}
if (xform.GridUid != null)
{
return !griddy.Inverted;
}
var worldPos = _transform.GetWorldPosition(xform);
var gridRange = new Vector2(griddy.Range, griddy.Range);
foreach (var _ in _mapManager.FindGridsIntersecting(
xform.MapID,
new Box2(worldPos - gridRange, worldPos + gridRange)))
{
return !griddy.Inverted;
}
break;
}
case InSpaceRule:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.GridUid != null)
{
return false;
}
break;
}
case NearbyAccessRule access:
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
var count = 0;
// TODO: Update this when we get the callback version
var entities = new HashSet<Entity<AccessReaderComponent>>();
_lookup.GetEntitiesInRange(xform.MapID, worldPos, access.Range, entities);
foreach (var comp in entities)
{
if (!_reader.AreAccessTagsAllowed(access.Access, comp) ||
access.Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < access.Count)
continue;
found = true;
break;
}
if (!found)
return false;
break;
}
case NearbyComponentsRule nearbyComps:
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform);
var count = 0;
foreach (var compType in nearbyComps.Components.Values)
{
inRange.Clear();
_lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, nearbyComps.Range, inRange);
foreach (var comp in inRange)
{
if (nearbyComps.Anchored &&
(!xformQuery.TryGetComponent(comp, out var compXform) ||
!compXform.Anchored))
{
continue;
}
count++;
if (count < nearbyComps.Count)
continue;
found = true;
break;
}
if (found)
break;
}
if (!found)
return false;
break;
}
case NearbyEntitiesRule entity:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.MapUid == null)
{
return false;
}
var found = false;
var worldPos = _transform.GetWorldPosition(xform);
var count = 0;
foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range))
{
if (_whitelistSystem.IsWhitelistFail(entity.Whitelist, ent))
continue;
count++;
if (count < entity.Count)
continue;
found = true;
break;
}
if (!found)
return false;
break;
}
case NearbyTilesPercentRule tiles:
{
if (!TryComp(uid, out TransformComponent? xform) ||
!TryComp<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var tileCount = 0;
var matchingTileCount = 0;
foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform),
tiles.Range)))
{
// Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
if (tiles.IgnoreAnchored)
{
var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
var found = false;
while (gridEnum.MoveNext(out var ancUid))
{
if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
!physics.CanCollide)
{
continue;
}
found = true;
break;
}
if (found)
continue;
}
tileCount++;
if (!tiles.Tiles.Contains(_tileDef[tile.Tile.TypeId].ID))
continue;
matchingTileCount++;
}
if (tileCount == 0 || matchingTileCount / (float) tileCount < tiles.Percent)
return false;
break;
}
case OnMapGridRule:
{
if (!TryComp(uid, out TransformComponent? xform) ||
xform.GridUid != xform.MapUid ||
xform.MapUid == null)
{
return false;
}
break;
}
default:
throw new NotImplementedException();
}
}
return true;
}
}

View file

@ -1,49 +1,4 @@
Entries:
- author: Weax
changes:
- message: The CLF3 reaction now requires heating first before you can engulf chemistry
in fiery death.
type: Tweak
id: 6417
time: '2024-04-22T08:44:14.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/27187
- author: Potato1234_x
changes:
- message: Added Psicodine, Mannitol, Lipolicide and Happiness.
type: Add
id: 6418
time: '2024-04-22T08:45:39.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/27134
- author: Terraspark4941
changes:
- message: Updated the engineering section of the guidebook!
type: Tweak
id: 6419
time: '2024-04-22T08:58:54.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/26851
- author: eclips_e
changes:
- message: Slimepeople can now morph into a "geras"--a smaller slime form that can
pass under grilles, at the cost of dropping all of their inventory. They can
also be picked up with two hands and placed into duffelbags.
type: Add
- message: Slimepeople now have an internal 2x2 storage that they (and anyone around
them) can access. It is not dropped when morphing into a geras!
type: Add
- message: Slimepeople now have slightly increased regeneration and a slightly meatier
punch, but slower attacks.
type: Add
id: 6420
time: '2024-04-22T10:03:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/23425
- author: FungiFellow
changes:
- message: Syndi-Cats are now 6TC, Insulated, Available to Syndies, Can Move in
Space, Open Doors, and Hit Harder
type: Tweak
id: 6421
time: '2024-04-22T12:18:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/27222
- author: Tayrtahn
changes:
- message: Ghosts can no longer trigger artifacts by examining them.
@ -3813,3 +3768,40 @@
id: 6916
time: '2024-07-14T02:59:45.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29979
- author: SlamBamActionman
changes:
- message: RGBee and Rainbow Carp plushies now cycle color when held/worn.
type: Fix
id: 6917
time: '2024-07-14T10:26:34.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30023
- author: Winkarst-cpu
changes:
- message: Now grappling gun is clumsy proof.
type: Tweak
id: 6918
time: '2024-07-14T10:26:56.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29904
- author: HahayesSiH
changes:
- message: It is now possible to pet cyborgs.
type: Add
- message: Clicking on cyborgs and opening the strip menu no longer unlocks them.
type: Tweak
id: 6919
time: '2024-07-14T14:09:41.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/30037
- author: deltanedas
changes:
- message: Fixed ninja shoes not working as magboots.
type: Fix
id: 6920
time: '2024-07-14T15:11:40.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/28586
- author: lzk228
changes:
- message: Scarves are eatable again.
type: Fix
id: 6921
time: '2024-07-14T15:12:25.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/29959

View file

@ -60,12 +60,26 @@ petting-success-honkbot = You pet {THE($target)} on {POSS-ADJ($target)} slippery
petting-success-mimebot = You pet {THE($target)} on {POSS-ADJ($target)} cold metal head.
petting-success-cleanbot = You pet {THE($target)} on {POSS-ADJ($target)} damp metal head.
petting-success-medibot = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head.
petting-success-generic-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} metal head.
petting-success-salvage-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dirty metal head.
petting-success-engineer-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} reflective metal head.
petting-success-janitor-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} damp metal head.
petting-success-medical-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head.
petting-success-service-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dapper looking metal head.
petting-success-syndicate-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} menacing metal head.
petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior.
petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "honk", "honks")} in refusal!
petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping!
petting-failure-mimebot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy miming!
petting-failure-medibot = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} syringe nearly stabs your hand!
petting-failure-generic-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy stating laws!
petting-failure-salvage-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy drilling!
petting-failure-engineer-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy repairing!
petting-failure-janitor-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy cleaning!
petting-failure-medical-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy saving lives!
petting-failure-service-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy serving others!
petting-failure-syndicate-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} treacherous affiliation makes you reconsider.
## Rattling fences

View file

@ -38,3 +38,4 @@
- type: Tag
tags:
- Scarf
- ClothMade

View file

@ -124,6 +124,7 @@
- type: Clothing
sprite: Clothing/Shoes/Specific/spaceninja.rsi
- type: NoSlip
- type: Magboots # always have gravity because le suction cups
- type: ClothingSpeedModifier
# ninja are masters of sneaking around relatively quickly, won't break cloak
walkModifier: 1.1

View file

@ -154,6 +154,7 @@
- type: Lock
locked: true
breakOnEmag: false
unlockOnClick: false
- type: ActivatableUIRequiresLock
- type: LockedWiresPanel
- type: Damageable

View file

@ -29,6 +29,11 @@
node: cyborg
- type: Speech
speechVerb: Robotic
- type: InteractionPopup
interactSuccessString: petting-success-generic-cyborg
interactFailureString: petting-failure-generic-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisMining
@ -85,6 +90,11 @@
access: [["Cargo"], ["Salvage"], ["Command"], ["Research"]]
- type: Inventory
templateId: borgTall
- type: InteractionPopup
interactSuccessString: petting-success-salvage-cyborg
interactFailureString: petting-failure-salvage-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisEngineer
@ -133,6 +143,11 @@
access: [["Engineering"], ["Command"], ["Research"]]
- type: Inventory
templateId: borgShort
- type: InteractionPopup
interactSuccessString: petting-success-engineer-cyborg
interactFailureString: petting-failure-engineer-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisJanitor
@ -189,6 +204,11 @@
access: [["Service"], ["Command"], ["Research"]]
- type: Inventory
templateId: borgShort
- type: InteractionPopup
interactSuccessString: petting-success-janitor-cyborg
interactFailureString: petting-failure-janitor-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisMedical
@ -248,8 +268,13 @@
- type: FootstepModifier
footstepSoundCollection:
collection: FootstepHoverBorg
- type: TTS
voice: FactCore
- type: InteractionPopup
interactSuccessString: petting-success-medical-cyborg
interactFailureString: petting-failure-medical-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: TTS # Sunrise-Edit
voice: FactCore # Sunrise-Edit
- type: entity
id: BorgChassisService
@ -298,6 +323,11 @@
access: [["Service"], ["Command"], ["Research"]]
- type: Inventory
templateId: borgTall
- type: InteractionPopup
interactSuccessString: petting-success-service-cyborg
interactFailureString: petting-failure-service-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisSyndicateAssault
@ -327,6 +357,11 @@
noMindState: synd_sec
- type: Construction
node: syndicateassault
- type: InteractionPopup
interactSuccessString: petting-success-syndicate-cyborg
interactFailureString: petting-failure-syndicate-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisSyndicateMedical
@ -359,6 +394,11 @@
- type: ShowHealthBars
damageContainers:
- Biological
- type: InteractionPopup
interactSuccessString: petting-success-syndicate-cyborg
interactFailureString: petting-failure-syndicate-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: entity
id: BorgChassisSyndicateSaboteur
@ -392,3 +432,8 @@
damageContainers:
- Inorganic
- Silicon
- type: InteractionPopup
interactSuccessString: petting-success-syndicate-cyborg
interactFailureString: petting-failure-syndicate-cyborg
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg

View file

@ -2,7 +2,7 @@
id: Wristwatch
parent: BaseItem
name: wristwatch
description: A cheap watch for telling time. How much did you waste playing Space Station 14?
description: A cheap watch for telling time. How much did you waste working on this shift?
components:
- type: Sprite
sprite: Objects/Devices/wristwatch.rsi

View file

@ -184,6 +184,19 @@
energy: 2
- type: RgbLightController
layers: [ 0 ]
- type: Item
inhandVisuals:
left:
- state: bee-inhand-left
shader: unshaded
right:
- state: bee-inhand-right
shader: unshaded
- type: Clothing
clothingVisuals:
head:
- state: bee-equipped-HELMET
shader: unshaded
- type: entity
parent: BasePlushie
@ -543,6 +556,15 @@
energy: 2
- type: RgbLightController
layers: [ 0 ]
- type: Item
heldPrefix: rainbowcarpplush
inhandVisuals:
left:
- state: rainbowcarpplush-inhand-left
shader: unshaded
right:
- state: rainbowcarpplush-inhand-right
shader: unshaded
- type: entity
parent: PlushieCarp

View file

@ -251,6 +251,7 @@
- type: Gun
soundGunshot: /Audio/Weapons/Guns/Gunshots/harpoon.ogg
fireRate: 0.5
clumsyProof: true
- type: BasicEntityAmmoProvider
proto: GrapplingHook
capacity: 1

View file

@ -5,4 +5,5 @@
noSpawn: true
components:
- type: Item
size: Ginormous # no storage insertion visuals
- type: VirtualItem

View file

@ -53,6 +53,14 @@
{
"name": "rainbowcarpplush"
},
{
"name": "rainbowcarpplush-inhand-left",
"directions": 4
},
{
"name": "rainbowcarpplush-inhand-right",
"directions": 4
},
{
"name": "narplush"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB