Merge remote-tracking branch 'upstream/master' into staging

This commit is contained in:
Vasilis The Pikachu
2025-10-12 21:37:42 +02:00
256 changed files with 7453 additions and 4715 deletions

View File

@@ -1,6 +1,7 @@
<DefaultWindow xmlns="https://spacestation14.io" <DefaultWindow xmlns="https://spacestation14.io"
MinSize="650 290"> MinSize="650 290">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<!-- Privileged and target IDs, crew manifest button. -->
<GridContainer Columns="2"> <GridContainer Columns="2">
<GridContainer Columns="3" HorizontalExpand="True"> <GridContainer Columns="3" HorizontalExpand="True">
<Label Text="{Loc 'id-card-console-window-privileged-id'}" /> <Label Text="{Loc 'id-card-console-window-privileged-id'}" />
@@ -16,6 +17,7 @@
</BoxContainer> </BoxContainer>
</GridContainer> </GridContainer>
<Control MinSize="0 8" /> <Control MinSize="0 8" />
<!-- Full name and job title editing. -->
<GridContainer Columns="3" HSeparationOverride="4"> <GridContainer Columns="3" HSeparationOverride="4">
<Label Name="FullNameLabel" Text="{Loc 'id-card-console-window-full-name-label'}" /> <Label Name="FullNameLabel" Text="{Loc 'id-card-console-window-full-name-label'}" />
<LineEdit Name="FullNameLineEdit" HorizontalExpand="True" /> <LineEdit Name="FullNameLineEdit" HorizontalExpand="True" />
@@ -26,10 +28,19 @@
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" /> <Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
</GridContainer> </GridContainer>
<Control MinSize="0 8" /> <Control MinSize="0 8" />
<GridContainer Columns="2"> <!-- Job preset selection, grant/revoke all access buttons. -->
<BoxContainer Margin="0 8 0 4">
<BoxContainer>
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" /> <Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" /> <OptionButton Name="JobPresetOptionButton" />
</GridContainer> </BoxContainer>
<Control HorizontalExpand="True"/>
<BoxContainer>
<Button Name="SelectAllButton" Text="{Loc 'id-card-console-window-select-all-button'}" />
<Button Name="DeselectAllButton" Text="{Loc 'id-card-console-window-deselect-all-button'}" />
</BoxContainer>
</BoxContainer>
<!-- Individual access buttons -->
<Control Name="AccessLevelControlContainer" /> <Control Name="AccessLevelControlContainer" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -79,6 +79,18 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1); JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1);
} }
SelectAllButton.OnPressed += _ =>
{
SetAllAccess(true);
SubmitData();
};
DeselectAllButton.OnPressed += _ =>
{
SetAllAccess(false);
SubmitData();
};
JobPresetOptionButton.OnItemSelected += SelectJobPreset; JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager); _accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons); AccessLevelControlContainer.AddChild(_accessButtons);
@@ -89,14 +101,13 @@ namespace Content.Client.Access.UI
} }
} }
private void ClearAllAccess() /// <param name="enabled">If true, every individual access button will be pressed. If false, each will be depressed.</param>
private void SetAllAccess(bool enabled)
{ {
foreach (var button in _accessButtons.ButtonsList.Values) foreach (var button in _accessButtons.ButtonsList.Values)
{ {
if (button.Pressed) if (!button.Disabled && button.Pressed != enabled)
{ button.Pressed = enabled;
button.Pressed = false;
}
} }
} }
@@ -110,7 +121,7 @@ namespace Content.Client.Access.UI
JobTitleLineEdit.Text = Loc.GetString(job.Name); JobTitleLineEdit.Text = Loc.GetString(job.Name);
args.Button.SelectId(args.Id); args.Button.SelectId(args.Id);
ClearAllAccess(); SetAllAccess(false);
// this is a sussy way to do this // this is a sussy way to do this
foreach (var access in job.Access) foreach (var access in job.Access)

View File

@@ -226,7 +226,7 @@ public sealed partial class BanPanel : DefaultWindow
var roleGroupCheckbox = new Button var roleGroupCheckbox = new Button
{ {
Name = $"{groupName}GroupCheckbox", Name = $"{groupName}GroupCheckbox",
Text = "Ban all", Text = Loc.GetString("role-bans-ban-group"),
Margin = new Thickness(0, 0, 5, 0), Margin = new Thickness(0, 0, 5, 0),
ToggleMode = true, ToggleMode = true,
}; };
@@ -391,7 +391,7 @@ public sealed partial class BanPanel : DefaultWindow
TimeLine.Text = args.Text; TimeLine.Text = args.Text;
if (!double.TryParse(args.Text, out var result)) if (!double.TryParse(args.Text, out var result))
{ {
ExpiresLabel.Text = "err"; ExpiresLabel.Text = Loc.GetString("ban-panel-expiry-error");
ErrorLevel |= ErrorLevelEnum.Minutes; ErrorLevel |= ErrorLevelEnum.Minutes;
TimeLine.ModulateSelfOverride = Color.Red; TimeLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled(); UpdateSubmitEnabled();

View File

@@ -166,7 +166,7 @@ public sealed class AdminLogsEui : BaseEui
ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
{ {
Maximized = false, Maximized = false,
Title = "Admin Logs", Title = Loc.GetString("admin-logs-title"),
Monitor = monitor, Monitor = monitor,
Width = 1100, Width = 1100,
Height = 400 Height = 400

View File

@@ -1,14 +0,0 @@
using Content.Shared.Crayon;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Client.Crayon
{
[RegisterComponent]
public sealed partial class CrayonComponent : SharedCrayonComponent
{
[ViewVariables(VVAccess.ReadWrite)] public bool UIUpdateNeeded;
[ViewVariables] public int Charges { get; set; }
[ViewVariables] public int Capacity { get; set; }
}
}

View File

@@ -1,67 +1,52 @@
using Content.Client.Items; using Content.Client.Items;
using Content.Client.Message; using Content.Client.Message;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Crayon; using Content.Shared.Crayon;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Localization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Client.Crayon; namespace Content.Client.Crayon;
public sealed class CrayonSystem : SharedCrayonSystem public sealed class CrayonSystem : SharedCrayonSystem
{ {
// Didn't do in shared because I don't think most of the server stuff can be predicted. [Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<CrayonComponent, ComponentHandleState>(OnCrayonHandleState);
Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent));
}
private static void OnCrayonHandleState(EntityUid uid, CrayonComponent component, ref ComponentHandleState args) Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent, _charges, _entityManager));
{
if (args.Current is not CrayonComponentState state) return;
component.Color = state.Color;
component.SelectedState = state.State;
component.Charges = state.Charges;
component.Capacity = state.Capacity;
component.UIUpdateNeeded = true;
} }
private sealed class StatusControl : Control private sealed class StatusControl : Control
{ {
private readonly CrayonComponent _parent; private readonly Entity<CrayonComponent> _crayon;
private readonly SharedChargesSystem _charges;
private readonly RichTextLabel _label; private readonly RichTextLabel _label;
private readonly int _capacity;
public StatusControl(CrayonComponent parent) public StatusControl(Entity<CrayonComponent> crayon, SharedChargesSystem charges, EntityManager entityManage)
{ {
_parent = parent; _crayon = crayon;
_charges = charges;
_capacity = entityManage.GetComponent<LimitedChargesComponent>(_crayon.Owner).MaxCharges;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label); AddChild(_label);
parent.UIUpdateNeeded = true;
} }
protected override void FrameUpdate(FrameEventArgs args) protected override void FrameUpdate(FrameEventArgs args)
{ {
base.FrameUpdate(args); base.FrameUpdate(args);
if (!_parent.UIUpdateNeeded)
{
return;
}
_parent.UIUpdateNeeded = false;
_label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label", _label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label",
("color",_parent.Color), ("color",_crayon.Comp.Color),
("state",_parent.SelectedState), ("state",_crayon.Comp.SelectedState),
("charges", _parent.Charges), ("charges", _charges.GetCurrentCharges(_crayon.Owner)),
("capacity",_parent.Capacity))); ("capacity", _capacity)));
} }
} }
} }

View File

@@ -100,11 +100,11 @@ public sealed partial class CreditsWindow : DefaultWindow
var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal }; var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal };
var previousPageButton = new Button { Text = "Previous Page" }; var previousPageButton = new Button { Text = Loc.GetString("credits-window-previous-page-button") };
previousPageButton.OnPressed += previousPageButton.OnPressed +=
_ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage); _ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage);
var nextPageButton = new Button { Text = "Next Page" }; var nextPageButton = new Button { Text = Loc.GetString("credits-window-next-page-button") };
nextPageButton.OnPressed += nextPageButton.OnPressed +=
_ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage); _ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage);

View File

@@ -58,7 +58,7 @@
StyleClasses="LabelBig" /> StyleClasses="LabelBig" />
<BoxContainer Orientation="Horizontal" <BoxContainer Orientation="Horizontal"
Margin="0 0 0 5"> Margin="0 0 0 5">
<Label Text="{Loc 'crew-monitoring-user-interface-job'}" <Label Text="{Loc 'crew-monitoring-ui-job-label'}"
FontColorOverride="DarkGray" /> FontColorOverride="DarkGray" />
<TextureRect Name="PersonJobIcon" <TextureRect Name="PersonJobIcon"
TextureScale="2 2" TextureScale="2 2"

View File

@@ -0,0 +1,5 @@
using Content.Shared.Damage.Systems;
namespace Content.Client.Damage.Systems;
public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem;

View File

@@ -4,11 +4,11 @@
<BoxContainer Orientation="Vertical" HorizontalExpand="True"> <BoxContainer Orientation="Vertical" HorizontalExpand="True">
<humanoid:MarkingPicker Name="MarkingPickerWidget" /> <humanoid:MarkingPicker Name="MarkingPickerWidget" />
<BoxContainer> <BoxContainer>
<CheckBox Name="MarkingForced" Text="Force" Pressed="True" /> <CheckBox Name="MarkingForced" Text="{Loc humanoid-marking-modifier-force}" Pressed="True" />
<CheckBox Name="MarkingIgnoreSpecies" Text="Ignore Species" Pressed="True" /> <CheckBox Name="MarkingIgnoreSpecies" Text="{Loc humanoid-marking-modifier-ignore-species}" Pressed="True" />
</BoxContainer> </BoxContainer>
<Collapsible HorizontalExpand="True"> <Collapsible HorizontalExpand="True">
<CollapsibleHeading Title="Base layers" /> <CollapsibleHeading Title="{Loc humanoid-marking-modifier-base-layers}" />
<CollapsibleBody HorizontalExpand="True"> <CollapsibleBody HorizontalExpand="True">
<BoxContainer Name="BaseLayersContainer" Orientation="Vertical" HorizontalExpand="True" /> <BoxContainer Name="BaseLayersContainer" Orientation="Vertical" HorizontalExpand="True" />
</CollapsibleBody> </CollapsibleBody>

View File

@@ -118,7 +118,7 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
}); });
_enable = new CheckBox _enable = new CheckBox
{ {
Text = "Enable", Text = Loc.GetString("humanoid-marking-modifier-enable"),
HorizontalAlignment = HAlignment.Right HorizontalAlignment = HAlignment.Right
}; };
@@ -134,8 +134,8 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
OnStateChanged!(); OnStateChanged!();
}; };
var lineEditBox = new BoxContainer(); var lineEditBox = new BoxContainer { SeparationOverride = 4 };
lineEditBox.AddChild(new Label { Text = "Prototype id: "}); lineEditBox.AddChild(new Label { Text = Loc.GetString("humanoid-marking-modifier-prototype-id") });
// TODO: This line edit should really be an options / dropdown selector, not text. // TODO: This line edit should really be an options / dropdown selector, not text.
_lineEdit = new() { MinWidth = 200 }; _lineEdit = new() { MinWidth = 200 };

View File

@@ -607,6 +607,7 @@ namespace Content.Client.Lobby.UI
_species.Clear(); _species.Clear();
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart)); _species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
_species.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase));
var speciesIds = _species.Select(o => o.ID).ToList(); var speciesIds = _species.Select(o => o.ID).ToList();
for (var i = 0; i < _species.Count; i++) for (var i = 0; i < _species.Count; i++)

View File

@@ -62,7 +62,7 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
continue; continue;
if (!LocalizedNames.TryGetValue(netEntity, out var name)) if (!LocalizedNames.TryGetValue(netEntity, out var name))
name = "Unknown"; name = Loc.GetString("navmap-unknown-entity");
var message = name + "\n" + Loc.GetString("navmap-location", var message = name + "\n" + Loc.GetString("navmap-location",
("x", MathF.Round(blip.Coordinates.X)), ("x", MathF.Round(blip.Coordinates.X)),

View File

@@ -1,7 +1,7 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring" xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'crew-monitoring-user-interface-title'}" Title="{Loc crew-monitoring-ui-title}"
Resizable="False" Resizable="False"
SetSize="1210 700" SetSize="1210 700"
MinSize="1210 700"> MinSize="1210 700">
@@ -11,12 +11,12 @@
<BoxContainer Orientation="Vertical" Margin="0 0 10 0"> <BoxContainer Orientation="Vertical" Margin="0 0 10 0">
<controls:StripeBack> <controls:StripeBack>
<PanelContainer> <PanelContainer>
<Label Name="StationName" Text="Unknown station" Align="Center" Margin="0 5 0 3"/> <Label Name="StationName" Text="{Loc crew-monitoring-ui-no-station-label}" Align="Center" Margin="0 5 0 3"/>
</PanelContainer> </PanelContainer>
</controls:StripeBack> </controls:StripeBack>
<LineEdit Name="SearchLineEdit" HorizontalExpand="True" <LineEdit Name="SearchLineEdit" HorizontalExpand="True"
PlaceHolder="{Loc crew-monitor-filter-line-placeholder}" /> PlaceHolder="{Loc crew-monitoring-ui-filter-line-placeholder}" />
<ScrollContainer Name="SensorScroller" <ScrollContainer Name="SensorScroller"
VerticalExpand="True" VerticalExpand="True"
@@ -29,7 +29,7 @@
<!-- Table rows are filled by code --> <!-- Table rows are filled by code -->
</BoxContainer> </BoxContainer>
<Label Name="NoServerLabel" <Label Name="NoServerLabel"
Text="{Loc 'crew-monitoring-user-interface-no-server'}" Text="{Loc crew-monitoring-ui-no-server-label}"
StyleClasses="LabelHeading" StyleClasses="LabelHeading"
FontColorOverride="Red" FontColorOverride="Red"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -42,8 +42,8 @@
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" /> <PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom"> <BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'crew-monitoring-user-interface-flavor-left'}" StyleClasses="WindowFooterText" /> <Label Text="{Loc crew-monitoring-ui-flavor-left-label}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'crew-monitoring-user-interface-flavor-right'}" StyleClasses="WindowFooterText" <Label Text="{Loc crew-monitoring-ui-flavor-right-label}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" /> HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered" <TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/> VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>

View File

@@ -155,7 +155,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
HorizontalExpand = true, HorizontalExpand = true,
}; };
deparmentLabel.SetMessage(Loc.GetString("crew-monitoring-user-interface-no-department")); deparmentLabel.SetMessage(Loc.GetString("crew-monitoring-ui-no-department-label"));
deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription); deparmentLabel.StyleClasses.Add(StyleNano.StyleClassTooltipActionDescription);
SensorsTable.AddChild(deparmentLabel); SensorsTable.AddChild(deparmentLabel);

View File

@@ -1,24 +1,24 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="NPC debug" Title="{Loc npc-debug-overlay-window-title}"
MinSize="200 200"> MinSize="200 200">
<BoxContainer Name="Options" Orientation="Vertical" Margin="8 8"> <BoxContainer Name="Options" Orientation="Vertical" Margin="8 8">
<controls:StripeBack> <controls:StripeBack>
<Label Text="NPC" HorizontalAlignment="Center"/> <Label Text="{Loc npc-debug-overlay-window-section-npc-label}" HorizontalAlignment="Center"/>
</controls:StripeBack> </controls:StripeBack>
<BoxContainer Name="NPCBox" Orientation="Vertical"> <BoxContainer Name="NPCBox" Orientation="Vertical">
<CheckBox Name="NPCThonk" Text="Thonk"/> <CheckBox Name="NPCThonk" Text="{Loc npc-debug-overlay-window-show-htn-tree-checkbox}"/>
</BoxContainer> </BoxContainer>
<controls:StripeBack> <controls:StripeBack>
<Label Text="Pathfinder" HorizontalAlignment="Center"/> <Label Text="{Loc npc-debug-overlay-window-section-pathfinder-label}" HorizontalAlignment="Center"/>
</controls:StripeBack> </controls:StripeBack>
<BoxContainer Name="PathfinderBox" Orientation="Vertical"> <BoxContainer Name="PathfinderBox" Orientation="Vertical">
<CheckBox Name="PathCrumbs" Text="Breadcrumbs"/> <CheckBox Name="PathCrumbs" Text="{Loc npc-debug-overlay-window-path-breadcrumbs-checkbox}"/>
<CheckBox Name="PathPolys" Text="Polygons"/> <CheckBox Name="PathPolys" Text="{Loc npc-debug-overlay-window-path-polygons-checkbox}"/>
<CheckBox Name="PathNeighbors" Text="Neighbors"/> <CheckBox Name="PathNeighbors" Text="{Loc npc-debug-overlay-window-path-neighbors-checkbox}"/>
<CheckBox Name="PathRouteCosts" Text="Route costs"/> <CheckBox Name="PathRouteCosts" Text="{Loc npc-debug-overlay-window-path-route-costs-checkbox}"/>
<CheckBox Name="PathRoutes" Text="Routes"/> <CheckBox Name="PathRoutes" Text="{Loc npc-debug-overlay-window-path-routes-checkbox}"/>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -1,8 +0,0 @@
using Content.Shared.PAI;
namespace Content.Client.PAI
{
public sealed class PAISystem : SharedPAISystem
{
}
}

View File

@@ -36,7 +36,7 @@ public sealed class JointVisualsOverlay : Overlay
if (xform.MapID != args.MapId) if (xform.MapID != args.MapId)
continue; continue;
var other = _entManager.GetEntity(visuals.Target); var other = visuals.Target;
if (!xformQuery.TryGetComponent(other, out var otherXform)) if (!xformQuery.TryGetComponent(other, out var otherXform))
continue; continue;
@@ -58,7 +58,7 @@ public sealed class JointVisualsOverlay : Overlay
var posA = xformSystem.ToMapCoordinates(coordsA).Position; var posA = xformSystem.ToMapCoordinates(coordsA).Position;
var posB = xformSystem.ToMapCoordinates(coordsB).Position; var posB = xformSystem.ToMapCoordinates(coordsB).Position;
var diff = (posB - posA); var diff = posB - posA;
var length = diff.Length(); var length = diff.Length();
var midPoint = diff / 2f + posA; var midPoint = diff / 2f + posA;

