Stable to master sink after release 2026-08-31 (#45785)

* HOTFIX TeleportActionSystem map coords (#45659)

* Fix target map coords

* Improve performance

* Bonus improvement

* refactor: updated changelog

* refactor: fix changelog entry

---------

Co-authored-by: Fildrance <fildrance@gmail.com>

* [HOTFIX] fixing chameleon projector (#45689)

SpriteSystem.CopySprite instead of direct Copy Component

* Hotfix - Grappling gun assuming either end has physics component (#45683)

a can of monster does miracles at 4am

* Hotfix: Return hunger slowdown back to Starving from Peckish (#45693)

move hunger slowdown back to starving

* HOTFIX Set AI camera static to accessible variant by default (#45673)

* disable AI static

* comment

* [HOTFIX] Prevent Off-Station Objective Targets (#45692)

* init

* targets

* attempt 1 at this

* init

* rename file

* dont roll unkillable antags for kill objectives

* refactor: do not allocate on MindGetAllAntagTags when fail to get MindComponent, remove unneeded linq

* refactor: cleanup and added test for new condition. might be oddly specific but...

* oversight

* refactor: test cleanup after review

* refactor: fix changelog (AGAIN GOD DAMMIT)

* refactor: revert changelog thing, make it separate PR

---------

Co-authored-by: Fildrance <fildrance@gmail.com>

* [HOTIFX] Changelog actualisation for stable release (#45738)

* [HOTFIX] fix: make changelog discord publication not fail on missing 'labels' (#45779)

fix: make changelog discord publication not fail on missing 'labels' tag in changelog entry (which does not exist).

---------

Co-authored-by: Connor Huffine <chuffine@gmail.com>
Co-authored-by: xsainteer <156868231+xsainteer@users.noreply.github.com>
Co-authored-by: Velken <8467292+Velken@users.noreply.github.com>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
This commit is contained in:
Fildrance 2026-08-31 23:13:01 +03:00 • committed by GitHub
commit 9c8df525bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 869 additions and 328 deletions

View file

@ -28,7 +28,7 @@ public sealed partial class ChameleonProjectorSystem : SharedChameleonProjectorS
private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
{
CopyComp<SpriteComponent>(ent);
CopySprite(ent);
CopyComp<GenericVisualizerComponent>(ent);
CopyComp<SolutionContainerVisualsComponent>(ent);
CopyComp<BurnStateVisualsComponent>(ent);
@ -38,6 +38,18 @@ public sealed partial class ChameleonProjectorSystem : SharedChameleonProjectorS
_appearance.QueueUpdate(ent, appearance);
}
/// <summary>
/// Copies the source entity/prototype's sprite onto the disguise.
/// </summary>
private void CopySprite(Entity<ChameleonDisguiseComponent> ent)
{
if (!GetSrcEntity<SpriteComponent>(ent.Comp, out var src))
return;
var dest = EnsureComp<SpriteComponent>(ent);
_sprite.CopySprite(src, (ent.Owner, dest));
}
private void OnStartup(Entity<ChameleonDisguisedComponent> ent, ref ComponentStartup args)
{
if (!_spriteQuery.TryComp(ent, out var sprite))

View file

@ -0,0 +1,160 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.IntegrationTests.Fixtures;
using Content.IntegrationTests.Fixtures.Attributes;
using Content.Server.Mind;
using Content.Server.Roles;
using Content.Shared.EntityConditions;
using Content.Shared.EntityConditions.Conditions.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.EntityTable;
[TestOf(typeof(AntagonistTagCondition))]
[TestOf(typeof(AntagonistTagEntityConditionSystem))]
public sealed class AntagonistTagConditionTest : GameTest
{
[SidedDependency(Side.Server)]
private readonly SharedEntityConditionsSystem _conditions = null!;
[SidedDependency(Side.Server)]
private readonly MindSystem _minds = null!;
[SidedDependency(Side.Server)]
private readonly RoleSystem _roles = null!;
private const string OnStationAntagRole = "AntagTagTestAntagRoleOnStation";
private const string UnkillableAntagRole = "AntagTagTestAntagRoleUnkillable";
[TestPrototypes]
private const string Prototypes =
$"""
- type: antag
id: AntagTagTestAntagOnStation
name: roles-antag-generic-solo-antagonist-name
antagonist: true
objective: never-shown
tags:
- OnStation
- type: antag
id: AntagTagTestAntagUnkillable
name: roles-antag-generic-solo-antagonist-name
antagonist: true
objective: never-shown
tags:
- OnStation
- Unkillable
- type: entity
parent: BaseMindRole
id: {OnStationAntagRole}
components:
- type: MindRole
antag: true
antagPrototype: AntagTagTestAntagOnStation
- type: entity
parent: BaseMindRole
id: {UnkillableAntagRole}
components:
- type: MindRole
antag: true
antagPrototype: AntagTagTestAntagUnkillable
""";
private static readonly HashSet<ProtoId<AntagTagPrototype>> OnStationTags = AntagTags("OnStation");
private static readonly HashSet<ProtoId<AntagTagPrototype>> UnkillableTags = AntagTags("Unkillable");
[Test]
[RunOnSide(Side.Server)]
public void NonAntag_AlwaysPasses_WhenAllowNonAntags()
{
var mind = CreateMind();
using (Assert.EnterMultipleScope())
{
// Positive filter (changeling.yml: "Only allow on-station targets.").
var onStation = Cond(OnStationTags);
Assert.That(_conditions.TryCondition(mind, onStation), Is.True);
// Inverted filters still pass non-antags.
var unkillable = Cond(UnkillableTags, inverted: true);
Assert.That(_conditions.TryCondition(mind, unkillable), Is.True);
var onStationInverted = Cond(OnStationTags, inverted: true);
Assert.That(_conditions.TryCondition(mind, onStationInverted), Is.True);
}
}
[Test]
[RunOnSide(Side.Server)]
public void NonAntag_InvertedFlagWithAllowNonAntagFalse_WhenDisallowed()
{
var mind = CreateMind();
using (Assert.EnterMultipleScope())
{
var onStation = Cond(OnStationTags, allowNonAntags: false);
Assert.That(_conditions.TryCondition(mind, onStation), Is.False);
var onStationInverted = Cond(OnStationTags, allowNonAntags: false, inverted: true);
Assert.That(_conditions.TryCondition(mind, onStationInverted), Is.True);
}
}
[Test]
[RunOnSide(Side.Server)]
public void CombinedFilter_MatchesChangelingObjectiveIntent()
{
var nonAntag = CreateMind();
var onStationAntag = CreateMind(OnStationAntagRole);
var unkillableAntag = CreateMind(UnkillableAntagRole);
using (Assert.EnterMultipleScope())
{
Assert.That(PassesAll(nonAntag), Is.True);
Assert.That(PassesAll(onStationAntag), Is.True);
Assert.That(PassesAll(unkillableAntag), Is.False);
}
bool PassesAll(EntityUid mind)
{
var onStation = Cond(OnStationTags);
var unkillableInverted = Cond(UnkillableTags, inverted: true);
return _conditions.TryCondition(mind, onStation)
&& _conditions.TryCondition(mind, unkillableInverted);
}
}
private static HashSet<ProtoId<AntagTagPrototype>> AntagTags(params string[] ids)
=> ids.Select(id => new ProtoId<AntagTagPrototype>(id)).ToHashSet();
private static AntagonistTagCondition Cond(
HashSet<ProtoId<AntagTagPrototype>> tags,
bool allowNonAntags = true,
bool inverted = false
) => new()
{
Tags = tags,
AllowNonAntags = allowNonAntags,
Inverted = inverted
};
private EntityUid CreateMind(string? roleProto = null)
{
var mind = _minds.CreateMind(null);
// Give the mind an owned entity so MindAddRole doesn't log an error about it.
var owned = SSpawn(null);
SEntMan.EnsureComponent<MindContainerComponent>(owned);
_minds.TransferTo(mind.Owner, owned);
if (roleProto != null)
_roles.MindAddRole(mind.Owner, roleProto, silent: true);
return mind.Owner;
}
}

View file

@ -21,7 +21,9 @@ public sealed partial class CCVars
/// Replaces the AI static camera effect with a plain gradient.
/// </summary>
public static readonly CVarDef<bool> DisableAiStatic =
CVarDef.Create("accessibility.disable_ai_static", false, CVar.CLIENTONLY | CVar.ARCHIVE);
CVarDef.Create("accessibility.disable_ai_static", true, CVar.CLIENTONLY | CVar.ARCHIVE);
//TODO return default value to False
// This was set to True pending evaluation of the static effect visuals, due to headache potential of the current static shader
/// <summary>
/// Replaces the movement in the blurry shader for a static effect.

View file

@ -0,0 +1,49 @@
using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityConditions.Conditions.Mind;
public sealed partial class AntagonistTagEntityConditionSystem : EntityConditionSystem<MindComponent, AntagonistTagCondition>
{
[Dependency] private SharedRoleSystem _roleSystem = default!;
protected override void Condition(Entity<MindComponent> entity, ref EntityConditionEvent<AntagonistTagCondition> args)
{
var conditionTags = args.Condition.Tags;
if (!_roleSystem.TryGetAllAntagTags(entity.AsNullable(), out var antagTags))
{
args.Result = args.Condition is { AllowNonAntags: true, Inverted: false };
return;
}
args.Result = antagTags.Overlaps(conditionTags);
}
}
/// <summary>
/// Checks if the given mind is an antagonist with specified tag.
/// </summary>
public sealed partial class AntagonistTagCondition : EntityConditionBase<AntagonistTagCondition>
{
/// <summary>
/// The tags this check will succeed for.
/// For example, if "OnStation" is provided, all on-station antags will pass the check.
/// If <see cref="AllowNonAntags"/> is true, it will additionally allow every non-antag to pass the check.
/// </summary>
[DataField]
public HashSet<ProtoId<AntagTagPrototype>> Tags = new();
/// <summary>
/// Whether non-antagonists should always pass this condition.
/// </summary>
[DataField]
public bool AllowNonAntags = true;
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
{
return String.Empty;
}
}

View file

@ -338,7 +338,7 @@ public abstract partial class SharedChameleonProjectorSystem : EntitySystem
/// <summary>
/// Try to get a single component from the source entity/prototype.
/// </summary>
private bool GetSrcComp<T>(ChameleonDisguiseComponent comp, [NotNullWhen(true)] out T? src) where T : Component, new()
protected bool GetSrcComp<T>(ChameleonDisguiseComponent comp, [NotNullWhen(true)] out T? src) where T : Component, new()
{
if (TryComp(comp.SourceEntity, out src))
return true;
@ -351,6 +351,29 @@ public abstract partial class SharedChameleonProjectorSystem : EntitySystem
return proto.TryComp(out src, EntityManager.ComponentFactory);
}
/// <summary>
/// Try to get a single component, paired with its owning entity, from the source entity/prototype.
/// </summary>
protected bool GetSrcEntity<T>(ChameleonDisguiseComponent comp, out Entity<T?> src) where T : Component, new()
{
if (TryComp<T>(comp.SourceEntity, out var liveComp))
{
src = (comp.SourceEntity, liveComp);
return true;
}
if (comp.SourceProto is { } protoId
&& ProtoMan.TryIndex<EntityPrototype>(protoId, out var proto)
&& proto.TryComp<T>(out var protoComp, EntityManager.ComponentFactory))
{
src = (EntityUid.Invalid, protoComp);
return true;
}
src = default;
return false;
}
}
/// <summary>

View file

@ -56,4 +56,25 @@ public sealed partial class AntagPrototype : IPrototype
/// </summary>
[DataField]
public List<ProtoId<GuideEntryPrototype>>? Guides;
/// <summary>
/// The tags of this antagonist.
/// Can be used to specify the type of gameplay loop they follow.
/// Used for filtering purposes.
/// </summary>
[DataField]
public HashSet<ProtoId<AntagTagPrototype>> Tags = [];
}
/// <summary>
/// Used to describe the type of gameplay loop some antagonists follow.
/// Such as whether they are on-station antags or off-station.
/// </summary>
[Prototype]
public sealed partial class AntagTagPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
// Can potentially be expanded in the future to show up in things like guidebooks etc.
}

View file

@ -1,5 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
@ -16,6 +14,9 @@ using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Shared.Roles;
@ -541,6 +542,41 @@ public abstract partial class SharedRoleSystem : EntitySystem
return result;
}
/// <summary>
/// Reads all <see cref="AntagTagPrototype"/> IDs on a mind and returns them.
/// </summary>
/// <param name="mind">The mind entity.</param>
/// <param name="tags">Antag tags from all roles that mind entity had. Null if mind had no roles marked as antag.</param>
/// <returns>A hashset of <see cref="AntagTagPrototype"/> IDs on the mind.</returns>
public bool TryGetAllAntagTags(Entity<MindComponent?> mind, [NotNullWhen(true)] out IReadOnlySet<ProtoId<AntagTagPrototype>>? tags)
{
tags = FrozenSet<ProtoId<AntagTagPrototype>>.Empty;
if (!Resolve(mind.Owner, ref mind.Comp))
return false;
var roles = MindGetAllRoleInfo(mind);
var antagTags = new HashSet<ProtoId<AntagTagPrototype>>();
var foundAntag = false;
foreach (var role in roles)
{
if (!role.Antagonist)
continue;
if (!ProtoMan.TryIndex<AntagPrototype>(role.Prototype, out var antag))
continue;
foundAntag = true;
foreach (var tag in antag.Tags)
{
antagTags.Add(tag);
}
}
tags = antagTags;
return foundAntag;
}
/// <summary>
/// Reads all Roles of a mind Entity and returns their data as RoleInfo
/// </summary>

View file

@ -57,18 +57,18 @@ public sealed partial class TeleportActionSystem : EntitySystem
return false;
var xform = Transform(user);
var mapId = _transform.GetMapId(target);
if (xform.MapID != mapId ||
!_examine.InRangeUnOccluded(user, target, SharedInteractionSystem.MaxRaycastRange))
var targetXform = Transform(target.EntityId);
if (!_examine.InRangeUnOccluded(user, target, SharedInteractionSystem.MaxRaycastRange))
{
_popup.PopupEntity(Loc.GetString("teleport-action-popup-cant-see"), user, user);
return false;
}
var mapTarget = _transform.GetWorldPositionRotation(target.EntityId);
var targetMapCoordinates = new MapCoordinates(mapTarget.WorldPosition + target.Position, mapId);
var targetEntPosRot = _transform.GetWorldPositionRotation(targetXform);
var targetRotated = targetEntPosRot.WorldRotation.RotateVec(target.Position);
var targetMapCoordinates = new MapCoordinates(targetEntPosRot.WorldPosition + targetRotated, targetXform.MapID);
if (IsDestinationBlocked(user, targetMapCoordinates, mapTarget.WorldRotation))
if (IsDestinationBlocked(user, targetMapCoordinates, targetEntPosRot.WorldRotation))
{
_popup.PopupEntity(Loc.GetString("teleport-action-popup-blocked"), user, user);
return false;

View file

@ -302,13 +302,22 @@ public abstract partial class SharedGrapplingGunSystem : VirtualController
var grapplerUidA = _container.TryGetOuterContainer(physicalHook, Transform(physicalHook), out var containerA) ? containerA.Owner : physicalHook;
var grapplerOffsetA = _transform.GetRelativePosition(Transform(joint.BodyAUid), grapplerUidA);
var grapplerBodyA = Comp<PhysicsComponent>(grapplerUidA);
var grapplerUidB = _container.TryGetOuterContainer(physicalGrapple, Transform(physicalGrapple), out var containerB) ? containerB.Owner : physicalGrapple;
if (attachedToGrid)
grapplerUidB = _transform.GetGrid(joint.BodyBUid) ?? grapplerUidB;
// Don't try to push grids without physics (e.g. Planets).
if (attachedToGrid && _transform.GetGrid(joint.BodyBUid) is { } gridB && HasComp<PhysicsComponent>(gridB))
grapplerUidB = gridB;
var grapplerOffsetB = _transform.GetRelativePosition(Transform(joint.BodyBUid), grapplerUidB);
var grapplerBodyB = Comp<PhysicsComponent>(grapplerUidB);
// Either end may lack physics in which case there's nothing to apply an impulse to.
if (!TryComp<PhysicsComponent>(grapplerUidA, out var grapplerBodyA) ||
!TryComp<PhysicsComponent>(grapplerUidB, out var grapplerBodyB))
{
Dirty(uid, jointComp);
continue;
}
// Note that this way of calculating the impulse does not take into account objects being stuck on things, e.g. a movable grapple point stuck behind a wall.
// Ideally the contraction of the joint itself should take this into account, but alas, this works for now.
@ -425,11 +434,11 @@ public abstract partial class SharedGrapplingGunSystem : VirtualController
if (!jointComp.GetJoints.TryGetValue(GrapplingJoint, out var joint))
continue;
if (Transform(entity).Anchored && _transform.GetGrid(entity.Owner) != null)
// Grids without physics (e.g. planets) can't be used as a relay body, so relay to the entity itself instead.
if (Transform(entity).Anchored && _transform.GetGrid(entity.Owner) is { } grid && HasComp<PhysicsComponent>(grid))
{
joint.LocalAnchorA = _transform.GetRelativePosition(Transform(hook), _transform.GetGrid(entity.Owner)!.Value);
_joints.SetRelay(hook, _transform.GetGrid(entity.Owner));
joint.LocalAnchorA = _transform.GetRelativePosition(Transform(hook), grid);
_joints.SetRelay(hook, grid);
}
else
{

View file

@ -1248,15 +1248,8 @@ Entries:
url: https://github.com/space-wizards/space-station-14/pull/39046
- author: slarticodefast
changes:
- &o0
message: The listplayers command now requires PII permissions due to showing IP addresses.
- message: The listplayers command now requires PII permissions due to showing IP addresses.
type: Tweak
id: 169
time: '2025-09-13T15:53:48.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40324
- author: slarticodefast
changes:
- *o0
id: 170
time: '2025-09-16T07:59:11.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40395
@ -1736,6 +1729,7 @@ Entries:
type: Add
id: 233
time: '2026-07-26T09:36:27.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44812
- author: Centronias
changes:
- message: Satiation reduction command "unsatiate"
@ -1750,5 +1744,6 @@ Entries:
type: Remove
id: 234
time: '2026-08-08T22:59:23.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/34166
Name: Admin
Order: 3

File diff suppressed because it is too large Load diff

View file

@ -1181,6 +1181,7 @@ Entries:
type: Tweak
id: 154
time: '2026-07-27T00:00:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44889
- author: Emisse
changes:
- message: Several job slots have been tweaked across the board to accommodate a smaller maxpop.
@ -1189,6 +1190,7 @@ Entries:
type: Remove
id: 155
time: '2026-07-29T14:47:34.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44996
- author: SuperDicq
changes:
- message: Added Sushi Station!
@ -1197,36 +1199,42 @@ Entries:
type: Tweak
id: 156
time: '2026-08-01T17:20:31.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45064
- author: ProPeperos
changes:
- message: On all Cargo shuttles - Tweaked atmos systems and small fixes
type: Tweak
id: 157
time: '2026-08-01T19:47:13.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44125
- author: ProPeperos
changes:
- message: On Fland - Reworked the TEG room + small tweaks around atmos.
type: Tweak
id: 158
time: '2026-08-01T19:47:13.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44751
- author: ProPeperos
changes:
- message: On Bagel - Replaced some pressure pumps to manual valves in atmos
type: Tweak
id: 159
time: '2026-08-01T20:12:53.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/44010
- author: ProPeperos
changes:
- message: On Box station - Added AI restoration console in RD office and minor issue fixes
type: Add
id: 160
time: '2026-08-03T18:19:36.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45099
- author: ProPeperos
changes:
- message: On Elkridge - Added AI restoration console to RD office and other issue fixes
type: Add
id: 161
time: '2026-08-03T18:36:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45111
- author: ProPeperos
changes:
- message: On Bagel - Moved exosuit fabricator and material silo in science and other minor tweaks and fixes.
@ -1235,6 +1243,7 @@ Entries:
type: Add
id: 162
time: '2026-08-03T18:42:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45096
- author: ProPeperos
changes:
- message: On Plasma - Added missing AI Restoration computer and other issue fixes
@ -1245,18 +1254,21 @@ Entries:
type: Fix
id: 163
time: '2026-08-03T21:20:28.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45125
- author: ProPeperos
changes:
- message: On Packed - You will no longer spawn in cryogenics pod at round start and other tweaks and fixes
type: Fix
id: 164
time: '2026-08-05T19:30:13.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45160
- author: ProPeperos
changes:
- message: On Elkridge - You will no longer spawn in cryogenics pod at round start
type: Fix
id: 165
time: '2026-08-05T20:08:46.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45161
- author: Spessmann
changes:
- message: On Snowball added wheelchairs to med
@ -1265,46 +1277,54 @@ Entries:
type: Remove
id: 166
time: '2026-08-09T04:34:51.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45131
- author: ProPeperos
changes:
- message: Updated Box to comply with the Respritening.
type: Tweak
id: 167
time: '2026-08-22T23:10:16.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45567
- author: ProPeperos
changes:
- message: Updated Snowball to comply with the Respritening.
type: Tweak
id: 168
time: '2026-08-22T23:10:33.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45571
- author: SuperDicq
changes:
- message: Updated Sushi Station to comply with the Respritening.
type: Tweak
id: 169
time: '2026-08-22T23:10:41.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45532
- author: ScarKy0
changes:
- message: Updated event shuttles to comply with the Respritening.
type: Tweak
id: 170
time: '2026-08-22T23:11:05.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45566
- author: ketufaispikinut
changes:
- message: Added energy gates to Elkridge's arrivals.
type: Add
id: 171
time: '2026-08-22T23:11:14.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45575
- author: ScarKy0
changes:
- message: Updated Marathon to comply with the Respritening.
type: Tweak
id: 172
time: '2026-08-22T23:13:04.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45557
- author: ProPeperos
changes:
- message: Updated Packed to comply with the Respritening.
type: Tweak
id: 173
time: '2026-08-22T23:13:15.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/45568
Order: 2

View file

@ -6,7 +6,7 @@
satiations:
Hunger:
thresholds:
Peckish: 0.75
Starving: 0.75
Thirst:
thresholds:
Parched: 0.75

View file

@ -60,6 +60,13 @@
components:
- type: PickRandomPerson
conditions:
- !type:AntagonistTagCondition # Only roll on-station targets.
tags:
- OnStation
- !type:AntagonistTagCondition # Don't roll unkillable targets.
inverted: true
tags:
- Unkillable
# Can't have multiple objectives to kill the same person.
- !type:ObjectiveTargetCondition
inverted: true

View file

@ -70,11 +70,20 @@
- type: ChangelingEscapeIdentityCondition
- type: PickRandomPerson
conditions:
- !type:RoleCondition
inverted: true # Do not roll changelings here. Their initial identity will be basically impossible to get after they transform.
- !type:AntagonistTagCondition # The only targets we want to roll for this objective are on-station.
tags:
- OnStation
- !type:AntagonistTagCondition # We don't want to roll unkillable antagonists, because cannot be devoured.
inverted: true
tags:
- Unkillable
# Kill and Escape objectives should have different targets.
- !type:ObjectiveTargetCondition
inverted: true
whitelist:
components:
- ChangelingRole
- KillPersonCondition
- ChangelingEscapeIdentityCondition
- type: entity
parent: [BaseChangelingObjective, BaseLivingObjective]
@ -91,11 +100,9 @@
- type: ChangelingEscapeDepartmentCondition
- type: PickRandomPerson
conditions:
- !type:RoleCondition # Do not roll changelings here. It will cause problems with weird job mindroles as they transform n stuff.
inverted: true
whitelist:
components:
- ChangelingRole
- !type:AntagonistTagCondition # Only allow on-station targets.
tags:
- OnStation # Also don't bother checking for unkillable cause here we only need the DEPARTMENT of the target and I don't foresee departments full of unkillable antags in the future.
- !type:RoleCondition
whitelist:
components:
@ -114,12 +121,20 @@
title: objective-condition-kill-maroon-title
- type: PickRandomPerson
conditions:
# Can't have multiple objectives to kill the same person.
- !type:AntagonistTagCondition # Only roll on-station targets.
tags:
- OnStation
- !type:AntagonistTagCondition # Don't roll unkillable targets.
inverted: true
tags:
- Unkillable
# Kill and Escape objectives should have different targets.
- !type:ObjectiveTargetCondition
inverted: true
whitelist:
components:
- KillPersonCondition
- ChangelingEscapeIdentityCondition
- type: KillPersonCondition
requireDead: true
requireMaroon: true

View file

@ -0,0 +1,17 @@
- type: antagTag
id: OffStation # The antag comes from space or is otherwise uninvolved in the station gameplay loop. (Ex. Ninja, Nukies, Blob)
- type: antagTag
id: OnStation # The antag comes from the station or is otherwise involved in the station gameplay loop. (Ex. Traitor, Thieves, Lings)
- type: antagTag
id: Unkillable # The antag cannot be killed by conventional means or is fully unkillable. (Ex. Lings)
- type: antagTag
id: Roundstart # This antag is selected at the start of the round. (Ex. Traitors, Initial Infected, Revs)
- type: antagTag
id: Midround # This antag can be chosen midround via events. (Ex. Clones, Initial Infected)
- type: antagTag
id: Converted # This antag is selected by being converted midround. (Ex. Revs, Zombies, Xorgs)

View file

@ -5,6 +5,10 @@
setPreference: true
objective: roles-antag-changeling-objective
guides: [ Changelings ]
tags:
- OnStation
- Roundstart
- Unkillable
requirements:
- !type:OverallPlaytimeRequirement
time: 5h

View file

@ -3,6 +3,9 @@
name: roles-antag-dragon-name
antagonist: true
objective: roles-antag-dragon-objective
tags:
- OffStation
- Midround
- type: antagSpecifier
parent: RestrictedAntag

View file

@ -5,6 +5,9 @@
setPreference: false
objective: roles-antag-space-ninja-objective
guides: [ SpaceNinja ]
tags:
- OffStation
- Midround
- type: antagSpecifier
parent: RestrictedAntag

View file

@ -8,6 +8,9 @@
- !type:OverallPlaytimeRequirement
time: 5h
guides: [ NuclearOperatives ]
tags:
- OffStation
- Roundstart
- type: antag
id: NukeopsMedic
@ -22,6 +25,9 @@
role: JobChemist
time: 3h
guides: [ NuclearOperatives ]
tags:
- OffStation
- Roundstart
- type: antag
id: NukeopsCommander
@ -37,6 +43,9 @@
time: 5h
# should be changed to nukie playtime when thats tracked (wyci)
guides: [ NuclearOperatives ]
tags:
- OffStation
- Roundstart
- type: antagSpecifier
abstract: true

View file

@ -4,6 +4,9 @@
antagonist: true
objective: roles-antag-paradox-clone-objective
setPreference: false
tags:
- OffStation
- Midround
- type: antagSpecifier
parent: RestrictedAntag

View file

@ -5,6 +5,9 @@
setPreference: true
objective: roles-antag-rev-head-objective
guides: [ Revolutionaries ]
tags:
- OnStation
- Roundstart
requirements:
- !type:OverallPlaytimeRequirement
time: 1h
@ -44,6 +47,9 @@
setPreference: false
objective: roles-antag-rev-objective
guides: [ Revolutionaries ]
tags:
- OnStation
- Converted
- type: startingGear
id: HeadRevGear

View file

@ -5,6 +5,9 @@
setPreference: true
objective: roles-antag-thief-objective
guides: [ Thieves ]
tags:
- OnStation
- Roundstart
requirements:
- !type:OverallPlaytimeRequirement
time: 1h

View file

@ -5,6 +5,9 @@
setPreference: true
objective: roles-antag-syndicate-agent-objective
guides: [ Traitors ]
tags:
- OnStation
- Roundstart
requirements:
- !type:OverallPlaytimeRequirement
time: 1h
@ -32,6 +35,9 @@
setPreference: true
objective: roles-antag-syndicate-agent-sleeper-objective
guides: [ Traitors ]
tags:
- OnStation
- Midround
requirements:
- !type:OverallPlaytimeRequirement
time: 1h
@ -108,7 +114,7 @@
- WeaponPistolViper
- Syringe
- Syringe
- type: startingGear
id: SyndicateReinforcementSpy
equipment:

View file

@ -11,10 +11,14 @@
antagonist: true
# setPreference: true # Disabled as roundstart gamemode until reworked
objective: roles-antag-wizard-objective # TODO: maybe give random objs and stationary ones from AntagObjectives and AntagRandomObjectives
guides: [ Wizard ]
tags:
- OffStation
# - Roundstart - Not currently available roundstart
- Midround
requirements: # I hate time locked roles but this should be enough time for someone to be acclimated
- !type:OverallPlaytimeRequirement
time: 5h
guides: [ Wizard ]
- type: antagSpecifier
parent: RestrictedAntag

View file

@ -4,11 +4,14 @@
antagonist: true
setPreference: true
objective: roles-antag-mothership-core-objective
guides: [ Xenoborgs ]
tags:
- OffStation
- Roundstart
requirements:
- !type:RoleTimeRequirement
role: JobBorg
time: 18000 # 5 hrs
guides: [ Xenoborgs ]
- type: antagSpecifier
parent: RestrictedAntag
@ -22,11 +25,15 @@
antagonist: true
setPreference: true
objective: roles-antag-xenoborg-objective
guides: [ Xenoborgs ]
tags:
- OffStation
- Roundstart
- Converted
requirements:
- !type:RoleTimeRequirement
role: JobBorg
time: 18000 # 5 hrs
guides: [ Xenoborgs ]
- type: antagSpecifier
abstract: true

View file

@ -5,6 +5,10 @@
setPreference: true
objective: roles-antag-initial-infected-objective
guides: [ Zombies ]
tags:
- OnStation
- Roundstart
- Midround # Breakout event
requirements:
- !type:OverallPlaytimeRequirement
time: 1h
@ -40,3 +44,6 @@
setPreference: false
objective: roles-antag-zombie-objective
guides: [ Zombies ]
tags:
- OnStation
- Converted

View file

@ -184,7 +184,8 @@ def generate_description_for_entries(entries: List[Any]) -> str:
for entry in sorted(group, key=lambda x: x["time"]):
for change in entry["changes"]:
emoji = TYPES_TO_EMOJI.get(change["type"], "")
if EXPERIMENTAL_LABEL in entry["labels"]:
labels = entry.get("labels") or []
if EXPERIMENTAL_LABEL in labels:
emoji = f"{emoji}{EXPERIMENTAL_EMOJI}"
msg = change["message"]
desc.write(f"<li>{emoji} {html.escape(msg)}</li>")

View file

@ -242,7 +242,8 @@ def changelog_entries_to_message_lines(entries: Iterable[ChangelogEntry]) -> lis
emoji = TYPES_TO_EMOJI.get(change["type"], "❓")
message = change["message"]
if EXPERIMENTAL_LABEL in entry["labels"]:
labels = entry.get("labels") or []
if EXPERIMENTAL_LABEL in labels:
emoji = f"{emoji}{EXPERIMENTAL_EMOJI}"
message_lines.append(create_change_line(emoji, message, url))