View File

@@ -9,7 +9,7 @@
<!-- Station name --> <!-- Station name -->
<controls:StripeBack> <controls:StripeBack>
<PanelContainer> <PanelContainer>
<Label Name="StationName" Text="Unknown station" StyleClasses="LabelBig" Align="Center"/> <Label Name="StationName" Text="{Loc 'station-map-unknown-station'}" StyleClasses="LabelBig" Align="Center"/>
</PanelContainer> </PanelContainer>
</controls:StripeBack> </controls:StripeBack>

View File

@@ -58,25 +58,6 @@ public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
} }
} }
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
base.UpdateEntityAppearance(entity, prototype); base.UpdateEntityAppearance(entity, prototype);
} }
} }

View File

@@ -0,0 +1,5 @@
using Content.Shared.Silicons.Bots;
namespace Content.Client.Silicons.Bots;
public sealed partial class HugBotSystem : SharedHugBotSystem;

View File

@@ -11,7 +11,7 @@ public sealed class StatValuesEui : BaseEui
public StatValuesEui() public StatValuesEui()
{ {
_window = new StatsWindow(); _window = new StatsWindow();
_window.Title = "Melee stats"; _window.Title = Loc.GetString("stat-values-ui-title");
_window.OpenCentered(); _window.OpenCentered();
_window.OnClose += Closed; _window.OnClose += Closed;
} }

View File

@@ -232,7 +232,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
helper.ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters helper.ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
{ {
Maximized = false, Maximized = false,
Title = "Admin Help", Title = Loc.GetString("bwoink-admin-title"),
Monitor = monitor, Monitor = monitor,
Width = 900, Width = 900,
Height = 500 Height = 500

View File

@@ -1,47 +1,47 @@
<DefaultWindow Title="{Loc Make Ghost Role}" <DefaultWindow Title="{Loc make-ghost-roles-window-title}"
xmlns="https://spacestation14.io"> xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RoleEntityLabel" Text="Entity" /> <Label Name="RoleEntityLabel" Text="{Loc make-ghost-roles-window-entity-label}" />
<Label Name="RoleEntity" Text="" /> <Label Name="RoleEntity" Text="" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RoleNameLabel" Text="Role Name" /> <Label Name="RoleNameLabel" Text="{Loc make-ghost-roles-window-role-name-label}" />
<LineEdit Name="RoleName" HorizontalExpand="True" /> <LineEdit Name="RoleName" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RoleDescriptionLabel" Text="Role Description" /> <Label Name="RoleDescriptionLabel" Text="{Loc make-ghost-roles-window-role-description-label}" />
<LineEdit Name="RoleDescription" HorizontalExpand="True" /> <LineEdit Name="RoleDescription" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RoleRulesLabel" Text="Role Rules" /> <Label Name="RoleRulesLabel" Text="{Loc make-ghost-roles-window-role-rules-label}" />
<LineEdit Name="RoleRules" HorizontalExpand="True" Text="{Loc ghost-role-component-default-rules}" /> <LineEdit Name="RoleRules" HorizontalExpand="True" Text="{Loc ghost-role-component-default-rules}" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="MakeSentientLabel" Text="Make Sentient" /> <Label Name="MakeSentientLabel" Text="{Loc make-ghost-roles-window-make-sentient-label}" />
<CheckBox Name="MakeSentientCheckbox" /> <CheckBox Name="MakeSentientCheckbox" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RaffleLabel" Text="Raffle Role?" /> <Label Name="RaffleLabel" Text="{Loc make-ghost-roles-window-raffle-role-label}" />
<OptionButton Name="RaffleButton" /> <OptionButton Name="RaffleButton" />
</BoxContainer> </BoxContainer>
<BoxContainer Name="RaffleCustomSettingsContainer" Orientation="Vertical" Visible="False"> <BoxContainer Name="RaffleCustomSettingsContainer" Orientation="Vertical" Visible="False">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RaffleInitialDurationLabel" Text="Initial Duration (s)" /> <Label Name="RaffleInitialDurationLabel" Text="{Loc make-ghost-roles-window-initial-duration-label}" />
<SpinBox Name="RaffleInitialDuration" HorizontalExpand="True" /> <SpinBox Name="RaffleInitialDuration" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RaffleJoinExtendsDurationByLabel" Text="Joins Extend By (s)" /> <Label Name="RaffleJoinExtendsDurationByLabel" Text="{Loc make-ghost-roles-window-join-extends-by-label}" />
<SpinBox Name="RaffleJoinExtendsDurationBy" HorizontalExpand="True" /> <SpinBox Name="RaffleJoinExtendsDurationBy" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="RaffleMaxDurationLabel" Text="Max Duration (s)" /> <Label Name="RaffleMaxDurationLabel" Text="{Loc make-ghost-roles-window-max-duration-label}" />
<SpinBox Name="RaffleMaxDuration" HorizontalExpand="True" /> <SpinBox Name="RaffleMaxDuration" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Button Name="MakeButton" Text="Make" /> <Button Name="MakeButton" Text="{Loc make-ghost-roles-window-make-button}" />
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>

View File

@@ -62,8 +62,8 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
RaffleMaxDuration.ValueChanged += OnRaffleDurationChanged; RaffleMaxDuration.ValueChanged += OnRaffleDurationChanged;
RaffleButton.AddItem("Don't raffle", RaffleDontRaffleId); RaffleButton.AddItem(Loc.GetString("make-ghost-roles-window-raffle-not-button"), RaffleDontRaffleId);
RaffleButton.AddItem("Custom settings", RaffleCustomRaffleId); RaffleButton.AddItem(Loc.GetString("make-ghost-roles-window-raffle-custom-settings-button"), RaffleCustomRaffleId);
var raffleProtos = var raffleProtos =
_prototypeManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>(); _prototypeManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>();
@@ -74,7 +74,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
_rafflePrototypes.Add(raffleProto); _rafflePrototypes.Add(raffleProto);
var s = raffleProto.Settings; var s = raffleProto.Settings;
var label = var label =
$"{raffleProto.ID} (initial {s.InitialDuration}s, max {s.MaxDuration}s, join adds {s.JoinExtendsDurationBy}s)"; Loc.GetString("make-ghost-roles-window-raffle-settings-label", ("id", raffleProto.ID), ("initialDuration", s.InitialDuration), ("maxDuration", s.MaxDuration), ("joinExtendsDurationBy", s.JoinExtendsDurationBy));
RaffleButton.AddItem(label, idx++); RaffleButton.AddItem(label, idx++);
} }
@@ -92,7 +92,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
if (RaffleInitialDuration.Value > RaffleMaxDuration.Value) if (RaffleInitialDuration.Value > RaffleMaxDuration.Value)
{ {
MakeButton.Disabled = true; MakeButton.Disabled = true;
MakeButton.ToolTip = "The initial duration must not exceed the maximum duration."; MakeButton.ToolTip = Loc.GetString("make-ghost-roles-window-raffle-warning-tooltip");
} }
else else
{ {

View File

@@ -111,7 +111,7 @@ public sealed class StorageWindow : BaseWindow
HorizontalExpand = true, HorizontalExpand = true,
Name = "StorageLabel", Name = "StorageLabel",
ClipText = true, ClipText = true,
Text = "Dummy", Text = Loc.GetString("comp-storage-window-dummy"),
StyleClasses = StyleClasses =
{ {
"FancyWindowTitle", "FancyWindowTitle",

View File

@@ -33,7 +33,7 @@ public sealed class MaterialArbitrageTest
[ [
"BaseChemistryEmptyVial", "DrinkShotGlass", "SodiumLightTube", "DrinkGlassCoupeShaped", "BaseChemistryEmptyVial", "DrinkShotGlass", "SodiumLightTube", "DrinkGlassCoupeShaped",
"LedLightBulb", "ExteriorLightTube", "LightTube", "DrinkGlass", "DimLightBulb", "LightBulb", "LedLightTube", "LedLightBulb", "ExteriorLightTube", "LightTube", "DrinkGlass", "DimLightBulb", "LightBulb", "LedLightTube",
"SheetRGlass1", "ChemistryEmptyBottle01", "WarmLightBulb", "ChemistryEmptyBottle01", "WarmLightBulb",
]; ];
private readonly HashSet<string> _compositionArbitrageIgnore = private readonly HashSet<string> _compositionArbitrageIgnore =

View File

@@ -69,7 +69,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb bolt = new() Verb bolt = new()
{ {
Text = bolts.BoltsDown ? "Unbolt" : "Bolt", Text = Loc.GetString(bolts.BoltsDown ? "admin-verbs-unbolt" : "admin-verbs-bolt"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = bolts.BoltsDown Icon = bolts.BoltsDown
? new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/unbolt.png")) ? new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/unbolt.png"))
@@ -91,7 +91,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb emergencyAccess = new() Verb emergencyAccess = new()
{ {
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On", Text = Loc.GetString(airlockComp.EmergencyAccess ? "admin-verbs-emergency-access-off" : "admin-verbs-emergency-access-on"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
Act = () => Act = () =>
@@ -111,7 +111,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb rejuvenate = new() Verb rejuvenate = new()
{ {
Text = "Rejuvenate", Text = Loc.GetString("admin-verbs-rejuvenate"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rejuvenate.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rejuvenate.png")),
Act = () => Act = () =>
@@ -129,7 +129,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb makeIndestructible = new() Verb makeIndestructible = new()
{ {
Text = "Make Indestructible", Text = Loc.GetString("admin-verbs-make-indestructible"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
Act = () => Act = () =>
@@ -146,7 +146,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb makeVulnerable = new() Verb makeVulnerable = new()
{ {
Text = "Make Vulnerable", Text = Loc.GetString("admin-verbs-make-vulnerable"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
Act = () => Act = () =>
@@ -164,7 +164,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb refillBattery = new() Verb refillBattery = new()
{ {
Text = "Refill Battery", Text = Loc.GetString("admin-verbs-refill-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")),
Act = () => Act = () =>
@@ -179,7 +179,7 @@ public sealed partial class AdminVerbSystem
Verb drainBattery = new() Verb drainBattery = new()
{ {
Text = "Drain Battery", Text = Loc.GetString("admin-verbs-drain-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")),
Act = () => Act = () =>
@@ -194,7 +194,7 @@ public sealed partial class AdminVerbSystem
Verb infiniteBattery = new() Verb infiniteBattery = new()
{ {
Text = "Infinite Battery", Text = Loc.GetString("admin-verbs-infinite-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")),
Act = () => Act = () =>
@@ -215,7 +215,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb blockUnanchor = new() Verb blockUnanchor = new()
{ {
Text = "Block Unanchoring", Text = Loc.GetString("admin-verbs-block-unanchoring"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/anchor.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/anchor.svg.192dpi.png")),
Act = () => Act = () =>
@@ -233,7 +233,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb refillInternalsO2 = new() Verb refillInternalsO2 = new()
{ {
Text = "Refill Internals Oxygen", Text = Loc.GetString("admin-verbs-refill-internals-oxygen"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
Act = () => Act = () =>
@@ -248,7 +248,7 @@ public sealed partial class AdminVerbSystem
Verb refillInternalsN2 = new() Verb refillInternalsN2 = new()
{ {
Text = "Refill Internals Nitrogen", Text = Loc.GetString("admin-verbs-refill-internals-nitrogen"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
Act = () => Act = () =>
@@ -263,7 +263,7 @@ public sealed partial class AdminVerbSystem
Verb refillInternalsPlasma = new() Verb refillInternalsPlasma = new()
{ {
Text = "Refill Internals Plasma", Text = Loc.GetString("admin-verbs-refill-internals-plasma"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
Act = () => Act = () =>
@@ -281,7 +281,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb refillInternalsO2 = new() Verb refillInternalsO2 = new()
{ {
Text = "Refill Internals Oxygen", Text = Loc.GetString("admin-verbs-refill-internals-oxygen"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
Act = () => RefillEquippedTanks(args.User, Gas.Oxygen), Act = () => RefillEquippedTanks(args.User, Gas.Oxygen),
@@ -293,7 +293,7 @@ public sealed partial class AdminVerbSystem
Verb refillInternalsN2 = new() Verb refillInternalsN2 = new()
{ {
Text = "Refill Internals Nitrogen", Text = Loc.GetString("admin-verbs-refill-internals-nitrogen"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"),
Act = () => RefillEquippedTanks(args.User, Gas.Nitrogen), Act = () => RefillEquippedTanks(args.User, Gas.Nitrogen),
@@ -305,7 +305,7 @@ public sealed partial class AdminVerbSystem
Verb refillInternalsPlasma = new() Verb refillInternalsPlasma = new()
{ {
Text = "Refill Internals Plasma", Text = Loc.GetString("admin-verbs-refill-internals-plasma"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
Act = () => RefillEquippedTanks(args.User, Gas.Plasma), Act = () => RefillEquippedTanks(args.User, Gas.Plasma),
@@ -318,7 +318,7 @@ public sealed partial class AdminVerbSystem
Verb sendToTestArena = new() Verb sendToTestArena = new()
{ {
Text = "Send to test arena", Text = Loc.GetString("admin-verbs-send-to-test-arena"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
@@ -339,7 +339,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb grantAllAccess = new() Verb grantAllAccess = new()
{ {
Text = "Grant All Access", Text = Loc.GetString("admin-verbs-grant-all-access"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
Act = () => Act = () =>
@@ -354,7 +354,7 @@ public sealed partial class AdminVerbSystem
Verb revokeAllAccess = new() Verb revokeAllAccess = new()
{ {
Text = "Revoke All Access", Text = Loc.GetString("admin-verbs-revoke-all-access"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
Act = () => Act = () =>
@@ -372,7 +372,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb grantAllAccess = new() Verb grantAllAccess = new()
{ {
Text = "Grant All Access", Text = Loc.GetString("admin-verbs-grant-all-access"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
Act = () => Act = () =>
@@ -387,7 +387,7 @@ public sealed partial class AdminVerbSystem
Verb revokeAllAccess = new() Verb revokeAllAccess = new()
{ {
Text = "Revoke All Access", Text = Loc.GetString("admin-verbs-revoke-all-access"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Misc/id_cards.rsi"), "default"),
Act = () => Act = () =>
@@ -405,13 +405,13 @@ public sealed partial class AdminVerbSystem
{ {
Verb adjustStack = new() Verb adjustStack = new()
{ {
Text = "Adjust Stack", Text = Loc.GetString("admin-verbs-adjust-stack"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/adjust-stack.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/adjust-stack.png")),
Act = () => Act = () =>
{ {
// Unbounded intentionally. // Unbounded intentionally.
_quickDialog.OpenDialog(player, "Adjust stack", $"Amount (max {_stackSystem.GetMaxCount(stack)})", (int newAmount) => _quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-adjust-stack"), Loc.GetString("admin-verbs-dialog-adjust-stack-amount", ("max", _stackSystem.GetMaxCount(stack))), (int newAmount) =>
{ {
_stackSystem.SetCount(args.Target, newAmount, stack); _stackSystem.SetCount(args.Target, newAmount, stack);
}); });
@@ -424,7 +424,7 @@ public sealed partial class AdminVerbSystem
Verb fillStack = new() Verb fillStack = new()
{ {
Text = "Fill Stack", Text = Loc.GetString("admin-verbs-fill-stack"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill-stack.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill-stack.png")),
Act = () => Act = () =>
@@ -440,12 +440,12 @@ public sealed partial class AdminVerbSystem
Verb rename = new() Verb rename = new()
{ {
Text = "Rename", Text = Loc.GetString("admin-verbs-rename"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rename.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rename.png")),
Act = () => Act = () =>
{ {
_quickDialog.OpenDialog(player, "Rename", "Name", (string newName) => _quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-dialog-rename-title"), Loc.GetString("admin-verbs-dialog-rename-name"), (string newName) =>
{ {
_metaSystem.SetEntityName(args.Target, newName); _metaSystem.SetEntityName(args.Target, newName);
}); });
@@ -458,12 +458,12 @@ public sealed partial class AdminVerbSystem
Verb redescribe = new() Verb redescribe = new()
{ {
Text = "Redescribe", Text = Loc.GetString("admin-verbs-redescribe"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/redescribe.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/redescribe.png")),
Act = () => Act = () =>
{ {
_quickDialog.OpenDialog(player, "Redescribe", "Description", (LongString newDescription) => _quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-dialog-redescribe-title"), Loc.GetString("admin-verbs-dialog-redescribe-description"), (LongString newDescription) =>
{ {
_metaSystem.SetEntityDescription(args.Target, newDescription.String); _metaSystem.SetEntityDescription(args.Target, newDescription.String);
}); });
@@ -476,12 +476,12 @@ public sealed partial class AdminVerbSystem
Verb renameAndRedescribe = new() Verb renameAndRedescribe = new()
{ {
Text = "Redescribe", Text = Loc.GetString("admin-verbs-rename-and-redescribe"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rename_and_redescribe.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/rename_and_redescribe.png")),
Act = () => Act = () =>
{ {
_quickDialog.OpenDialog(player, "Rename & Redescribe", "Name", "Description", _quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-dialog-rename-and-redescribe-title"), Loc.GetString("admin-verbs-dialog-rename-name"), Loc.GetString("admin-verbs-dialog-redescribe-description"),
(string newName, LongString newDescription) => (string newName, LongString newDescription) =>
{ {
var meta = MetaData(args.Target); var meta = MetaData(args.Target);
@@ -501,7 +501,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb barJobSlots = new() Verb barJobSlots = new()
{ {
Text = "Bar job slots", Text = Loc.GetString("admin-verbs-bar-job-slots"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/bar_jobslots.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/bar_jobslots.png")),
Act = () => Act = () =>
@@ -520,7 +520,7 @@ public sealed partial class AdminVerbSystem
Verb locateCargoShuttle = new() Verb locateCargoShuttle = new()
{ {
Text = "Locate Cargo Shuttle", Text = Loc.GetString("admin-verbs-locate-cargo-shuttle"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Soft/cargosoft.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Soft/cargosoft.rsi"), "icon"),
Act = () => Act = () =>
@@ -543,7 +543,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb refillBattery = new() Verb refillBattery = new()
{ {
Text = "Refill Battery", Text = Loc.GetString("admin-verbs-refill-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")),
Act = () => Act = () =>
@@ -564,7 +564,7 @@ public sealed partial class AdminVerbSystem
Verb drainBattery = new() Verb drainBattery = new()
{ {
Text = "Drain Battery", Text = Loc.GetString("admin-verbs-drain-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")),
Act = () => Act = () =>
@@ -585,7 +585,7 @@ public sealed partial class AdminVerbSystem
Verb infiniteBattery = new() Verb infiniteBattery = new()
{ {
Text = "Infinite Battery", Text = Loc.GetString("admin-verbs-infinite-battery"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")),
Act = () => Act = () =>
@@ -615,7 +615,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb haltMovement = new() Verb haltMovement = new()
{ {
Text = "Halt Movement", Text = Loc.GetString("admin-verbs-halt-movement"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/halt.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/halt.png")),
Act = () => Act = () =>
@@ -638,7 +638,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb unpauseMap = new() Verb unpauseMap = new()
{ {
Text = "Unpause Map", Text = Loc.GetString("admin-verbs-unpause-map"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/play.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/play.png")),
Act = () => Act = () =>
@@ -655,7 +655,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb pauseMap = new() Verb pauseMap = new()
{ {
Text = "Pause Map", Text = Loc.GetString("admin-verbs-pause-map"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/pause.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/pause.png")),
Act = () => Act = () =>
@@ -675,7 +675,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb snapJoints = new() Verb snapJoints = new()
{ {
Text = "Snap Joints", Text = Loc.GetString("admin-verbs-snap-joints"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/snap_joints.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/snap_joints.png")),
Act = () => Act = () =>
@@ -693,7 +693,7 @@ public sealed partial class AdminVerbSystem
{ {
Verb minigunFire = new() Verb minigunFire = new()
{ {
Text = "Make Minigun", Text = Loc.GetString("admin-verbs-make-minigun"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi"), "icon"),
Act = () => Act = () =>
@@ -712,12 +712,12 @@ public sealed partial class AdminVerbSystem
{ {
Verb setCapacity = new() Verb setCapacity = new()
{ {
Text = "Set Bullet Amount", Text = Loc.GetString("admin-verbs-set-bullet-amount"),
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/caps.rsi"), "mag-6"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/caps.rsi"), "mag-6"),
Act = () => Act = () =>
{ {
_quickDialog.OpenDialog(player, "Set Bullet Amount", $"Amount (standard {ballisticAmmo.Capacity}):", (string amount) => _quickDialog.OpenDialog(player, Loc.GetString("admin-verbs-dialog-set-bullet-amount-title"), Loc.GetString("admin-verbs-dialog-set-bullet-amount-amount", ("cap", ballisticAmmo.Capacity)), (string amount) =>
{ {
if (!int.TryParse(amount, out var result)) if (!int.TryParse(amount, out var result))
return; return;

View File

@@ -46,7 +46,11 @@ public sealed class AutoEmoteSystem : EntitySystem
if (autoEmotePrototype.WithChat) if (autoEmotePrototype.WithChat)
{ {
_chatSystem.TryEmoteWithChat(uid, autoEmotePrototype.EmoteId, autoEmotePrototype.HiddenFromChatWindow ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal); _chatSystem.TryEmoteWithChat(uid,
autoEmotePrototype.EmoteId,
autoEmotePrototype.HiddenFromChatWindow ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal,
ignoreActionBlocker: autoEmotePrototype.IgnoreActionBlocker,
forceEmote: autoEmotePrototype.Force);
} }
else else
{ {

View File

@@ -4,6 +4,7 @@ using Content.Shared.Access.Components;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Preferences; using Content.Shared.Preferences;
@@ -23,7 +24,7 @@ public sealed class OutfitSystem : EntitySystem
[Dependency] private readonly InventorySystem _invSystem = default!; [Dependency] private readonly InventorySystem _invSystem = default!;
[Dependency] private readonly SharedStationSpawningSystem _spawningSystem = default!; [Dependency] private readonly SharedStationSpawningSystem _spawningSystem = default!;
public bool SetOutfit(EntityUid target, string gear, Action<EntityUid, EntityUid>? onEquipped = null) public bool SetOutfit(EntityUid target, string gear, Action<EntityUid, EntityUid>? onEquipped = null, bool unremovable = false)
{ {
if (!EntityManager.TryGetComponent(target, out InventoryComponent? inventoryComponent)) if (!EntityManager.TryGetComponent(target, out InventoryComponent? inventoryComponent))
return false; return false;
@@ -60,6 +61,8 @@ public sealed class OutfitSystem : EntitySystem
} }
_invSystem.TryEquip(target, equipmentEntity, slot.Name, silent: true, force: true, inventory: inventoryComponent); _invSystem.TryEquip(target, equipmentEntity, slot.Name, silent: true, force: true, inventory: inventoryComponent);
if (unremovable)
EnsureComp<UnremoveableComponent>(equipmentEntity);
onEquipped?.Invoke(target, equipmentEntity); onEquipped?.Invoke(target, equipmentEntity);
} }

View File

@@ -7,7 +7,6 @@ using Content.Server.Clothing.Systems;
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage; using Content.Shared.Damage;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Server.Emoting.Systems; using Content.Server.Emoting.Systems;
@@ -21,7 +20,6 @@ namespace Content.Server.Cluwne;
public sealed class CluwneSystem : EntitySystem public sealed class CluwneSystem : EntitySystem
{ {
private static readonly ProtoId<DamageGroupPrototype> GeneticDamageGroup = "Genetic";
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -48,15 +46,14 @@ public sealed class CluwneSystem : EntitySystem
/// <summary> /// <summary>
/// On death removes active comps and gives genetic damage to prevent cloning, reduce this to allow cloning. /// On death removes active comps and gives genetic damage to prevent cloning, reduce this to allow cloning.
/// </summary> /// </summary>
private void OnMobState(EntityUid uid, CluwneComponent component, MobStateChangedEvent args) private void OnMobState(Entity<CluwneComponent> ent, ref MobStateChangedEvent args)
{ {
if (args.NewMobState == MobState.Dead) if (args.NewMobState == MobState.Dead)
{ {
RemComp<CluwneComponent>(uid); RemComp<CluwneComponent>(ent.Owner);
RemComp<ClumsyComponent>(uid); RemComp<ClumsyComponent>(ent.Owner);
RemComp<AutoEmoteComponent>(uid); RemComp<AutoEmoteComponent>(ent.Owner);
var damageSpec = new DamageSpecifier(_prototypeManager.Index(GeneticDamageGroup), 300); _damageableSystem.TryChangeDamage(ent.Owner, ent.Comp.RevertDamage);
_damageableSystem.TryChangeDamage(uid, damageSpec);
} }
} }
@@ -65,52 +62,65 @@ public sealed class CluwneSystem : EntitySystem
/// <summary> /// <summary>
/// OnStartup gives the cluwne outfit, ensures clumsy, and makes sure emote sounds are laugh. /// OnStartup gives the cluwne outfit, ensures clumsy, and makes sure emote sounds are laugh.
/// </summary> /// </summary>
private void OnComponentStartup(EntityUid uid, CluwneComponent component, ComponentStartup args) private void OnComponentStartup(Entity<CluwneComponent> ent, ref ComponentStartup args)
{ {
if (component.EmoteSoundsId == null) if (ent.Comp.EmoteSoundsId == null)
return; return;
_prototypeManager.TryIndex(component.EmoteSoundsId, out EmoteSounds);
EnsureComp<AutoEmoteComponent>(uid); _prototypeManager.TryIndex(ent.Comp.EmoteSoundsId, out EmoteSounds);
_autoEmote.AddEmote(uid, "CluwneGiggle");
EnsureComp<ClumsyComponent>(uid);
_popupSystem.PopupEntity(Loc.GetString("cluwne-transform", ("target", uid)), uid, PopupType.LargeCaution);
_audio.PlayPvs(component.SpawnSound, uid);
_nameMod.RefreshNameModifiers(uid); if (ent.Comp.RandomEmote && ent.Comp.AutoEmoteId != null)
{
EnsureComp<AutoEmoteComponent>(ent.Owner);
_autoEmote.AddEmote(ent.Owner, ent.Comp.AutoEmoteId);
}
_outfitSystem.SetOutfit(uid, "CluwneGear"); EnsureComp<ClumsyComponent>(ent.Owner);
var transformMessage = Loc.GetString(ent.Comp.TransformMessage, ("target", ent.Owner));
_popupSystem.PopupEntity(transformMessage, ent.Owner, PopupType.LargeCaution);
_audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
_nameMod.RefreshNameModifiers(ent.Owner);
_outfitSystem.SetOutfit(ent.Owner, ent.Comp.OutfitId, unremovable: true);
} }
/// <summary> /// <summary>
/// Handles the timing on autoemote as well as falling over and honking. /// Handles the timing on autoemote as well as falling over and honking.
/// </summary> /// </summary>
private void OnEmote(EntityUid uid, CluwneComponent component, ref EmoteEvent args) private void OnEmote(Entity<CluwneComponent> ent, ref EmoteEvent args)
{ {
if (args.Handled) if (args.Handled)
return; return;
args.Handled = _chat.TryPlayEmoteSound(uid, EmoteSounds, args.Emote);
if (_robustRandom.Prob(component.GiggleRandomChance)) if (!ent.Comp.RandomEmote)
return;
args.Handled = _chat.TryPlayEmoteSound(ent.Owner, EmoteSounds, args.Emote);
if (_robustRandom.Prob(ent.Comp.GiggleRandomChance))
{ {
_audio.PlayPvs(component.SpawnSound, uid); _audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
_chat.TrySendInGameICMessage(uid, "honks", InGameICChatType.Emote, ChatTransmitRange.Normal); _chat.TrySendInGameICMessage(ent.Owner, Loc.GetString(ent.Comp.GiggleEmote), InGameICChatType.Emote, ChatTransmitRange.Normal);
} }
else if (_robustRandom.Prob(component.KnockChance)) else if (_robustRandom.Prob(ent.Comp.KnockChance))
{ {
_audio.PlayPvs(component.KnockSound, uid); _audio.PlayPvs(ent.Comp.KnockSound, ent.Owner);
_stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(component.ParalyzeTime)); _stunSystem.TryUpdateParalyzeDuration(ent.Owner, TimeSpan.FromSeconds(ent.Comp.ParalyzeTime));
_chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal); _chat.TrySendInGameICMessage(ent.Owner, Loc.GetString(ent.Comp.KnockEmote), InGameICChatType.Emote, ChatTransmitRange.Normal);
} }
} }
/// <summary> /// <summary>
/// Applies "Cluwnified" prefix /// Applies "Cluwnified" prefix
/// </summary> /// </summary>
private void OnRefreshNameModifiers(Entity<CluwneComponent> entity, ref RefreshNameModifiersEvent args) private void OnRefreshNameModifiers(Entity<CluwneComponent> ent, ref RefreshNameModifiersEvent args)
{ {
args.AddModifier("cluwne-name-prefix"); args.AddModifier(ent.Comp.NamePrefix);
} }
} }

View File

@@ -1,28 +0,0 @@
using Content.Server.UserInterface;
using Content.Shared.Crayon;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
namespace Content.Server.Crayon
{
[RegisterComponent]
public sealed partial class CrayonComponent : SharedCrayonComponent
{
[DataField("useSound")] public SoundSpecifier? UseSound;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("selectableColor")]
public bool SelectableColor { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public int Charges { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
[DataField("capacity")]
public int Capacity { get; set; } = 30;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("deleteEmpty")]
public bool DeleteEmpty = true;
}
}

View File

@@ -3,6 +3,7 @@ using System.Numerics;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Decals; using Content.Server.Decals;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Charges.Systems;
using Content.Shared.Crayon; using Content.Shared.Crayon;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Decals; using Content.Shared.Decals;
@@ -12,7 +13,6 @@ using Content.Shared.Nutrition.EntitySystems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Crayon; namespace Content.Server.Crayon;
@@ -24,23 +24,27 @@ public sealed class CrayonSystem : SharedCrayonSystem
[Dependency] private readonly DecalSystem _decals = default!; [Dependency] private readonly DecalSystem _decals = default!;
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<CrayonComponent, ComponentInit>(OnCrayonInit);
SubscribeLocalEvent<CrayonComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CrayonComponent, CrayonSelectMessage>(OnCrayonBoundUI); SubscribeLocalEvent<CrayonComponent, CrayonSelectMessage>(OnCrayonBoundUI);
SubscribeLocalEvent<CrayonComponent, CrayonColorMessage>(OnCrayonBoundUIColor); SubscribeLocalEvent<CrayonComponent, CrayonColorMessage>(OnCrayonBoundUIColor);
SubscribeLocalEvent<CrayonComponent, UseInHandEvent>(OnCrayonUse, before: new[] { typeof(FoodSystem) }); SubscribeLocalEvent<CrayonComponent, UseInHandEvent>(OnCrayonUse, before: new[] { typeof(FoodSystem) });
SubscribeLocalEvent<CrayonComponent, AfterInteractEvent>(OnCrayonAfterInteract, after: new[] { typeof(FoodSystem) }); SubscribeLocalEvent<CrayonComponent, AfterInteractEvent>(OnCrayonAfterInteract, after: new[] { typeof(FoodSystem) });
SubscribeLocalEvent<CrayonComponent, DroppedEvent>(OnCrayonDropped); SubscribeLocalEvent<CrayonComponent, DroppedEvent>(OnCrayonDropped);
SubscribeLocalEvent<CrayonComponent, ComponentGetState>(OnCrayonGetState);
} }
private static void OnCrayonGetState(EntityUid uid, CrayonComponent component, ref ComponentGetState args) private void OnMapInit(Entity<CrayonComponent> ent, ref MapInitEvent args)
{ {
args.State = new CrayonComponentState(component.Color, component.SelectedState, component.Charges, component.Capacity); // Get the first one from the catalog and set it as default
var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
ent.Comp.SelectedState = decal?.ID ?? string.Empty;
Dirty(ent);
} }
private void OnCrayonAfterInteract(EntityUid uid, CrayonComponent component, AfterInteractEvent args) private void OnCrayonAfterInteract(EntityUid uid, CrayonComponent component, AfterInteractEvent args)
@@ -48,7 +52,7 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (args.Handled || !args.CanReach) if (args.Handled || !args.CanReach)
return; return;
if (component.Charges <= 0) if (_charges.IsEmpty(uid))
{ {
if (component.DeleteEmpty) if (component.DeleteEmpty)
UseUpCrayon(uid, args.User); UseUpCrayon(uid, args.User);
@@ -72,17 +76,15 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (component.UseSound != null) if (component.UseSound != null)
_audio.PlayPvs(component.UseSound, uid, AudioParams.Default.WithVariation(0.125f)); _audio.PlayPvs(component.UseSound, uid, AudioParams.Default.WithVariation(0.125f));
// Decrease "Ammo" _charges.TryUseCharge(uid);
component.Charges--;
Dirty(uid, component);
_adminLogger.Add(LogType.CrayonDraw, LogImpact.Low, $"{ToPrettyString(args.User):user} drew a {component.Color:color} {component.SelectedState}"); _adminLogger.Add(LogType.CrayonDraw, LogImpact.Low, $"{ToPrettyString(args.User):user} drew a {component.Color:color} {component.SelectedState}");
args.Handled = true; args.Handled = true;
if (component.DeleteEmpty && component.Charges <= 0) if (component.DeleteEmpty && _charges.IsEmpty(uid))
UseUpCrayon(uid, args.User); UseUpCrayon(uid, args.User);
else else
_uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState)); _uiSystem.ServerSendUiMessage(uid, CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
} }
private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args) private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args)
@@ -91,14 +93,12 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (args.Handled) if (args.Handled)
return; return;
if (!_uiSystem.HasUi(uid, SharedCrayonComponent.CrayonUiKey.Key)) if (!_uiSystem.HasUi(uid, CrayonUiKey.Key))
{
return; return;
}
_uiSystem.TryToggleUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User); _uiSystem.TryToggleUi(uid, CrayonUiKey.Key, args.User);
_uiSystem.SetUiState(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color)); _uiSystem.SetUiState(uid, CrayonUiKey.Key, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
args.Handled = true; args.Handled = true;
} }
@@ -109,35 +109,23 @@ public sealed class CrayonSystem : SharedCrayonSystem
return; return;
component.SelectedState = args.State; component.SelectedState = args.State;
Dirty(uid, component); Dirty(uid, component);
} }
private void OnCrayonBoundUIColor(EntityUid uid, CrayonComponent component, CrayonColorMessage args) private void OnCrayonBoundUIColor(EntityUid uid, CrayonComponent component, CrayonColorMessage args)
{ {
// you still need to ensure that the given color is a valid color // Ensure that the given color can be changed or already matches
if (!component.SelectableColor || args.Color == component.Color) if (!component.SelectableColor || args.Color == component.Color)
return; return;
component.Color = args.Color; component.Color = args.Color;
Dirty(uid, component); Dirty(uid, component);
}
private void OnCrayonInit(EntityUid uid, CrayonComponent component, ComponentInit args)
{
component.Charges = component.Capacity;
// Get the first one from the catalog and set it as default
var decal = _prototypeManager.EnumeratePrototypes<DecalPrototype>().FirstOrDefault(x => x.Tags.Contains("crayon"));
component.SelectedState = decal?.ID ?? string.Empty;
Dirty(uid, component);
} }
private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args) private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args)
{ {
// TODO: Use the existing event. // TODO: Use the existing event.
_uiSystem.CloseUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User); _uiSystem.CloseUi(uid, CrayonUiKey.Key, args.User);
} }
private void UseUpCrayon(EntityUid uid, EntityUid user) private void UseUpCrayon(EntityUid uid, EntityUid user)

View File

@@ -1,19 +0,0 @@
using Content.Server.Damage.Systems;
using Content.Shared.Damage;
namespace Content.Server.Damage.Components
{
[Access(typeof(DamageOtherOnHitSystem))]
[RegisterComponent]
public sealed partial class DamageOtherOnHitComponent : Component
{
[DataField("ignoreResistances")]
[ViewVariables(VVAccess.ReadWrite)]
public bool IgnoreResistances = false;
[DataField("damage", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = default!;
}
}

View File

@@ -1,35 +1,31 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Damage.Components;
using Content.Server.Weapons.Ranged.Systems; using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Events; using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Effects; using Content.Shared.Effects;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Wires;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
namespace Content.Server.Damage.Systems namespace Content.Server.Damage.Systems;
{
public sealed class DamageOtherOnHitSystem : EntitySystem public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem
{ {
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly GunSystem _guns = default!; [Dependency] private readonly GunSystem _guns = default!;
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
[Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
SubscribeLocalEvent<DamageOtherOnHitComponent, ThrowDoHitEvent>(OnDoHit); SubscribeLocalEvent<DamageOtherOnHitComponent, ThrowDoHitEvent>(OnDoHit);
SubscribeLocalEvent<DamageOtherOnHitComponent, DamageExamineEvent>(OnDamageExamine);
SubscribeLocalEvent<DamageOtherOnHitComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
} }
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args) private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
@@ -45,7 +41,7 @@ namespace Content.Server.Damage.Systems
if (dmg is { Empty: false }) if (dmg is { Empty: false })
{ {
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager)); _color.RaiseEffect(Color.Red, [args.Target], Filter.Pvs(args.Target, entityManager: EntityManager));
} }
_guns.PlayImpactSound(args.Target, dmg, null, false); _guns.PlayImpactSound(args.Target, dmg, null, false);
@@ -55,18 +51,4 @@ namespace Content.Server.Damage.Systems
_sharedCameraRecoil.KickCamera(args.Target, direction); _sharedCameraRecoil.KickCamera(args.Target, direction);
} }
} }
private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args)
{
_damageExamine.AddDamageExamine(args.Message, _damageable.ApplyUniversalAllModifiers(component.Damage * _damageable.UniversalThrownDamageModifier), Loc.GetString("damage-throw"));
}
/// <summary>
/// Prevent players with the Pacified status effect from throwing things that deal damage.
/// </summary>
private void OnAttemptPacifiedThrow(Entity<DamageOtherOnHitComponent> ent, ref AttemptPacifiedThrowEvent args)
{
args.Cancel("pacified-cannot-throw");
}
}
} }

View File

@@ -404,6 +404,9 @@ public sealed class HolopadSystem : SharedHolopadSystem
if (!this.IsPowered(entity, EntityManager)) if (!this.IsPowered(entity, EntityManager))
return; return;
if (HasComp<StationAiCoreComponent>(entity))
return;
if (!TryComp<TelephoneComponent>(entity, out var entityTelephone) || if (!TryComp<TelephoneComponent>(entity, out var entityTelephone) ||
_telephoneSystem.IsTelephoneEngaged((entity, entityTelephone))) _telephoneSystem.IsTelephoneEngaged((entity, entityTelephone)))
return; return;

View File

@@ -0,0 +1,31 @@
using Content.Shared.Emag.Systems;
namespace Content.Server.NPC.HTN.Preconditions;
/// <summary>
/// A precondition which is met if the NPC is emagged with <see cref="EmagType"/>, as computed by
/// <see cref="EmagSystem.CheckFlag"/>. This is useful for changing NPC behavior in the case that the NPC is emagged,
/// eg. like a helper NPC bot turning evil.
/// </summary>
public sealed partial class IsEmaggedPrecondition : HTNPrecondition
{
private EmagSystem _emag;
/// <summary>
/// The type of emagging to check for.
/// </summary>
[DataField]
public EmagType EmagType = EmagType.Interaction;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_emag = sysManager.GetEntitySystem<EmagSystem>();
}
public override bool IsMet(NPCBlackboard blackboard)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
return _emag.CheckFlag(owner, EmagType);
}
}

View File

@@ -0,0 +1,70 @@
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions;
using Content.Shared.CombatMode;
using Content.Shared.Weapons.Melee;
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee;
/// <summary>
/// Something between <see cref="MeleeOperator"/> and <see cref="InteractWithOperator"/>, this operator causes the NPC
/// to attempt a SINGLE <see cref="SharedMeleeWeaponSystem.AttemptLightAttack">melee attack</see> on the specified
/// <see cref="TargetKey">target</see>.
/// </summary>
public sealed partial class MeleeAttackOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedMeleeWeaponSystem _melee;
/// <summary>
/// Key that contains the target entity.
/// </summary>
[DataField(required: true)]
public string TargetKey = default!;
public override void Initialize(IEntitySystemManager sysManager)
{
base.Initialize(sysManager);
_melee = sysManager.GetEntitySystem<SharedMeleeWeaponSystem>();
}
public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
{
base.TaskShutdown(blackboard, status);
ExitCombatMode(blackboard);
}
public override void PlanShutdown(NPCBlackboard blackboard)
{
base.PlanShutdown(blackboard);
ExitCombatMode(blackboard);
}
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
if (!_entManager.TryGetComponent<CombatModeComponent>(owner, out var combatMode) ||
!_melee.TryGetWeapon(owner, out var weaponUid, out var weapon))
{
return HTNOperatorStatus.Failed;
}
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, true, combatMode);
if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) ||
!_melee.AttemptLightAttack(owner, weaponUid, weapon, target))
{
return HTNOperatorStatus.Continuing;
}
return HTNOperatorStatus.Finished;
}
private void ExitCombatMode(NPCBlackboard blackboard)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
_entManager.System<SharedCombatModeSystem>().SetInCombatMode(owner, false);
}
}

View File

@@ -1,13 +1,21 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Shared.Dataset;
using Content.Shared.Random.Helpers;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using static Content.Server.NPC.HTN.PrimitiveTasks.Operators.SpeakOperator.SpeakOperatorSpeech;
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
public sealed partial class SpeakOperator : HTNOperator public sealed partial class SpeakOperator : HTNOperator
{ {
private ChatSystem _chat = default!; private ChatSystem _chat = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[DataField(required: true)] [DataField(required: true)]
public string Speech = string.Empty; public SpeakOperatorSpeech Speech;
/// <summary> /// <summary>
/// Whether to hide message from chat window and logs. /// Whether to hide message from chat window and logs.
@@ -18,15 +26,51 @@ public sealed partial class SpeakOperator : HTNOperator
public override void Initialize(IEntitySystemManager sysManager) public override void Initialize(IEntitySystemManager sysManager)
{ {
base.Initialize(sysManager); base.Initialize(sysManager);
_chat = sysManager.GetEntitySystem<ChatSystem>(); _chat = sysManager.GetEntitySystem<ChatSystem>();
} }
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{ {
LocId speechLocId;
switch (Speech)
{
case LocalizedSetSpeakOperatorSpeech localizedDataSet:
if (!_proto.TryIndex(localizedDataSet.LineSet, out var speechSet))
return HTNOperatorStatus.Failed;
speechLocId = _random.Pick(speechSet);
break;
case SingleSpeakOperatorSpeech single:
speechLocId = single.Line;
break;
default:
throw new ArgumentOutOfRangeException(nameof(Speech));
}
var speaker = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner); var speaker = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
_chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); _chat.TrySendInGameICMessage(
speaker,
Loc.GetString(speechLocId),
InGameICChatType.Speak,
hideChat: Hidden,
hideLog: Hidden
);
return base.Update(blackboard, frameTime); return base.Update(blackboard, frameTime);
} }
[ImplicitDataDefinitionForInheritors, MeansImplicitUse]
public abstract partial class SpeakOperatorSpeech
{
public sealed partial class SingleSpeakOperatorSpeech : SpeakOperatorSpeech
{
[DataField(required: true)]
public string Line;
}
public sealed partial class LocalizedSetSpeakOperatorSpeech : SpeakOperatorSpeech
{
[DataField(required: true)]
public ProtoId<LocalizedDatasetPrototype> LineSet;
}
}
} }

View File

@@ -0,0 +1,47 @@
namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
/// <summary>
/// Raises an <see cref="HTNRaisedEvent"/> on the <see cref="NPCBlackboard.Owner">owner</see>. The event will contain
/// the specified <see cref="Args"/>, and if not null, the value of <see cref="TargetKey"/>.
/// </summary>
public sealed partial class RaiseEventForOwnerOperator : HTNOperator
{
[Dependency] private readonly IEntityManager _entMan = default!;
/// <summary>
/// The conceptual "target" of this event. Note that this is NOT the entity for which the event is raised. If null,
/// <see cref="HTNRaisedEvent.Target"/> will be null.
/// </summary>
[DataField]
public string? TargetKey;
/// <summary>
/// The data contained in the raised event. Since <see cref="HTNRaisedEvent"/> is itself pretty meaningless, this is
/// included to give some context of what the event is actually supposed to mean.
/// </summary>
[DataField(required: true)]
public EntityEventArgs Args;
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
_entMan.EventBus.RaiseLocalEvent(
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
new HTNRaisedEvent(
blackboard.GetValue<EntityUid>(NPCBlackboard.Owner),
TargetKey is { } targetKey ? blackboard.GetValue<EntityUid>(targetKey) : null,
Args
)
);
return HTNOperatorStatus.Finished;
}
}
public sealed partial class HTNRaisedEvent(EntityUid owner, EntityUid? target, EntityEventArgs args) : EntityEventArgs
{
// Owner and target are both included here in case we want to add a "RaiseEventForTargetOperator" in the future
// while reusing this event.
public EntityUid Owner = owner;
public EntityUid? Target = target;
public EntityEventArgs Args = args;
}

View File

@@ -9,4 +9,11 @@ public sealed partial class ComponentFilter : UtilityQueryFilter
/// </summary> /// </summary>
[DataField("components", required: true)] [DataField("components", required: true)]
public ComponentRegistry Components = new(); public ComponentRegistry Components = new();
/// <summary>
/// If true, this filter retains entities with ALL of the specified components. If false, this filter removes
/// entities with ANY of the specified components.
/// </summary>
[DataField]
public bool RetainWithComp = true;
} }

View File

@@ -512,13 +512,14 @@ public sealed class NPCUtilitySystem : EntitySystem
{ {
foreach (var comp in compFilter.Components) foreach (var comp in compFilter.Components)
{ {
if (HasComp(ent, comp.Value.Component.GetType())) var hasComp = HasComp(ent, comp.Value.Component.GetType());
continue; if (!compFilter.RetainWithComp == hasComp)
{
_entityList.Add(ent); _entityList.Add(ent);
break; break;
} }
} }
}
foreach (var ent in _entityList) foreach (var ent in _entityList)
{ {

View File

@@ -1,5 +1,6 @@
using Content.Server.Ninja.Systems; using Content.Server.Ninja.Systems;
using Content.Server.Objectives.Systems; using Content.Server.Objectives.Systems;
using Content.Shared.Whitelist;
namespace Content.Server.Objectives.Components; namespace Content.Server.Objectives.Components;
@@ -14,4 +15,11 @@ public sealed partial class SpiderChargeConditionComponent : Component
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public EntityUid? Target; public EntityUid? Target;
/// <summary>
/// Tags that should be used to exclude Warp Points
/// from the list of valid bombing targets
/// </summary>
[DataField]
public EntityWhitelist? Blacklist;
} }

View File

@@ -1,9 +1,9 @@
using Content.Server.Objectives.Components; using Content.Server.Objectives.Components;
using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Roles.Components; using Content.Shared.Roles.Components;
using Content.Shared.Warps; using Content.Shared.Warps;
using Content.Shared.Whitelist;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Objectives.Systems; namespace Content.Server.Objectives.Systems;
@@ -14,6 +14,7 @@ namespace Content.Server.Objectives.Systems;
/// </summary> /// </summary>
public sealed class NinjaConditionsSystem : EntitySystem public sealed class NinjaConditionsSystem : EntitySystem
{ {
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly NumberObjectiveSystem _number = default!; [Dependency] private readonly NumberObjectiveSystem _number = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
@@ -53,10 +54,13 @@ public sealed class NinjaConditionsSystem : EntitySystem
// choose spider charge detonation point // choose spider charge detonation point
var warps = new List<EntityUid>(); var warps = new List<EntityUid>();
var query = EntityQueryEnumerator<BombingTargetComponent, WarpPointComponent>(); var allEnts = EntityQueryEnumerator<WarpPointComponent>();
while (query.MoveNext(out var warpUid, out _, out var warp)) var bombingBlacklist = comp.Blacklist;
while (allEnts.MoveNext(out var warpUid, out var warp))
{ {
if (warp.Location != null) if (_whitelist.IsBlacklistFail(bombingBlacklist, warpUid)
&& !string.IsNullOrWhiteSpace(warp.Location))
{ {
warps.Add(warpUid); warps.Add(warpUid);
} }

View File

@@ -2,27 +2,22 @@ using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Instruments; using Content.Server.Instruments;
using Content.Server.Kitchen.Components; using Content.Server.Kitchen.Components;
using Content.Server.Store.Systems;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.PAI; using Content.Shared.PAI;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Store;
using Content.Shared.Store.Components;
using Content.Shared.Instruments; using Content.Shared.Instruments;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Prototypes;
using System.Text; using System.Text;
namespace Content.Server.PAI; namespace Content.Server.PAI;
public sealed class PAISystem : SharedPAISystem public sealed class PAISystem : EntitySystem
{ {
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!; [Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly ToggleableGhostRoleSystem _toggleableGhostRole = default!; [Dependency] private readonly ToggleableGhostRoleSystem _toggleableGhostRole = default!;
/// <summary> /// <summary>
@@ -38,8 +33,6 @@ public sealed class PAISystem : SharedPAISystem
SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded); SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved); SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<PAIComponent, BeingMicrowavedEvent>(OnMicrowaved); SubscribeLocalEvent<PAIComponent, BeingMicrowavedEvent>(OnMicrowaved);
SubscribeLocalEvent<PAIComponent, PAIShopActionEvent>(OnShop);
} }
private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args) private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
@@ -106,15 +99,6 @@ public sealed class PAISystem : SharedPAISystem
var val = Loc.GetString("pai-system-pai-name-raw", ("name", name.ToString())); var val = Loc.GetString("pai-system-pai-name-raw", ("name", name.ToString()));
_metaData.SetEntityName(uid, val); _metaData.SetEntityName(uid, val);
} }
private void OnShop(Entity<PAIComponent> ent, ref PAIShopActionEvent args)
{
if (!TryComp<StoreComponent>(ent, out var store))
return;
_store.ToggleUi(args.Performer, ent, store);
}
public void PAITurningOff(EntityUid uid) public void PAITurningOff(EntityUid uid)
{ {
// Close the instrument interface if it was open // Close the instrument interface if it was open

View File

@@ -171,7 +171,7 @@ namespace Content.Server.Power.EntitySystems
if (!Resolve(uid, ref battery)) if (!Resolve(uid, ref battery))
return 0; return 0;
var newValue = Math.Clamp(0, battery.CurrentCharge + value, battery.MaxCharge); var newValue = Math.Clamp(battery.CurrentCharge + value, 0, battery.MaxCharge);
var delta = newValue - battery.CurrentCharge; var delta = newValue - battery.CurrentCharge;
battery.CurrentCharge = newValue; battery.CurrentCharge = newValue;

View File

@@ -66,7 +66,7 @@ namespace Content.Server.PowerSink
if (!transform.Anchored) if (!transform.Anchored)
continue; continue;
_battery.SetCharge(entity, battery.CurrentCharge + networkLoad.NetworkLoad.ReceivingPower / 1000, battery); _battery.ChangeCharge(entity, networkLoad.NetworkLoad.ReceivingPower * frameTime, battery);
var currentBatteryThreshold = battery.CurrentCharge / battery.MaxCharge; var currentBatteryThreshold = battery.CurrentCharge / battery.MaxCharge;

View File

@@ -1,7 +1,6 @@
using System.Numerics; using System.Numerics;
using Content.Server.Actions; using Content.Server.Actions;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.Store.Components;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -21,7 +20,6 @@ using Content.Shared.Store.Components;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Revenant.EntitySystems; namespace Content.Server.Revenant.EntitySystems;
@@ -46,17 +44,12 @@ public sealed partial class RevenantSystem : EntitySystem
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly VisibilitySystem _visibility = default!; [Dependency] private readonly VisibilitySystem _visibility = default!;
[Dependency] private readonly TurfSystem _turf = default!; [Dependency] private readonly TurfSystem _turf = default!;
private static readonly EntProtoId RevenantShopId = "ActionRevenantShop";
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<RevenantComponent, ComponentStartup>(OnStartup); SubscribeLocalEvent<RevenantComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<RevenantComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RevenantComponent, RevenantShopActionEvent>(OnShop);
SubscribeLocalEvent<RevenantComponent, DamageChangedEvent>(OnDamage); SubscribeLocalEvent<RevenantComponent, DamageChangedEvent>(OnDamage);
SubscribeLocalEvent<RevenantComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<RevenantComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<RevenantComponent, StatusEffectAddedEvent>(OnStatusAdded); SubscribeLocalEvent<RevenantComponent, StatusEffectAddedEvent>(OnStatusAdded);
@@ -94,11 +87,6 @@ public sealed partial class RevenantSystem : EntitySystem
_eye.RefreshVisibilityMask(uid); _eye.RefreshVisibilityMask(uid);
} }
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
{
_action.AddAction(uid, ref component.Action, RevenantShopId);
}
private void OnStatusAdded(EntityUid uid, RevenantComponent component, StatusEffectAddedEvent args) private void OnStatusAdded(EntityUid uid, RevenantComponent component, StatusEffectAddedEvent args)
{ {
if (args.Key == "Stun") if (args.Key == "Stun")
@@ -182,13 +170,6 @@ public sealed partial class RevenantSystem : EntitySystem
return true; return true;
} }
private void OnShop(EntityUid uid, RevenantComponent component, RevenantShopActionEvent args)
{
if (!TryComp<StoreComponent>(uid, out var store))
return;
_store.ToggleUi(uid, uid, store);
}
public void MakeVisible(bool visible) public void MakeVisible(bool visible)
{ {
var query = EntityQueryEnumerator<RevenantComponent, VisibilityComponent>(); var query = EntityQueryEnumerator<RevenantComponent, VisibilityComponent>();

View File

@@ -0,0 +1,65 @@
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
using Content.Shared.Silicons.Bots;
using Robust.Shared.Timing;
namespace Content.Server.Silicons.Bots;
/// <summary>
/// Beyond what <see cref="SharedHugBotSystem"/> does, this system manages the "lifecycle" of
/// <see cref="RecentlyHuggedByHugBotComponent"/>.
/// </summary>
public sealed class HugBotSystem : SharedHugBotSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HugBotComponent, HTNRaisedEvent>(OnHtnRaisedEvent);
}
private void OnHtnRaisedEvent(Entity<HugBotComponent> entity, ref HTNRaisedEvent args)
{
if (args.Args is not HugBotDidHugEvent ||
args.Target is not {} target)
return;
var ev = new HugBotHugEvent(GetNetEntity(entity));
RaiseLocalEvent(target, ev);
ApplyHugBotCooldown(entity, target);
}
/// <summary>
/// Applies <see cref="RecentlyHuggedByHugBotComponent"/> to <paramref name="target"/> based on the configuration of
/// <paramref name="hugBot"/>.
/// </summary>
public void ApplyHugBotCooldown(Entity<HugBotComponent> hugBot, EntityUid target)
{
var hugged = EnsureComp<RecentlyHuggedByHugBotComponent>(target);
hugged.CooldownCompleteAfter = _gameTiming.CurTime + hugBot.Comp.HugCooldown;
}
public override void Update(float frameTime)
{
// Iterate through all RecentlyHuggedByHugBot entities...
var huggedEntities = AllEntityQuery<RecentlyHuggedByHugBotComponent>();
while (huggedEntities.MoveNext(out var huggedEnt, out var huggedComp))
{
// ... and if their cooldown is complete...
if (huggedComp.CooldownCompleteAfter <= _gameTiming.CurTime)
{
// ... remove it, allowing them to receive the blessing of hugs once more.
RemCompDeferred<RecentlyHuggedByHugBotComponent>(huggedEnt);
}
}
}
}
/// <summary>
/// This event is indirectly raised (by being <see cref="HTNRaisedEvent.Args"/>) on a HugBot when it hugs (or emaggedly
/// punches) an entity.
/// </summary>
[Serializable, DataDefinition]
public sealed partial class HugBotDidHugEvent : EntityEventArgs;

View File

@@ -0,0 +1,15 @@
using Content.Shared.Silicons.Bots;
namespace Content.Server.Silicons.Bots;
/// <summary>
/// This marker component indicates that its entity has been recently hugged by a HugBot and should not be hugged again
/// before <see cref="CooldownCompleteAfter">a cooldown period</see> in order to prevent hug spam.
/// </summary>
/// <see cref="SharedHugBotSystem"/>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class RecentlyHuggedByHugBotComponent : Component
{
[DataField, AutoPausedField]
public TimeSpan CooldownCompleteAfter = TimeSpan.MinValue;
}

View File

@@ -176,9 +176,10 @@ public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSyste
foreach (var result in rayCastResults) foreach (var result in rayCastResults)
{ {
if (genQuery.HasComponent(result.HitEntity)) if (!genQuery.HasComponent(result.HitEntity))
closestResult = result; continue;
closestResult = result;
break; break;
} }

View File

@@ -1,17 +1,16 @@
using System.Linq;
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Shared.UserInterface;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Implants.Components; using Content.Shared.Implants.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Store.Components; using Content.Shared.Store.Components;
using JetBrains.Annotations; using Content.Shared.Store.Events;
using Content.Shared.UserInterface;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared.Mind; using Robust.Shared.Utility;
namespace Content.Server.Store.Systems; namespace Content.Server.Store.Systems;
@@ -37,6 +36,7 @@ public sealed partial class StoreSystem : EntitySystem
SubscribeLocalEvent<StoreComponent, ComponentStartup>(OnStartup); SubscribeLocalEvent<StoreComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<StoreComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<StoreComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<StoreComponent, OpenUplinkImplantEvent>(OnImplantActivate); SubscribeLocalEvent<StoreComponent, OpenUplinkImplantEvent>(OnImplantActivate);
SubscribeLocalEvent<StoreComponent, IntrinsicStoreActionEvent>(OnIntrinsicStoreAction);
InitializeUi(); InitializeUi();
InitializeCommand(); InitializeCommand();
@@ -187,6 +187,12 @@ public sealed partial class StoreSystem : EntitySystem
UpdateUserInterface(null, uid, store); UpdateUserInterface(null, uid, store);
return true; return true;
} }
private void OnIntrinsicStoreAction(Entity<StoreComponent> ent, ref IntrinsicStoreActionEvent args)
{
ToggleUi(args.Performer, ent.Owner, ent.Comp);
}
} }
public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs

View File

@@ -1,7 +1,5 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Movement.Systems; using Content.Server.Movement.Systems;
using Content.Shared.Damage.Events;
using Content.Shared.Damage.Systems;
using Content.Shared.Effects; using Content.Shared.Effects;
using Content.Shared.Speech.Components; using Content.Shared.Speech.Components;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
@@ -16,28 +14,14 @@ namespace Content.Server.Weapons.Melee;
public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
{ {
[Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
[Dependency] private readonly LagCompensationSystem _lag = default!; [Dependency] private readonly LagCompensationSystem _lag = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<MeleeSpeechComponent, MeleeHitEvent>(OnSpeechHit); SubscribeLocalEvent<MeleeSpeechComponent, MeleeHitEvent>(OnSpeechHit);
SubscribeLocalEvent<MeleeWeaponComponent, DamageExamineEvent>(OnMeleeExamineDamage);
}
private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component, ref DamageExamineEvent args)
{
if (component.Hidden)
return;
var damageSpec = GetDamage(uid, args.User, component);
if (damageSpec.Empty)
return;
_damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), Loc.GetString("damage-melee"));
} }
protected override bool ArcRaySuccessful(EntityUid targetUid, protected override bool ArcRaySuccessful(EntityUid targetUid,

View File

@@ -1,12 +1,7 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Events;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.Prototypes;
namespace Content.Server.Weapons.Ranged.Systems; namespace Content.Server.Weapons.Ranged.Systems;
@@ -19,13 +14,11 @@ public sealed partial class GunSystem
// Hitscan // Hitscan
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ComponentStartup>(OnBatteryStartup); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ComponentStartup>(OnBatteryStartup);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ChargeChangedEvent>(OnBatteryChargeChange); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ChargeChangedEvent>(OnBatteryChargeChange);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged);
// Projectile // Projectile
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentStartup>(OnBatteryStartup); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentStartup>(OnBatteryStartup);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ChargeChangedEvent>(OnBatteryChargeChange); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ChargeChangedEvent>(OnBatteryChargeChange);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged);
} }
@@ -73,50 +66,6 @@ public sealed partial class GunSystem
RaiseLocalEvent(uid, ref updateAmmoEv); RaiseLocalEvent(uid, ref updateAmmoEv);
} }
private void OnBatteryDamageExamine<T>(Entity<T> entity, ref DamageExamineEvent args) where T : BatteryAmmoProviderComponent
{
var damageSpec = GetDamage(entity.Comp);
if (damageSpec == null)
return;
var damageType = entity.Comp switch
{
HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"),
ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"),
_ => throw new ArgumentOutOfRangeException(),
};
_damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), damageType);
}
private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component)
{
if (component is ProjectileBatteryAmmoProviderComponent battery)
{
if (ProtoManager.Index<EntityPrototype>(battery.Prototype).Components
.TryGetValue(Factory.GetComponentName<ProjectileComponent>(), out var projectile))
{
var p = (ProjectileComponent) projectile.Component;
if (!p.Damage.Empty)
{
return p.Damage * Damageable.UniversalProjectileDamageModifier;
}
}
return null;
}
if (component is HitscanBatteryAmmoProviderComponent hitscan)
{
var dmg = ProtoManager.Index<HitscanPrototype>(hitscan.Prototype).Damage;
return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier;
}
return null;
}
protected override void TakeCharge(Entity<BatteryAmmoProviderComponent> entity) protected override void TakeCharge(Entity<BatteryAmmoProviderComponent> entity)
{ {
var ev = new ChangeChargeEvent(-entity.Comp.FireCost); var ev = new ChangeChargeEvent(-entity.Comp.FireCost);

View File

@@ -1,87 +1,25 @@
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack; using Content.Server.Stack;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Power;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Content.Shared.Xenoarchaeology.Equipment; using Content.Shared.Xenoarchaeology.Equipment;
using Content.Shared.Xenoarchaeology.Equipment.Components; using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Xenoarchaeology.Equipment.Systems; namespace Content.Server.Xenoarchaeology.Equipment.Systems;
/// <inheritdoc/> /// <inheritdoc/>
public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
{ {
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BodySystem _body = default!; [Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
/// <inheritdoc/> // TODO: Move to shared once StackSystem spawning is in Shared and we have RandomPredicted
public override void Initialize() public override void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
{
base.Initialize();
SubscribeLocalEvent<ArtifactCrusherComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
SubscribeLocalEvent<ArtifactCrusherComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnGetVerbs(Entity<ArtifactCrusherComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing)
return;
if (!TryComp<EntityStorageComponent>(ent, out var entityStorageComp) ||
entityStorageComp.Contents.ContainedEntities.Count == 0)
return;
if (!this.IsPowered(ent, EntityManager))
return;
var verb = new AlternativeVerb
{
Text = Loc.GetString("artifact-crusher-verb-start-crushing"),
Priority = 2,
Act = () => StartCrushing((ent, ent.Comp, entityStorageComp))
};
args.Verbs.Add(verb);
}
private void OnPowerChanged(Entity<ArtifactCrusherComponent> ent, ref PowerChangedEvent args)
{
if (!args.Powered)
StopCrushing(ent);
}
public void StartCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
{
var (uid, crusher, _) = ent;
if (crusher.Crushing)
return;
if (crusher.AutoLock)
_popup.PopupEntity(Loc.GetString("artifact-crusher-autolocks-enable"), uid);
crusher.Crushing = true;
crusher.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
crusher.CrushEndTime = _timing.CurTime + crusher.CrushDuration;
crusher.CrushingSoundEntity = AudioSystem.PlayPvs(crusher.CrushingSound, ent);
Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, true);
Dirty(ent, ent.Comp1);
}
public void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
{ {
var (_, crusher, storage) = ent; var (_, crusher, storage) = ent;
StopCrushing((ent, ent.Comp1), false); StopCrushing((ent, ent.Comp1), false);
@@ -113,32 +51,4 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
} }
} }
} }
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ArtifactCrusherComponent, EntityStorageComponent>();
while (query.MoveNext(out var uid, out var crusher, out var storage))
{
if (!crusher.Crushing)
continue;
if (crusher.NextSecond < _timing.CurTime)
{
var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
foreach (var contained in contents)
{
_damageable.TryChangeDamage(contained, crusher.CrushingDamage);
}
crusher.NextSecond += TimeSpan.FromSeconds(1);
Dirty(uid, crusher);
}
if (crusher.CrushEndTime < _timing.CurTime)
{
FinishCrushing((uid, crusher, storage));
}
}
}
} }

View File

@@ -14,30 +14,44 @@ public sealed partial class AutoEmotePrototype : IPrototype
/// The ID of the emote prototype. /// The ID of the emote prototype.
/// </summary> /// </summary>
[DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))] [DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
public string EmoteId = String.Empty; public string EmoteId = string.Empty;
/// <summary> /// <summary>
/// How often an attempt at the emote will be made. /// How often an attempt at the emote will be made.
/// </summary> /// </summary>
[DataField("interval", required: true)] [DataField(required: true)]
public TimeSpan Interval; public TimeSpan Interval;
/// <summary> /// <summary>
/// Probability of performing the emote each interval. /// Probability of performing the emote each interval.
/// <summary> /// </summary>
[DataField("chance")] [DataField("chance")]
public float Chance = 1; public float Chance = 1;
/// <summary> /// <summary>
/// Also send the emote in chat. /// Also send the emote in chat.
/// <summary> /// </summary>
[DataField("withChat")] [DataField]
public bool WithChat = true; public bool WithChat = true;
/// <summary>
/// Should we ignore action blockers?
/// This does nothing if WithChat is false.
/// </summary>
[DataField]
public bool IgnoreActionBlocker;
/// <summary>
/// Should we ignore whitelists and force the emote?
/// This does nothing if WithChat is false.
/// </summary>
[DataField]
public bool Force;
/// <summary> /// <summary>
/// Hide the chat message from the chat window, only showing the popup. /// Hide the chat message from the chat window, only showing the popup.
/// This does nothing if WithChat is false. /// This does nothing if WithChat is false.
/// <summary> /// </summary>
[DataField("hiddenFromChatWindow")] [DataField]
public bool HiddenFromChatWindow = false; public bool HiddenFromChatWindow;
} }

View File

@@ -62,27 +62,7 @@ public sealed class MaskSystem : EntitySystem
private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args) private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args)
{ {
if (!mask.IsToggled || !mask.IsToggleable) SetToggled(uid, false);
return;
mask.IsToggled = false;
ToggleMaskComponents(uid, mask, args.Equipee, mask.EquippedPrefix, true);
}
/// <summary>
/// Called after setting IsToggled, raises events and dirties.
/// </summary>
private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, string? equippedPrefix = null, bool isEquip = false)
{
Dirty(uid, mask);
if (mask.ToggleActionEntity is { } action)
_actionSystem.SetToggled(action, mask.IsToggled);
var maskEv = new ItemMaskToggledEvent((uid, mask), wearer);
RaiseLocalEvent(uid, ref maskEv);
var wearerEv = new WearerMaskToggledEvent((uid, mask));
RaiseLocalEvent(wearer, ref wearerEv);
} }
private void OnFolded(Entity<MaskComponent> ent, ref FoldedEvent args) private void OnFolded(Entity<MaskComponent> ent, ref FoldedEvent args)

View File

@@ -1,6 +1,9 @@
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Content.Shared.Damage;
using Content.Shared.Roles;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Cluwne; namespace Content.Shared.Cluwne;
@@ -12,22 +15,74 @@ public sealed partial class CluwneComponent : Component
/// <summary> /// <summary>
/// timings for giggles and knocks. /// timings for giggles and knocks.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public TimeSpan DamageGiggleCooldown = TimeSpan.FromSeconds(2); public TimeSpan DamageGiggleCooldown = TimeSpan.FromSeconds(2);
[ViewVariables(VVAccess.ReadWrite)] /// <summary>
/// Amount of genetic damage dealt when they revert
/// </summary>
[DataField]
public DamageSpecifier RevertDamage = new()
{
DamageDict = new()
{
{ "Genetic", 300.0 },
},
};
/// <summary>
/// Chance that the Cluwne will be knocked over and paralyzed.
/// </summary>
[DataField]
public float KnockChance = 0.05f; public float KnockChance = 0.05f;
[ViewVariables(VVAccess.ReadWrite)] /// <summary>
/// Chance that the Cluwne will randomly giggle
/// </summary>
[DataField]
public float GiggleRandomChance = 0.1f; public float GiggleRandomChance = 0.1f;
[DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))] /// <summary>
public string? EmoteSoundsId = "Cluwne"; /// Enable random emoting?
/// </summary>
[DataField]
public bool RandomEmote = true;
/// <summary>
/// Emote sound collection that the Cluwne should use.
/// </summary>
[DataField("emoteId")]
public ProtoId<EmoteSoundsPrototype>? EmoteSoundsId = "Cluwne";
/// <summary>
/// Emote to use for the Cluwne Giggling
/// </summary>
[DataField]
public ProtoId<AutoEmotePrototype>? AutoEmoteId = "CluwneGiggle";
/// <summary>
/// Message to popup when the Cluwne is transformed
/// </summary>
[DataField]
public LocId TransformMessage = "cluwne-transform";
/// <summary>
/// Name prefix for the Cluwne.
/// Example "Urist McHuman" will be "Cluwned Urist McHuman"
/// </summary>
[DataField]
public LocId NamePrefix = "cluwne-name-prefix";
/// <summary>
/// Outfit ID that the cluwne will spawn with.
/// </summary>
[DataField]
public ProtoId<StartingGearPrototype> OutfitId = "CluwneGear";
/// <summary> /// <summary>
/// Amount of time cluwne is paralyzed for when falling over. /// Amount of time cluwne is paralyzed for when falling over.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public float ParalyzeTime = 2f; public float ParalyzeTime = 2f;
/// <summary> /// <summary>
@@ -36,6 +91,21 @@ public sealed partial class CluwneComponent : Component
[DataField("spawnsound")] [DataField("spawnsound")]
public SoundSpecifier SpawnSound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg"); public SoundSpecifier SpawnSound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg");
[DataField("knocksound")] /// <summary>
/// Emote to use for the cluwne giggling
/// </summary>
[DataField]
public LocId GiggleEmote = "cluwne-giggle-emote";
/// <summary>
/// Sound to play when the Cluwne is knocked over and paralyzed
/// </summary>
[DataField]
public SoundSpecifier KnockSound = new SoundPathSpecifier("/Audio/Items/airhorn.ogg"); public SoundSpecifier KnockSound = new SoundPathSpecifier("/Audio/Items/airhorn.ogg");
/// <summary>
/// Emote thats used when the cluwne getting knocked over
/// </summary>
[DataField]
public LocId KnockEmote = "cluwne-knock-emote";
} }

View File

@@ -0,0 +1,119 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Crayon;
/// <summary>
/// Component holding the state of a crayon-like component
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedCrayonSystem))]
public sealed partial class CrayonComponent : Component
{
/// <summary>
/// The ID of currently selected decal prototype that will be placed when the crayon is used.
/// </summary>
[DataField, AutoNetworkedField]
public string SelectedState;
/// <summary>
/// Color with which the crayon will draw.
/// </summary>
[DataField, AutoNetworkedField]
public Color Color;
/// <summary>
/// Play a sound when drawing if specified.
/// </summary>
[DataField]
public SoundSpecifier? UseSound;
/// <summary>
/// Can the color can be changed?
/// </summary>
[DataField, AutoNetworkedField]
public bool SelectableColor;
/// <summary>
/// Should the crayon be deleted when all charges are consumed?
/// </summary>
[DataField, AutoNetworkedField]
public bool DeleteEmpty = true;
}
/// <summary>
/// Opens the crayon window for decal and color selection.
/// </summary>
[Serializable, NetSerializable]
public enum CrayonUiKey : byte
{
Key,
}
/// <summary>
/// Used by the client to notify the server about the selected decal ID
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
{
public readonly string State;
public CrayonSelectMessage(string selected)
{
State = selected;
}
}
/// <summary>
/// Sets the color of the crayon, used by Rainbow Crayon
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonColorMessage : BoundUserInterfaceMessage
{
public readonly Color Color;
public CrayonColorMessage(Color color)
{
Color = color;
}
}
/// <summary>
/// Server to CLIENT. Notifies the BUI that a decal with given ID has been drawn.
/// Allows the client UI to advance forward in the client-only ephemeral queue,
/// preventing the crayon from becoming a magic text storage device.
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonUsedMessage : BoundUserInterfaceMessage
{
public readonly string DrawnDecal;
public CrayonUsedMessage(string drawn)
{
DrawnDecal = drawn;
}
}
/// <summary>
/// The state of the crayon UI as sent by the server
/// </summary>
/// <summary>
/// TODO: Delete this and use component states and predict the UI.
/// This info is already networked on its own.
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonBoundUserInterfaceState : BoundUserInterfaceState
{
public string Selected;
/// <summary>
/// Can the color can be changed
/// </summary>
public bool SelectableColor;
public Color Color;
public CrayonBoundUserInterfaceState(string selected, bool selectableColor, Color color)
{
Selected = selected;
SelectableColor = selectableColor;
Color = color;
}
}

View File

@@ -1,113 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Crayon
{
/// <summary>
/// Component holding the state of a crayon-like component
/// </summary>
[NetworkedComponent, ComponentProtoName("Crayon"), Access(typeof(SharedCrayonSystem))]
public abstract partial class SharedCrayonComponent : Component
{
/// <summary>
/// The ID of currently selected decal prototype that will be placed when the crayon is used
/// </summary>
public string SelectedState { get; set; } = string.Empty;
/// <summary>
/// Color with which the crayon will draw
/// </summary>
[DataField("color")]
public Color Color;
[Serializable, NetSerializable]
public enum CrayonUiKey : byte
{
Key,
}
}
/// <summary>
/// Used by the client to notify the server about the selected decal ID
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonSelectMessage : BoundUserInterfaceMessage
{
public readonly string State;
public CrayonSelectMessage(string selected)
{
State = selected;
}
}
/// <summary>
/// Sets the color of the crayon, used by Rainbow Crayon
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonColorMessage : BoundUserInterfaceMessage
{
public readonly Color Color;
public CrayonColorMessage(Color color)
{
Color = color;
}
}
/// <summary>
/// Server to CLIENT. Notifies the BUI that a decal with given ID has been drawn.
/// Allows the client UI to advance forward in the client-only ephemeral queue,
/// preventing the crayon from becoming a magic text storage device.
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonUsedMessage : BoundUserInterfaceMessage
{
public readonly string DrawnDecal;
public CrayonUsedMessage(string drawn)
{
DrawnDecal = drawn;
}
}
/// <summary>
/// Component state, describes how many charges are left in the crayon in the near-hand UI
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonComponentState : ComponentState
{
public readonly Color Color;
public readonly string State;
public readonly int Charges;
public readonly int Capacity;
public CrayonComponentState(Color color, string state, int charges, int capacity)
{
Color = color;
State = state;
Charges = charges;
Capacity = capacity;
}
}
/// <summary>
/// The state of the crayon UI as sent by the server
/// </summary>
[Serializable, NetSerializable]
public sealed class CrayonBoundUserInterfaceState : BoundUserInterfaceState
{
public string Selected;
/// <summary>
/// Whether or not the color can be selected
/// </summary>
public bool SelectableColor;
public Color Color;
public CrayonBoundUserInterfaceState(string selected, bool selectableColor, Color color)
{
Selected = selected;
SelectableColor = selectableColor;
Color = color;
}
}
}

View File

@@ -2,7 +2,8 @@ using Robust.Shared.GameStates;
namespace Content.Shared.Damage.Components; namespace Content.Shared.Damage.Components;
/// <summary>
/// Shows a detailed examine window with this entity's damage stats when examined.
/// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class DamageExaminableComponent : Component public sealed partial class DamageExaminableComponent : Component;
{
}

View File

@@ -0,0 +1,24 @@
using Content.Shared.Damage.Systems;
namespace Content.Shared.Damage.Components;
/// <summary>
/// Makes this entity deal damage when thrown at something.
/// </summary>
[RegisterComponent]
[Access(typeof(SharedDamageOtherOnHitSystem))]
public sealed partial class DamageOtherOnHitComponent : Component
{
/// <summary>
/// Whether to ignore damage modifiers.
/// </summary>
[DataField]
public bool IgnoreResistances = false;
/// <summary>
/// The damage amount to deal on hit.
/// </summary>
[DataField(required: true)]
public DamageSpecifier Damage = default!;
}

View File

@@ -1,6 +1,10 @@
using Content.Shared.Damage.Components;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Shared.Damage.Events; namespace Content.Shared.Damage.Events;
/// <summary>
/// Raised on an entity with <see cref="DamageExaminableComponent"/> when examined to get the damage values displayed in the examine window.
/// </summary>
[ByRefEvent] [ByRefEvent]
public readonly record struct DamageExamineEvent(FormattedMessage Message, EntityUid User); public readonly record struct DamageExamineEvent(FormattedMessage Message, EntityUid User);

View File

@@ -0,0 +1,32 @@
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Events;
namespace Content.Shared.Damage.Systems;
public abstract class SharedDamageOtherOnHitSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DamageOtherOnHitComponent, DamageExamineEvent>(OnDamageExamine);
SubscribeLocalEvent<DamageOtherOnHitComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
}
private void OnDamageExamine(Entity<DamageOtherOnHitComponent> ent, ref DamageExamineEvent args)
{
_damageExamine.AddDamageExamine(args.Message, _damageable.ApplyUniversalAllModifiers(ent.Comp.Damage * _damageable.UniversalThrownDamageModifier), Loc.GetString("damage-throw"));
}
/// <summary>
/// Prevent players with the Pacified status effect from throwing things that deal damage.
/// </summary>
private void OnAttemptPacifiedThrow(Entity<DamageOtherOnHitComponent> ent, ref AttemptPacifiedThrowEvent args)
{
args.Cancel("pacified-cannot-throw");
}
}

View File

@@ -1,4 +1,4 @@
namespace Content.Shared.Destructible; namespace Content.Shared.Destructible;
public abstract class SharedDestructibleSystem : EntitySystem public abstract class SharedDestructibleSystem : EntitySystem
{ {
@@ -15,7 +15,7 @@ public abstract class SharedDestructibleSystem : EntitySystem
var eventArgs = new DestructionEventArgs(); var eventArgs = new DestructionEventArgs();
RaiseLocalEvent(owner, eventArgs); RaiseLocalEvent(owner, eventArgs);
QueueDel(owner); PredictedQueueDel(owner);
return true; return true;
} }

View File

@@ -214,6 +214,17 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
return links; return links;
} }
/// <summary>
/// Gets the entities linked to a specific source port.
/// </summary>
public HashSet<EntityUid> GetLinkedSinks(Entity<DeviceLinkSourceComponent?> source, ProtoId<SourcePortPrototype> port)
{
if (!Resolve(source, ref source.Comp) || !source.Comp.Outputs.TryGetValue(port, out var linked))
return new HashSet<EntityUid>(); // not a source or not linked
return new HashSet<EntityUid>(linked); // clone to prevent modifying the original
}
/// <summary> /// <summary>
/// Returns the default links for the given list of source port prototypes /// Returns the default links for the given list of source port prototypes
/// </summary> /// </summary>

View File

@@ -15,4 +15,10 @@ public sealed partial class FlashImmunityComponent : Component
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public bool Enabled = true; public bool Enabled = true;
/// <summary>
/// Should the flash protection be shown when examining the entity?
/// </summary>
[DataField, AutoNetworkedField]
public bool ShowInExamine = true;
} }

View File

@@ -268,6 +268,7 @@ public abstract class SharedFlashSystem : EntitySystem
private void OnExamine(Entity<FlashImmunityComponent> ent, ref ExaminedEvent args) private void OnExamine(Entity<FlashImmunityComponent> ent, ref ExaminedEvent args)
{ {
if (ent.Comp.ShowInExamine)
args.PushMarkup(Loc.GetString("flash-protection")); args.PushMarkup(Loc.GetString("flash-protection"));
} }
} }

View File

@@ -1,7 +0,0 @@
namespace Content.Shared.Ninja.Components;
/// <summary>
/// Makes this warp point a valid bombing target for ninja's spider charge.
/// </summary>
[RegisterComponent]
public sealed partial class BombingTargetComponent : Component;

View File

@@ -1,8 +1,4 @@
using Content.Shared.FixedPoint;
using Content.Shared.Store;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.PAI; namespace Content.Shared.PAI;
@@ -16,7 +12,7 @@ namespace Content.Shared.PAI;
/// and there's not always enough players and ghost roles to justify it. /// and there's not always enough players and ghost roles to justify it.
/// All logic in PAISystem. /// All logic in PAISystem.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent]
public sealed partial class PAIComponent : Component public sealed partial class PAIComponent : Component
{ {
/// <summary> /// <summary>
@@ -26,12 +22,6 @@ public sealed partial class PAIComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public EntityUid? LastUser; public EntityUid? LastUser;
[DataField]
public EntProtoId ShopActionId = "ActionPAIOpenShop";
[DataField, AutoNetworkedField]
public EntityUid? ShopAction;
/// <summary> /// <summary>
/// When microwaved there is this chance to brick the pai, kicking out its player and preventing it from being used again. /// When microwaved there is this chance to brick the pai, kicking out its player and preventing it from being used again.
/// </summary> /// </summary>

View File

@@ -1,38 +0,0 @@
using Content.Shared.Actions;
namespace Content.Shared.PAI;
/// <summary>
/// pAIs, or Personal AIs, are essentially portable ghost role generators.
/// In their current implementation, they create a ghost role anyone can access,
/// and that a player can also "wipe" (reset/kick out player).
/// Theoretically speaking pAIs are supposed to use a dedicated "offer and select" system,
/// with the player holding the pAI being able to choose one of the ghosts in the round.
/// This seems too complicated for an initial implementation, though,
/// and there's not always enough players and ghost roles to justify it.
/// </summary>
public abstract class SharedPAISystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PAIComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PAIComponent, ComponentShutdown>(OnShutdown);
}
private void OnMapInit(Entity<PAIComponent> ent, ref MapInitEvent args)
{
_actions.AddAction(ent, ent.Comp.ShopActionId);
}
private void OnShutdown(Entity<PAIComponent> ent, ref ComponentShutdown args)
{
_actions.RemoveAction(ent.Owner, ent.Comp.ShopAction);
}
}
public sealed partial class PAIShopActionEvent : InstantActionEvent
{
}

View File

@@ -38,7 +38,10 @@ public sealed partial class ParcelWrappingSystem
private void OnGetVerbsForWrappedParcel(Entity<WrappedParcelComponent> entity, private void OnGetVerbsForWrappedParcel(Entity<WrappedParcelComponent> entity,
ref GetVerbsEvent<InteractionVerb> args) ref GetVerbsEvent<InteractionVerb> args)
{ {
if (!args.CanAccess) if (!args.CanAccess || !args.CanComplexInteract)
return;
if (entity.Comp.Contents.Contains(args.User))
return; return;
// "Capture" the values from `args` because C# doesn't like doing the capturing for `ref` values. // "Capture" the values from `args` because C# doesn't like doing the capturing for `ref` values.

View File

@@ -10,21 +10,30 @@ namespace Content.Shared.Physics;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class JointVisualsComponent : Component public sealed partial class JointVisualsComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite), DataField("sprite", required: true), AutoNetworkedField] /// <summary>
/// The sprite to use for the line.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public SpriteSpecifier Sprite = default!; public SpriteSpecifier Sprite = default!;
[ViewVariables(VVAccess.ReadWrite), DataField("target"), AutoNetworkedField] /// <summary>
public NetEntity? Target; /// The line is drawn between this target and the entity owning the component.
/// </summary>
/// <summary>
/// TODO: WeakEntityReference.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Target;
/// <summary> /// <summary>
/// Offset from Body A. /// Offset from Body A.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("offsetA"), AutoNetworkedField] [DataField, AutoNetworkedField]
public Vector2 OffsetA; public Vector2 OffsetA;
/// <summary> /// <summary>
/// Offset from Body B. /// Offset from Body B.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("offsetB"), AutoNetworkedField] [DataField, AutoNetworkedField]
public Vector2 OffsetB; public Vector2 OffsetB;
} }

View File

@@ -214,6 +214,4 @@ public sealed partial class RevenantComponent : Component
[DataField("harvestingState")] [DataField("harvestingState")]
public string HarvestingState = "harvesting"; public string HarvestingState = "harvesting";
#endregion #endregion
[DataField] public EntityUid? Action;
} }

View File

@@ -42,10 +42,6 @@ public sealed class HarvestDoAfterCancelled : EntityEventArgs
{ {
} }
public sealed partial class RevenantShopActionEvent : InstantActionEvent
{
}
public sealed partial class RevenantDefileActionEvent : InstantActionEvent public sealed partial class RevenantDefileActionEvent : InstantActionEvent
{ {
} }

View File

@@ -17,19 +17,19 @@ namespace Content.Shared.Roles
[IdDataField] [IdDataField]
public string ID { get; private set; } = default!; public string ID { get; private set; } = default!;
[DataField("playTimeTracker", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<PlayTimeTrackerPrototype>))] [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<PlayTimeTrackerPrototype>))]
public string PlayTimeTracker { get; private set; } = string.Empty; public string PlayTimeTracker { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Who is the supervisor for this job. /// Who is the supervisor for this job.
/// </summary> /// </summary>
[DataField("supervisors")] [DataField]
public string Supervisors { get; private set; } = "nobody"; public LocId Supervisors = "job-supervisors-nobody";
/// <summary> /// <summary>
/// The name of this job as displayed to players. /// The name of this job as displayed to players.
/// </summary> /// </summary>
[DataField("name")] [DataField]
public string Name { get; private set; } = string.Empty; public string Name { get; private set; } = string.Empty;
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
@@ -38,7 +38,7 @@ namespace Content.Shared.Roles
/// <summary> /// <summary>
/// The name of this job as displayed to players. /// The name of this job as displayed to players.
/// </summary> /// </summary>
[DataField("description")] [DataField]
public string? Description { get; private set; } public string? Description { get; private set; }
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
@@ -53,19 +53,19 @@ namespace Content.Shared.Roles
/// <summary> /// <summary>
/// When true - the station will have anouncement about arrival of this player. /// When true - the station will have anouncement about arrival of this player.
/// </summary> /// </summary>
[DataField("joinNotifyCrew")] [DataField]
public bool JoinNotifyCrew { get; private set; } = false; public bool JoinNotifyCrew { get; private set; } = false;
/// <summary> /// <summary>
/// When true - the player will recieve a message about importancy of their job. /// When true - the player will recieve a message about importancy of their job.
/// </summary> /// </summary>
[DataField("requireAdminNotify")] [DataField]
public bool RequireAdminNotify { get; private set; } = false; public bool RequireAdminNotify { get; private set; } = false;
/// <summary> /// <summary>
/// Should this job appear in preferences menu? /// Should this job appear in preferences menu?
/// </summary> /// </summary>
[DataField("setPreference")] [DataField]
public bool SetPreference { get; private set; } = true; public bool SetPreference { get; private set; } = true;
/// <summary> /// <summary>
@@ -81,14 +81,14 @@ namespace Content.Shared.Roles
[DataField] [DataField]
public bool? OverrideConsoleVisibility { get; private set; } = null; public bool? OverrideConsoleVisibility { get; private set; } = null;
[DataField("canBeAntag")] [DataField]
public bool CanBeAntag { get; private set; } = true; public bool CanBeAntag { get; private set; } = true;
/// <summary> /// <summary>
/// The "weight" or importance of this job. If this number is large, the job system will assign this job /// The "weight" or importance of this job. If this number is large, the job system will assign this job
/// before assigning other jobs. /// before assigning other jobs.
/// </summary> /// </summary>
[DataField("weight")] [DataField]
public int Weight { get; private set; } public int Weight { get; private set; }
/// <summary> /// <summary>
@@ -105,7 +105,7 @@ namespace Content.Shared.Roles
/// A numerical score for how much easier this job is for antagonists. /// A numerical score for how much easier this job is for antagonists.
/// For traitors, reduces starting TC by this amount. Other gamemodes can use it for whatever they find fitting. /// For traitors, reduces starting TC by this amount. Other gamemodes can use it for whatever they find fitting.
/// </summary> /// </summary>
[DataField("antagAdvantage")] [DataField]
public int AntagAdvantage = 0; public int AntagAdvantage = 0;
[DataField] [DataField]
@@ -116,7 +116,7 @@ namespace Content.Shared.Roles
/// Starting gear will be ignored. /// Starting gear will be ignored.
/// If you want to just add special attributes to a humanoid, use AddComponentSpecial instead. /// If you want to just add special attributes to a humanoid, use AddComponentSpecial instead.
/// </summary> /// </summary>
[DataField("jobEntity", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? JobEntity = null; public string? JobEntity = null;
/// <summary> /// <summary>
@@ -129,19 +129,19 @@ namespace Content.Shared.Roles
[DataField] [DataField]
public ProtoId<JobIconPrototype> Icon { get; private set; } = "JobIconUnknown"; public ProtoId<JobIconPrototype> Icon { get; private set; } = "JobIconUnknown";
[DataField("special", serverOnly: true)] [DataField(serverOnly: true)]
public JobSpecial[] Special { get; private set; } = Array.Empty<JobSpecial>(); public JobSpecial[] Special { get; private set; } = Array.Empty<JobSpecial>();
[DataField("access")] [DataField]
public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> Access { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>(); public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> Access { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>();
[DataField("accessGroups")] [DataField]
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> AccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>(); public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> AccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
[DataField("extendedAccess")] [DataField]
public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> ExtendedAccess { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>(); public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> ExtendedAccess { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>();
[DataField("extendedAccessGroups")] [DataField]
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>(); public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
[DataField] [DataField]

View File

@@ -23,7 +23,6 @@ public abstract class SharedRoleSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] protected readonly ISharedPlayerManager Player = default!; [Dependency] protected readonly ISharedPlayerManager Player = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!;
@@ -400,7 +399,7 @@ public abstract class SharedRoleSystem : EntitySystem
foreach (var role in delete) foreach (var role in delete)
{ {
_entityManager.DeleteEntity(role); PredictedDel(role);
} }
var update = MindRolesUpdate(mind); var update = MindRolesUpdate(mind);

View File

@@ -120,5 +120,24 @@ public abstract class SharedBorgSwitchableTypeSystem : EntitySystem
{ {
footstepModifier.FootstepSoundCollection = prototype.FootstepCollection; footstepModifier.FootstepSoundCollection = prototype.FootstepCollection;
} }
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
} }
} }

View File

@@ -0,0 +1,12 @@
namespace Content.Shared.Silicons.Bots;
/// <summary>
/// This component describes how a HugBot hugs.
/// </summary>
/// <see cref="SharedHugBotSystem"/>
[RegisterComponent, AutoGenerateComponentState]
public sealed partial class HugBotComponent : Component
{
[DataField, AutoNetworkedField]
public TimeSpan HugCooldown = TimeSpan.FromMinutes(2);
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Emag.Systems;
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.Bots;
/// <summary>
/// This system handles HugBots.
/// </summary>
public abstract class SharedHugBotSystem : EntitySystem
{
[Dependency] private readonly EmagSystem _emag = default!;
public override void Initialize()
{
SubscribeLocalEvent<HugBotComponent, GotEmaggedEvent>(OnEmagged);
}
private void OnEmagged(Entity<HugBotComponent> entity, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction) ||
_emag.CheckFlag(entity, EmagType.Interaction) ||
!TryComp<HugBotComponent>(entity, out var hugBot))
return;
// HugBot HTN checks for emag state within its own logic, so we don't need to change anything here.
args.Handled = true;
}
}
/// <summary>
/// This event is raised on an entity when it is hugged by a HugBot.
/// </summary>
[Serializable, NetSerializable]
public sealed partial class HugBotHugEvent(NetEntity hugBot) : EntityEventArgs
{
public readonly NetEntity HugBot = hugBot;
}

View File

@@ -1,15 +1,25 @@
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Shared.Stacks; namespace Content.Shared.Stacks;
[Prototype] [Prototype]
public sealed partial class StackPrototype : IPrototype public sealed partial class StackPrototype : IPrototype, IInheritingPrototype
{ {
[ViewVariables] /// <inheritdoc />
[IdDataField] [IdDataField]
public string ID { get; private set; } = default!; public string ID { get; private set; } = default!;
/// <inheritdoc />
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<StackPrototype>))]
public string[]? Parents { get; private set; }
/// <inheritdoc />
[NeverPushInheritance]
[AbstractDataField]
public bool Abstract { get; private set; }
/// <summary> /// <summary>
/// Human-readable name for this stack type e.g. "Steel" /// Human-readable name for this stack type e.g. "Steel"
/// </summary> /// </summary>

View File

@@ -587,7 +587,7 @@ public abstract class SharedStorageSystem : EntitySystem
} }
_entList.Add(entity); _entList.Add(entity);
delay += itemSize.Weight * AreaInsertDelayPerItem; delay += itemSize.Weight;
if (_entList.Count >= StorageComponent.AreaPickupLimit) if (_entList.Count >= StorageComponent.AreaPickupLimit)
break; break;
@@ -596,7 +596,7 @@ public abstract class SharedStorageSystem : EntitySystem
//If there's only one then let's be generous //If there's only one then let's be generous
if (_entList.Count >= 1) if (_entList.Count >= 1)
{ {
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, delay, new AreaPickupDoAfterEvent(GetNetEntityList(_entList)), uid, target: uid) var doAfterArgs = new DoAfterArgs(EntityManager, args.User, delay * AreaInsertDelayPerItem, new AreaPickupDoAfterEvent(GetNetEntityList(_entList)), uid, target: uid)
{ {
BreakOnDamage = true, BreakOnDamage = true,
BreakOnMove = true, BreakOnMove = true,

View File

@@ -0,0 +1,11 @@
using Content.Shared.Actions;
namespace Content.Shared.Store.Events;
/// <summary>
/// Opens a store specified by <see cref="StoreComponent"/>
/// Used for entities with a store built into themselves like Revenant or PAI
/// </summary>
public sealed partial class IntrinsicStoreActionEvent : InstantActionEvent
{
}

View File

@@ -0,0 +1,27 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Conditions;
/// <summary>
/// Checks if a triggered entity or the user of a trigger has a certain mindrole.
/// Cancels the trigger otherwise.
/// </summary>
/// <remarks>
/// Mind roles are only networked to their owner! So if you use this on any other entity than yourself it won't be predicted.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MindRoleTriggerConditionComponent : BaseTriggerConditionComponent
{
/// <summary>
/// Whitelist for what mind role components on the owning entity allow this trigger.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? EntityWhitelist;
/// <summary>
/// Whitelist for what mind role components on the User allow this trigger.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? UserWhitelist;
}

View File

@@ -4,20 +4,20 @@ using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Conditions; namespace Content.Shared.Trigger.Components.Conditions;
/// <summary> /// <summary>
/// Checks if the user of a trigger satisfies a whitelist and blacklist condition for the triggered entity or the one triggering it. /// Checks if the user of a trigger satisfies a whitelist and blacklist condition.
/// Cancels the trigger otherwise. /// Cancels the trigger otherwise.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent
{ {
/// <summary> /// <summary>
/// Whitelist for what entites can cause this trigger. /// Whitelist for what entities can cause this trigger.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public EntityWhitelist? UserWhitelist; public EntityWhitelist? UserWhitelist;
/// <summary> /// <summary>
/// Blacklist for what entites can cause this trigger. /// Blacklist for what entities can cause this trigger.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public EntityWhitelist? UserBlacklist; public EntityWhitelist? UserBlacklist;

View File

@@ -58,5 +58,17 @@ public sealed class DnaScrambleOnTriggerSystem : EntitySystem
// Can't use PopupClient or PopupPredicted because the trigger might be unpredicted. // Can't use PopupClient or PopupPredicted because the trigger might be unpredicted.
_popup.PopupEntity(Loc.GetString("scramble-on-trigger-popup"), target.Value, target.Value); _popup.PopupEntity(Loc.GetString("scramble-on-trigger-popup"), target.Value, target.Value);
var ev = new DnaScrambledEvent(target.Value);
RaiseLocalEvent(target.Value, ref ev, true);
} }
} }
/// <summary>
/// Raised after an entity has been DNA Scrambled.
/// Useful for forks that need to run their own updates here.
/// </summary>
/// <param name="flag">The entity that had its DNA scrambled.</param>
[ByRefEvent]
public record struct DnaScrambledEvent(EntityUid Target);

View File

@@ -10,30 +10,35 @@ public sealed partial class TriggerSystem
private void InitializeCondition() private void InitializeCondition()
{ {
SubscribeLocalEvent<WhitelistTriggerConditionComponent, AttemptTriggerEvent>(OnWhitelistTriggerAttempt); SubscribeLocalEvent<WhitelistTriggerConditionComponent, AttemptTriggerEvent>(OnWhitelistTriggerAttempt);
SubscribeLocalEvent<UseDelayTriggerConditionComponent, AttemptTriggerEvent>(OnUseDelayTriggerAttempt); SubscribeLocalEvent<UseDelayTriggerConditionComponent, AttemptTriggerEvent>(OnUseDelayTriggerAttempt);
SubscribeLocalEvent<ToggleTriggerConditionComponent, AttemptTriggerEvent>(OnToggleTriggerAttempt); SubscribeLocalEvent<ToggleTriggerConditionComponent, AttemptTriggerEvent>(OnToggleTriggerAttempt);
SubscribeLocalEvent<ToggleTriggerConditionComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleGetAltVerbs);
SubscribeLocalEvent<RandomChanceTriggerConditionComponent, AttemptTriggerEvent>(OnRandomChanceTriggerAttempt); SubscribeLocalEvent<RandomChanceTriggerConditionComponent, AttemptTriggerEvent>(OnRandomChanceTriggerAttempt);
SubscribeLocalEvent<MindRoleTriggerConditionComponent, AttemptTriggerEvent>(OnMindRoleTriggerAttempt);
SubscribeLocalEvent<ToggleTriggerConditionComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleGetAltVerbs);
} }
private void OnWhitelistTriggerAttempt(Entity<WhitelistTriggerConditionComponent> ent, ref AttemptTriggerEvent args) private void OnWhitelistTriggerAttempt(Entity<WhitelistTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
{ {
if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) if (args.Key != null && !ent.Comp.Keys.Contains(args.Key))
return;
args.Cancelled |= !_whitelist.CheckBoth(args.User, ent.Comp.UserBlacklist, ent.Comp.UserWhitelist); args.Cancelled |= !_whitelist.CheckBoth(args.User, ent.Comp.UserBlacklist, ent.Comp.UserWhitelist);
} }
private void OnUseDelayTriggerAttempt(Entity<UseDelayTriggerConditionComponent> ent, ref AttemptTriggerEvent args) private void OnUseDelayTriggerAttempt(Entity<UseDelayTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
{ {
if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) if (args.Key != null && !ent.Comp.Keys.Contains(args.Key))
return;
args.Cancelled |= _useDelay.IsDelayed(ent.Owner, ent.Comp.UseDelayId); args.Cancelled |= _useDelay.IsDelayed(ent.Owner, ent.Comp.UseDelayId);
} }
private void OnToggleTriggerAttempt(Entity<ToggleTriggerConditionComponent> ent, ref AttemptTriggerEvent args) private void OnToggleTriggerAttempt(Entity<ToggleTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
{ {
if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) if (args.Key != null && !ent.Comp.Keys.Contains(args.Key))
return;
args.Cancelled |= !ent.Comp.Enabled; args.Cancelled |= !ent.Comp.Enabled;
} }
@@ -62,8 +67,9 @@ public sealed partial class TriggerSystem
private void OnRandomChanceTriggerAttempt(Entity<RandomChanceTriggerConditionComponent> ent, private void OnRandomChanceTriggerAttempt(Entity<RandomChanceTriggerConditionComponent> ent,
ref AttemptTriggerEvent args) ref AttemptTriggerEvent args)
{ {
if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) if (args.Key != null && !ent.Comp.Keys.Contains(args.Key))
{ return;
// TODO: Replace with RandomPredicted once the engine PR is merged // TODO: Replace with RandomPredicted once the engine PR is merged
var hash = new List<int> var hash = new List<int>
{ {
@@ -76,5 +82,36 @@ public sealed partial class TriggerSystem
args.Cancelled |= !rand.Prob(ent.Comp.SuccessChance); // When not successful, Cancelled = true args.Cancelled |= !rand.Prob(ent.Comp.SuccessChance); // When not successful, Cancelled = true
} }
private void OnMindRoleTriggerAttempt(Entity<MindRoleTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
{
if (args.Key != null && !ent.Comp.Keys.Contains(args.Key))
return;
if (ent.Comp.EntityWhitelist != null)
{
if (!_mind.TryGetMind(ent.Owner, out var entMindId, out var entMindComp))
{
args.Cancelled = true; // the entity has no mind
return;
}
if (!_role.MindHasRole((entMindId, entMindComp), ent.Comp.EntityWhitelist))
{
args.Cancelled = true; // the entity does not have the required role
return;
}
}
if (ent.Comp.UserWhitelist != null)
{
if (args.User == null || !_mind.TryGetMind(args.User.Value, out var userMindId, out var userMindComp))
{
args.Cancelled = true; // no user or the user has no mind
return;
}
if (!_role.MindHasRole((userMindId, userMindComp), ent.Comp.UserWhitelist))
{
args.Cancelled = true; // the user does not have the required role
}
}
} }
} }

View File

@@ -3,7 +3,9 @@ using Content.Shared.Database;
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Content.Shared.EntityTable; using Content.Shared.EntityTable;
using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle;
using Content.Shared.Mind;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Roles;
using Content.Shared.Timing; using Content.Shared.Timing;
using Content.Shared.Trigger.Components; using Content.Shared.Trigger.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
@@ -39,6 +41,8 @@ public sealed partial class TriggerSystem : EntitySystem
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly ItemToggleSystem _itemToggle = default!; [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
[Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!; [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly EntityTableSystem _entityTable = default!; [Dependency] private readonly EntityTableSystem _entityTable = default!;
public const string DefaultTriggerKey = "trigger"; public const string DefaultTriggerKey = "trigger";

View File

@@ -7,6 +7,7 @@ using Content.Shared.Administration.Components;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Events;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@@ -63,6 +64,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!; [Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!; [Dependency] private readonly SharedStaminaSystem _stamina = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque); private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque);
@@ -83,6 +85,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
SubscribeLocalEvent<MeleeWeaponComponent, HandSelectedEvent>(OnMeleeSelected); SubscribeLocalEvent<MeleeWeaponComponent, HandSelectedEvent>(OnMeleeSelected);
SubscribeLocalEvent<MeleeWeaponComponent, ShotAttemptedEvent>(OnMeleeShotAttempted); SubscribeLocalEvent<MeleeWeaponComponent, ShotAttemptedEvent>(OnMeleeShotAttempted);
SubscribeLocalEvent<MeleeWeaponComponent, GunShotEvent>(OnMeleeShot); SubscribeLocalEvent<MeleeWeaponComponent, GunShotEvent>(OnMeleeShot);
SubscribeLocalEvent<MeleeWeaponComponent, DamageExamineEvent>(OnMeleeExamineDamage);
SubscribeLocalEvent<BonusMeleeDamageComponent, GetMeleeDamageEvent>(OnGetBonusMeleeDamage); SubscribeLocalEvent<BonusMeleeDamageComponent, GetMeleeDamageEvent>(OnGetBonusMeleeDamage);
SubscribeLocalEvent<BonusMeleeDamageComponent, GetHeavyDamageModifierEvent>(OnGetBonusHeavyDamageModifier); SubscribeLocalEvent<BonusMeleeDamageComponent, GetHeavyDamageModifierEvent>(OnGetBonusHeavyDamageModifier);
SubscribeLocalEvent<BonusMeleeAttackRateComponent, GetMeleeAttackRateEvent>(OnGetBonusMeleeAttackRate); SubscribeLocalEvent<BonusMeleeAttackRateComponent, GetMeleeAttackRateEvent>(OnGetBonusMeleeAttackRate);
@@ -95,8 +98,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
SubscribeAllEvent<StopAttackEvent>(OnStopAttack); SubscribeAllEvent<StopAttackEvent>(OnStopAttack);
#if DEBUG #if DEBUG
SubscribeLocalEvent<MeleeWeaponComponent, SubscribeLocalEvent<MeleeWeaponComponent, MapInitEvent>(OnMapInit);
MapInitEvent> (OnMapInit);
} }
private void OnMapInit(EntityUid uid, MeleeWeaponComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, MeleeWeaponComponent component, MapInitEvent args)
@@ -124,6 +126,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
} }
} }
private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component, ref DamageExamineEvent args)
{
if (component.Hidden)
return;
var damageSpec = GetDamage(uid, args.User, component);
if (damageSpec.Empty)
return;
_damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), Loc.GetString("damage-melee"));
}
private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args) private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args)
{ {
var attackRate = GetAttackRate(uid, args.User, component); var attackRate = GetAttackRate(uid, args.User, component);

View File

@@ -66,7 +66,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value); var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value);
visuals.Sprite = component.RopeSprite; visuals.Sprite = component.RopeSprite;
visuals.OffsetA = new Vector2(0f, 0.5f); visuals.OffsetA = new Vector2(0f, 0.5f);
visuals.Target = GetNetEntity(uid); visuals.Target = uid;
Dirty(shotUid.Value, visuals); Dirty(shotUid.Value, visuals);
} }

View File

@@ -34,6 +34,12 @@ public sealed partial class CartridgeAmmoComponent : AmmoComponent
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public bool Spent; public bool Spent;
/// <summary>
/// Is this cartridge automatically marked as trash once spent?
/// </summary>
[DataField, AutoNetworkedField]
public bool MarkSpentAsTrash = true;
/// <summary> /// <summary>
/// Caseless ammunition. /// Caseless ammunition.
/// </summary> /// </summary>

View File

@@ -1,8 +1,12 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Events;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Weapons.Ranged.Systems; namespace Content.Shared.Weapons.Ranged.Systems;
@@ -18,6 +22,7 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine); SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
// Projectile // Projectile
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentGetState>(OnBatteryGetState); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentGetState>(OnBatteryGetState);
@@ -25,6 +30,7 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine); SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
} }
private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args) private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args)
@@ -53,6 +59,51 @@ public abstract partial class SharedGunSystem
args.PushMarkup(Loc.GetString("gun-battery-examine", ("color", AmmoExamineColor), ("count", component.Shots))); args.PushMarkup(Loc.GetString("gun-battery-examine", ("color", AmmoExamineColor), ("count", component.Shots)));
} }
private void OnBatteryDamageExamine<T>(Entity<T> entity, ref DamageExamineEvent args) where T : BatteryAmmoProviderComponent
{
var damageSpec = GetDamage(entity.Comp);
if (damageSpec == null)
return;
var damageType = entity.Comp switch
{
HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"),
ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"),
_ => throw new ArgumentOutOfRangeException(),
};
_damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), damageType);
}
private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component)
{
if (component is ProjectileBatteryAmmoProviderComponent battery)
{
if (ProtoManager.Index<EntityPrototype>(battery.Prototype)
.Components
.TryGetValue(Factory.GetComponentName<ProjectileComponent>(), out var projectile))
{
var p = (ProjectileComponent)projectile.Component;
if (!p.Damage.Empty)
{
return p.Damage * Damageable.UniversalProjectileDamageModifier;
}
}
return null;
}
if (component is HitscanBatteryAmmoProviderComponent hitscan)
{
var dmg = ProtoManager.Index<HitscanPrototype>(hitscan.Prototype).Damage;
return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier;
}
return null;
}
private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args) private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args)
{ {
var shots = Math.Min(args.Shots, component.Shots); var shots = Math.Min(args.Shots, component.Shots);

View File

@@ -65,6 +65,8 @@ public abstract partial class SharedGunSystem : EntitySystem
[Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
private static readonly ProtoId<TagPrototype> TrashTag = "Trash";
private const float InteractNextFire = 0.3f; private const float InteractNextFire = 0.3f;
private const double SafetyNextFire = 0.5; private const double SafetyNextFire = 0.5;
private const float EjectOffset = 0.4f; private const float EjectOffset = 0.4f;
@@ -452,6 +454,14 @@ public abstract partial class SharedGunSystem : EntitySystem
cartridge.Spent = spent; cartridge.Spent = spent;
Appearance.SetData(uid, AmmoVisuals.Spent, spent); Appearance.SetData(uid, AmmoVisuals.Spent, spent);
if (!cartridge.MarkSpentAsTrash)
return;
if (spent)
TagSystem.AddTag(uid, TrashTag);
else
TagSystem.RemoveTag(uid, TrashTag);
} }
/// <summary> /// <summary>

View File

@@ -17,7 +17,7 @@ public sealed partial class AnalysisConsoleComponent : Component
/// Can be null if not linked. /// Can be null if not linked.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public NetEntity? AnalyzerEntity; public EntityUid? AnalyzerEntity;
[DataField] [DataField]
public SoundSpecifier? ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg"); public SoundSpecifier? ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
@@ -35,7 +35,7 @@ public sealed partial class AnalysisConsoleComponent : Component
}; };
/// <summary> /// <summary>
/// The machine linking port for the analyzer /// The machine linking port for linking the console with the analyzer.
/// </summary> /// </summary>
[DataField] [DataField]
public ProtoId<SourcePortPrototype> LinkingPort = "ArtifactAnalyzerSender"; public ProtoId<SourcePortPrototype> LinkingPort = "ArtifactAnalyzerSender";

View File

@@ -1,5 +1,6 @@
using Robust.Shared.Audio; using Content.Shared.DeviceLinking;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Xenoarchaeology.Equipment.Components; namespace Content.Shared.Xenoarchaeology.Equipment.Components;
@@ -35,4 +36,10 @@ public sealed partial class ArtifactAnalyzerComponent : Component
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public bool ReadyToPrint = false; public bool ReadyToPrint = false;
/// <summary>
/// The machine linking port for linking the analyzer with the console.
/// </summary>
[DataField]
public ProtoId<SinkPortPrototype> LinkingPort = "ArtifactAnalyzerReceiver";
} }

View File

@@ -2,7 +2,6 @@ using Content.Shared.Damage;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -14,7 +13,8 @@ namespace Content.Shared.Xenoarchaeology.Equipment.Components;
/// <summary> /// <summary>
/// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments. /// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedArtifactCrusherSystem))] [Access(typeof(SharedArtifactCrusherSystem))]
public sealed partial class ArtifactCrusherComponent : Component public sealed partial class ArtifactCrusherComponent : Component
{ {
@@ -27,19 +27,21 @@ public sealed partial class ArtifactCrusherComponent : Component
/// <summary> /// <summary>
/// When the current crushing will end. /// When the current crushing will end.
/// </summary> /// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField]
public TimeSpan CrushEndTime; public TimeSpan CrushEndTime;
/// <summary> /// <summary>
/// The next second. Used to apply damage over time. /// The next second. Used to apply damage over time.
/// </summary> /// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField]
public TimeSpan NextSecond; public TimeSpan NextSecond;
/// <summary> /// <summary>
/// The total duration of the crushing. /// The total duration of the crushing.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField, AutoNetworkedField]
public TimeSpan CrushDuration = TimeSpan.FromSeconds(10); public TimeSpan CrushDuration = TimeSpan.FromSeconds(10);
/// <summary> /// <summary>
@@ -51,19 +53,19 @@ public sealed partial class ArtifactCrusherComponent : Component
/// <summary> /// <summary>
/// The minimum amount of fragments spawned. /// The minimum amount of fragments spawned.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField, AutoNetworkedField]
public int MinFragments = 2; public int MinFragments = 2;
/// <summary> /// <summary>
/// The maximum amount of fragments spawned, non-inclusive. /// The maximum amount of fragments spawned, non-inclusive.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] [DataField, AutoNetworkedField]
public int MaxFragments = 5; public int MaxFragments = 5;
/// <summary> /// <summary>
/// The material for the fragments. /// The material for the fragments.
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField]
public ProtoId<StackPrototype> FragmentStackProtoId = "ArtifactFragment"; public ProtoId<StackPrototype> FragmentStackProtoId = "ArtifactFragment";
/// <summary> /// <summary>
@@ -100,12 +102,12 @@ public sealed partial class ArtifactCrusherComponent : Component
/// Stores entity of <see cref="CrushingSound"/> to allow ending it early. /// Stores entity of <see cref="CrushingSound"/> to allow ending it early.
/// </summary> /// </summary>
[DataField] [DataField]
public (EntityUid, AudioComponent)? CrushingSoundEntity; public EntityUid? CrushingSoundEntity;
/// <summary> /// <summary>
/// When enabled, stops the artifact crusher from being opened when it is being crushed. /// When enabled, stops the artifact crusher from being opened when it is being crushed.
/// </summary> /// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
public bool AutoLock = false; public bool AutoLock = false;
} }

View File

@@ -15,6 +15,7 @@ namespace Content.Shared.Xenoarchaeology.Equipment;
public abstract class SharedArtifactAnalyzerSystem : EntitySystem public abstract class SharedArtifactAnalyzerSystem : EntitySystem
{ {
[Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!; [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
[Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
@@ -23,10 +24,14 @@ public abstract class SharedArtifactAnalyzerSystem : EntitySystem
SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced); SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved); SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<ArtifactAnalyzerComponent, NewLinkEvent>(OnNewLinkAnalyzer);
SubscribeLocalEvent<ArtifactAnalyzerComponent, LinkAttemptEvent>(OnLinkAttemptAnalyzer);
SubscribeLocalEvent<ArtifactAnalyzerComponent, PortDisconnectedEvent>(OnPortDisconnectedAnalyzer);
SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink); SubscribeLocalEvent<AnalysisConsoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected); SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLinkConsole);
SubscribeLocalEvent<AnalysisConsoleComponent, LinkAttemptEvent>(OnLinkAttemptConsole);
SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnectedConsole);
} }
private void OnItemPlaced(Entity<ArtifactAnalyzerComponent> ent, ref ItemPlacedEvent args) private void OnItemPlaced(Entity<ArtifactAnalyzerComponent> ent, ref ItemPlacedEvent args)
@@ -44,52 +49,74 @@ public abstract class SharedArtifactAnalyzerSystem : EntitySystem
Dirty(ent); Dirty(ent);
} }
private void OnMapInit(Entity<ArtifactAnalyzerComponent> ent, ref MapInitEvent args) private void OnMapInit(Entity<AnalysisConsoleComponent> ent, ref MapInitEvent args)
{ {
if (!TryComp<DeviceLinkSinkComponent>(ent, out var sink)) if (!TryComp<DeviceLinkSourceComponent>(ent, out var source))
return; return;
foreach (var source in sink.LinkedSources) var linkedEntities = _deviceLink.GetLinkedSinks((ent.Owner, source), ent.Comp.LinkingPort);
foreach (var sink in linkedEntities)
{ {
if (!TryComp<AnalysisConsoleComponent>(source, out var analysis)) if (!TryComp<ArtifactAnalyzerComponent>(sink, out var analyzer))
continue; continue;
analysis.AnalyzerEntity = GetNetEntity(ent); ent.Comp.AnalyzerEntity = sink;
ent.Comp.Console = source; analyzer.Console = ent.Owner;
Dirty(source, analysis);
Dirty(ent); Dirty(ent);
Dirty(sink, analyzer);
break; break;
} }
} }
private void OnNewLink(Entity<AnalysisConsoleComponent> ent, ref NewLinkEvent args) private void OnNewLinkConsole(Entity<AnalysisConsoleComponent> ent, ref NewLinkEvent args)
{ {
if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer)) if (args.SourcePort != ent.Comp.LinkingPort || !HasComp<ArtifactAnalyzerComponent>(args.Sink))
return; return;
ent.Comp.AnalyzerEntity = GetNetEntity(args.Sink); ent.Comp.AnalyzerEntity = args.Sink;
analyzer.Console = ent;
Dirty(args.Sink, analyzer);
Dirty(ent); Dirty(ent);
} }
private void OnPortDisconnected(Entity<AnalysisConsoleComponent> ent, ref PortDisconnectedEvent args) private void OnNewLinkAnalyzer(Entity<ArtifactAnalyzerComponent> ent, ref NewLinkEvent args)
{ {
var analyzerNetEntity = ent.Comp.AnalyzerEntity; if (args.SinkPort != ent.Comp.LinkingPort || !HasComp<AnalysisConsoleComponent>(args.Source))
if (args.Port != ent.Comp.LinkingPort || analyzerNetEntity == null)
return; return;
var analyzerEntityUid = GetEntity(analyzerNetEntity); ent.Comp.Console = args.Source;
if (TryComp<ArtifactAnalyzerComponent>(analyzerEntityUid, out var analyzer)) Dirty(ent);
{
analyzer.Console = null;
Dirty(analyzerEntityUid.Value, analyzer);
} }
private void OnLinkAttemptConsole(Entity<AnalysisConsoleComponent> ent, ref LinkAttemptEvent args)
{
if (ent.Comp.AnalyzerEntity != null)
args.Cancel(); // can only link to one device at a time
}
private void OnLinkAttemptAnalyzer(Entity<ArtifactAnalyzerComponent> ent, ref LinkAttemptEvent args)
{
if (ent.Comp.Console != null)
args.Cancel(); // can only link to one device at a time
}
private void OnPortDisconnectedConsole(Entity<AnalysisConsoleComponent> ent, ref PortDisconnectedEvent args)
{
if (args.Port != ent.Comp.LinkingPort || ent.Comp.AnalyzerEntity == null)
return;
ent.Comp.AnalyzerEntity = null; ent.Comp.AnalyzerEntity = null;
Dirty(ent); Dirty(ent);
} }
private void OnPortDisconnectedAnalyzer(Entity<ArtifactAnalyzerComponent> ent, ref PortDisconnectedEvent args)
{
if (args.Port != ent.Comp.LinkingPort || ent.Comp.Console == null)
return;
ent.Comp.Console = null;
Dirty(ent);
}
public bool TryGetAnalyzer(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<ArtifactAnalyzerComponent>? analyzer) public bool TryGetAnalyzer(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<ArtifactAnalyzerComponent>? analyzer)
{ {
analyzer = null; analyzer = null;
@@ -98,14 +125,13 @@ public abstract class SharedArtifactAnalyzerSystem : EntitySystem
if (!_powerReceiver.IsPowered(consoleEnt)) if (!_powerReceiver.IsPowered(consoleEnt))
return false; return false;
var analyzerUid = GetEntity(ent.Comp.AnalyzerEntity); if (!TryComp<ArtifactAnalyzerComponent>(ent.Comp.AnalyzerEntity, out var analyzerComp))
if (!TryComp<ArtifactAnalyzerComponent>(analyzerUid, out var analyzerComp))
return false; return false;
if (!_powerReceiver.IsPowered(analyzerUid.Value)) if (!_powerReceiver.IsPowered(ent.Comp.AnalyzerEntity.Value))
return false; return false;
analyzer = (analyzerUid.Value, analyzerComp); analyzer = (ent.Comp.AnalyzerEntity.Value, analyzerComp);
return true; return true;
} }

View File

@@ -1,9 +1,16 @@
using Content.Shared.Examine; using Content.Shared.Damage;
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Shared.Xenoarchaeology.Equipment.Components; using Content.Shared.Xenoarchaeology.Equipment.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
namespace Content.Shared.Xenoarchaeology.Equipment; namespace Content.Shared.Xenoarchaeology.Equipment;
@@ -12,10 +19,14 @@ namespace Content.Shared.Xenoarchaeology.Equipment;
/// </summary> /// </summary>
public abstract class SharedArtifactCrusherSystem : EntitySystem public abstract class SharedArtifactCrusherSystem : EntitySystem
{ {
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedAudioSystem AudioSystem = default!; [Dependency] protected readonly SharedAudioSystem AudioSystem = default!;
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
@@ -27,6 +38,8 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
SubscribeLocalEvent<ArtifactCrusherComponent, StorageOpenAttemptEvent>(OnStorageOpenAttempt); SubscribeLocalEvent<ArtifactCrusherComponent, StorageOpenAttemptEvent>(OnStorageOpenAttempt);
SubscribeLocalEvent<ArtifactCrusherComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<ArtifactCrusherComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ArtifactCrusherComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<ArtifactCrusherComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<ArtifactCrusherComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
SubscribeLocalEvent<ArtifactCrusherComponent, PowerChangedEvent>(OnPowerChanged);
} }
private void OnInit(Entity<ArtifactCrusherComponent> ent, ref ComponentInit args) private void OnInit(Entity<ArtifactCrusherComponent> ent, ref ComponentInit args)
@@ -53,6 +66,7 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
ent.Comp.AutoLock = true; ent.Comp.AutoLock = true;
args.Handled = true; args.Handled = true;
Dirty(ent);
} }
private void OnStorageOpenAttempt(Entity<ArtifactCrusherComponent> ent, ref StorageOpenAttemptEvent args) private void OnStorageOpenAttempt(Entity<ArtifactCrusherComponent> ent, ref StorageOpenAttemptEvent args)
@@ -66,22 +80,94 @@ public abstract class SharedArtifactCrusherSystem : EntitySystem
args.PushMarkup(ent.Comp.AutoLock ? Loc.GetString("artifact-crusher-examine-autolocks") : Loc.GetString("artifact-crusher-examine-no-autolocks")); args.PushMarkup(ent.Comp.AutoLock ? Loc.GetString("artifact-crusher-examine-autolocks") : Loc.GetString("artifact-crusher-examine-no-autolocks"));
} }
public void StopCrushing(Entity<ArtifactCrusherComponent> ent, bool early = true) private void OnGetVerbs(Entity<ArtifactCrusherComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{ {
var (_, crusher) = ent; if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing)
if (!crusher.Crushing)
return; return;
crusher.Crushing = false; if (!TryComp<EntityStorageComponent>(ent, out var entityStorageComp) ||
Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, false); entityStorageComp.Contents.ContainedEntities.Count == 0)
return;
if (!_power.IsPowered(ent.Owner))
return;
var user = args.User;
var verb = new AlternativeVerb
{
Text = Loc.GetString("artifact-crusher-verb-start-crushing"),
Priority = 2,
Act = () => StartCrushing((ent, ent.Comp, entityStorageComp), user)
};
args.Verbs.Add(verb);
}
private void OnPowerChanged(Entity<ArtifactCrusherComponent> ent, ref PowerChangedEvent args)
{
if (!args.Powered)
StopCrushing(ent);
}
public void StartCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent, EntityUid? user = null)
{
var (uid, crusher, _) = ent;
if (crusher.Crushing)
return;
if (crusher.AutoLock)
_popup.PopupPredicted(Loc.GetString("artifact-crusher-autolocks-enable"), uid, user);
crusher.Crushing = true;
crusher.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
crusher.CrushEndTime = _timing.CurTime + crusher.CrushDuration;
crusher.CrushingSoundEntity = AudioSystem.PlayPvs(crusher.CrushingSound, ent)?.Entity;
_appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, true);
Dirty(ent, ent.Comp1);
}
public void StopCrushing(Entity<ArtifactCrusherComponent> ent, bool early = true)
{
if (!ent.Comp.Crushing)
return;
ent.Comp.Crushing = false;
_appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, false);
if (early) if (early)
{ {
AudioSystem.Stop(crusher.CrushingSoundEntity?.Item1, crusher.CrushingSoundEntity?.Item2); AudioSystem.Stop(ent.Comp.CrushingSoundEntity);
crusher.CrushingSoundEntity = null; ent.Comp.CrushingSoundEntity = null;
} }
Dirty(ent, ent.Comp); Dirty(ent, ent.Comp);
} }
public virtual void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent) { }
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ArtifactCrusherComponent, EntityStorageComponent>();
while (query.MoveNext(out var uid, out var crusher, out var storage))
{
if (!crusher.Crushing)
continue;
if (crusher.NextSecond < _timing.CurTime)
{
var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
foreach (var contained in contents)
{
_damageable.TryChangeDamage(contained, crusher.CrushingDamage);
}
crusher.NextSecond += TimeSpan.FromSeconds(1);
Dirty(uid, crusher);
}
if (crusher.CrushEndTime < _timing.CurTime)
FinishCrushing((uid, crusher, storage));
}
}
} }

View File

@@ -1,15 +1,4 @@
Entries: Entries:
- author: CoconutThunder
changes:
- message: Fixed lubed items being thrown when looking at the pickup verb.
type: Fix
- message: Fixed multihanded items showing a popup when looking at the pickup verb.
type: Fix
- message: Fixed a bug with lubed handcuffs.
type: Fix
id: 8561
time: '2025-05-25T05:10:58.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38705
- author: CoconutThunder - author: CoconutThunder
changes: changes:
- message: The Chief Medical Officer should now appear with the correct precedence - message: The Chief Medical Officer should now appear with the correct precedence
@@ -3960,3 +3949,183 @@
id: 9062 id: 9062
time: '2025-10-09T14:00:07.0000000+00:00' time: '2025-10-09T14:00:07.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40790 url: https://github.com/space-wizards/space-station-14/pull/40790
- author: Princess-Cheeseballs
changes:
- message: The thieving beacon can now detect the officer's handgun objective.
type: Fix
id: 9063
time: '2025-10-10T04:59:26.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40811
- author: Hitlinemoss
changes:
- message: The Paladin AI lawset has been rewritten.
type: Tweak
id: 9064
time: '2025-10-10T12:41:57.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40343
- author: kontakt
changes:
- message: Ninjas can now have a bombing target at any warp point.
type: Tweak
id: 9065
time: '2025-10-10T21:27:36.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40726
- author: frigid-dev
changes:
- message: Ore Crabs now collide with the player if mob collisions are enabled.
type: Tweak
- message: Ore Crabs now show they are stunned
type: Fix
id: 9066
time: '2025-10-10T23:31:04.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40764
- author: Crude Oil
changes:
- message: Removed 'Activate Holopad Projector' verb from the station AI core.
type: Fix
id: 9067
time: '2025-10-11T00:03:14.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39937
- author: Hitlinemoss
changes:
- message: Ice now satiates thirst.
type: Tweak
- message: Ice now evaporates.
type: Tweak
id: 9068
time: '2025-10-11T00:20:22.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40149
- author: slarticodefast
changes:
- message: The artifact analysis console no longer breaks when trying to link it
to multiple artifact analyzers or vice versa.
type: Fix
id: 9069
time: '2025-10-11T00:33:04.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39984
- author: qwerltaz
changes:
- message: The ID card computer now has buttons to grant or revoke all access from
the target ID card.
type: Add
id: 9070
time: '2025-10-11T00:52:01.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39375
- author: Centronias
changes:
- message: The HugBot, which is similar in purpose and construction to other small
robots like the CleanBot.
type: Add
id: 9071
time: '2025-10-11T01:05:08.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/37557
- author: PicklOH
changes:
- message: Added more Syndie ammo to the EMAG lathe inventory
type: Add
id: 9072
time: '2025-10-11T05:14:34.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40822
- author: TrixxedHeart
changes:
- message: Updated Xenoarchaeology guidebook page to current system.
type: Tweak
id: 9073
time: '2025-10-11T17:32:21.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40621
- author: HTMLSystem
changes:
- message: Added infectious anom sprites for moths and arachnids.
type: Add
id: 9074
time: '2025-10-11T18:01:49.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39508
- author: ScarKy0
changes:
- message: Mice, cockroaches and other small mobs can no longer unwrap parcels.
type: Fix
id: 9075
time: '2025-10-11T21:32:59.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40838
- author: Hitlinemoss
changes:
- message: Autolathes can now print durathread.
type: Add
id: 9076
time: '2025-10-11T21:46:46.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40837
- author: cloudyias
changes:
- message: Added the ability for droppers to be printed via autolathes and medfabs
type: Add
id: 9077
time: '2025-10-11T23:15:41.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40074
- author: jessicamaybe
changes:
- message: Added swabs and an emag inventory to the biogenerator
type: Tweak
id: 9078
time: '2025-10-12T00:15:18.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39037
- author: SuperGDPWYL
changes:
- message: Added the Syndicate ID Card to the uplink for 1 TC.
type: Add
id: 9079
time: '2025-10-12T00:56:09.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38381
- author: aada
changes:
- message: Cannabis no longer stacks infinitely. Sorry, botanists!
type: Fix
id: 9080
time: '2025-10-12T01:29:01.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38412
- author: GovnokradZXC
changes:
- message: New hair named Pigtail (Over Eye)
type: Add
id: 9081
time: '2025-10-12T05:45:59.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39850
- author: Princess-Cheeseballs
changes:
- message: Meat Kudzu gasps less often.
type: Tweak
id: 9082
time: '2025-10-12T10:47:30.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39304
- author: PJB3005
changes:
- message: The patrons list in the in-game credits works again.
type: Fix
id: 9083
time: '2025-10-12T11:02:33.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40840
- author: DrSmugleaf
changes:
- message: Fixed species not being ordered alphabetically in the character customization
UI.
type: Fix
id: 9084
time: '2025-10-12T11:14:46.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39359
- author: Callmore
changes:
- message: Bullet casings can now be picked up again.
type: Fix
id: 9085
time: '2025-10-12T18:45:39.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40829
- author: CoconutThunder
changes:
- message: Fixed lubed items being thrown when looking at the pickup verb.
type: Fix
- message: Fixed multihanded items showing a popup when looking at the pickup verb.
type: Fix
- message: Fixed a bug with lubed handcuffs.
type: Fix
id: 9086
time: '2025-05-25T05:10:58.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38705

View File

@@ -764,4 +764,25 @@
id: 92 id: 92
time: '2025-10-08T20:41:46.0000000+00:00' time: '2025-10-08T20:41:46.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40785 url: https://github.com/space-wizards/space-station-14/pull/40785
- author: ToastEnjoyer
changes:
- message: On Plasma, added more nitrogen canisters to the maintenance hallways
type: Add
id: 93
time: '2025-10-10T01:11:33.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40794
- author: opl
changes:
- message: On Packed, prisoners can now use the Megaseed in brig.
type: Fix
id: 94
time: '2025-10-10T20:33:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40821
- author: CoconutThunder
changes:
- message: Reduced frequency of syndicate shark attacks
type: Tweak
id: 95
time: '2025-10-12T18:45:54.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40855
Order: 1 Order: 1

Some files were not shown because too many files have changed in this diff Show More