Compare commits

...

72 Commits

Author SHA1 Message Date
Ed
237716b353 icons base 2024-10-18 17:21:19 +03:00
Ed
f14177bf86 Alchemist update (#497)
* rebalance alchemy resource

* lockers alternative

* map update
2024-10-18 14:56:01 +03:00
Ed
9a8d30022a Wallpaper update + paper folder (#495)
* paper folders

* Update CP14WorkbenchSystem.cs

* wallpaper update

* Update walls.yml

* remove cable sprites

* Update migration.yml
2024-10-17 13:08:36 +03:00
Ed
ebca84d28f Minor fixes (#494)
* minor loadout tweaks

* wooden counter

* fix parry predicting

* tables crafting simplify
2024-10-16 17:32:41 +03:00
Ed
5b4a7f6edc Ed 15 10 2024 entity hided (#493)
* test

* hide ss14 ents

* Update basic_cloak.yml
2024-10-15 16:53:54 +03:00
Ed
a83d6f0d87 locale (#491)
* locale

* delete ss14 items generated locales
2024-10-15 16:21:07 +03:00
Ed
5d89812abc Merge pull request #490 from crystallpunk-14/ed-15-10-2024-upstream
Ed 15 10 2024 upstream
2024-10-15 15:41:02 +03:00
Ed
31d39b36ae Update CP14ExpeditionSystem.cs 2024-10-15 15:28:16 +03:00
Ed
dc47bb2283 Merge remote-tracking branch 'upstream/stable' into ed-15-10-2024-upstream
# Conflicts:
#	Content.Server/Station/Systems/StationSpawningSystem.cs
2024-10-15 15:25:44 +03:00
Ed
d259191d3d Cargo system (#487)
* simple storeship arriving

* pupu

* ship cycling

* buy positions prototypes

* i hate UI

* PriceControl

* second tab ui

* baloon! pallets!

* update shop in town

* setup billboard timer

* split to sell and buy categories

* renaming gaming

* actually selling

* fix infinity selling

* improve timer

* move description too rigt part UI

* bar selling

* iron cabinet

* purge currency categories

* remove town balance, add money box

* special proposal, FTLImmune anchor

* fix UI

* remove tests buying

* Update CP14StoreWindow.xaml.cs

* currency converter

* currency clean up

* Update CP14CargoSystem.cs

* clean up part 2

* rider petpet

* coins audio

* coin improvment

* Update coins.yml

* translate

* more coins roundstart

* Update wallet.yml

* Update wallet.yml

* generate coin problem fix

* refactor proto reading

* fixes

* huh

* shuttle logshit fix, add to tavern map

* Update CP14StationTravelingStoreShipTargetComponent.cs
2024-10-15 15:22:06 +03:00
Errant
519a6b2474 HOTFIX: Fix tech anomaly nexttimer (#32805) (#32807)
Fix tech anomaly nexttimer (#32805)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-10-14 11:41:31 +02:00
Ed
4425b77c90 Return RU lang 2024-10-14 11:39:12 +03:00
Pieter-Jan Briers
796764d755 Fix some rounds failing to end due to mind roles (#32792) (#32793)
* Fix some rounds failing to end due to mind roles

Fixes #32791

This is caused by ShowRoundEndScoreboard running into a bug trying to display antags: some player is showing up as antag with MindIsAntagonist(), but has no antag roles listed in MindGetAllRoleInfo().

This was caused by one of the roles of the player having the Antag boolean set, but having no AntagPrototype set.

The responsible mind role appeared to be MindRoleSubvertedSilicon which is missing a set for the SubvertedSilicon antag prototype.

I also added resilience to the round-end code to make it so that an exception showing the scoreboard (and sending the Discord message) would not cause the round end logic to completely abort from an exception.

I am planning to add an integration test to cover this bug (no prototype in mind roles), but I'll leave that for not-the-immediate-hotfix.

* At least one maintainer approved this tiny PR without reading it, not naming names.
2024-10-13 23:00:40 +02:00
Errant
e5ad32fe93 Fix random test fail in DeleteAllThenGhost (#32753)
It's simple. We kill the heisentest
2024-10-13 17:31:38 +02:00
Ed
648505dc15 Switch to ENG language 2024-10-13 15:02:57 +03:00
Jezithyr
af72f2e17c Applying Fix from #32764 to staging 2024-10-12 22:21:44 -07:00
Nim
40959cf422 fix resp (#488) 2024-10-13 02:09:41 +03:00
Nim
a6fc5e6ace Rabbit (#486)
* rabbit

* meat

* гладить

* тоже гладить
2024-10-12 20:01:13 +03:00
scrivoy
e98af125dc Fixed capitalization in Interface description (#32739) 2024-10-10 21:40:41 +02:00
PJBot
bc64676747 Automatic changelog update 2024-10-10 14:36:43 +00:00
nikthechampiongr
e7b7e2270d Fix bounties(and potentially other things) running out of ids (#32700)
* Make NameIdentifier Ids get refreshed after round restarts

Before this commit the existing values would just get shuffled.
This means that eventually the server would run out of ids to give to
new entities for different groups. As a result everything would get id 0

* Comply with what seemingly is the convention for sawmills

* Make it impossible to insert a bounty with a duplicate id

* Reduce duplication

* Remove unused sawmill

* Fix rustbrain and skill issue

* Aaaa

* Apply suggestions from code review

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-10-10 16:35:35 +02:00
PJBot
740288eb96 Automatic changelog update 2024-10-10 08:50:05 +00:00
Errant
93c7bdc134 Mind Role Entities (#31318)
* Mind Role Entities wip

* headrev count fix

* silicon stuff, cleanup

* exclusive antag config, cleanup

* jobroleadd overwerite

* logging stuff

* MindHasRole cleanup, admin log stuff

* last second cleanup

* ocd

* minor cleanup

* remove createdTime datafield

* now actually using the event replacement I made for role time tracking

* weh
2024-10-10 10:48:56 +02:00
Pieter-Jan Briers
3e078ab3e0 Fix error log when recycling something with small material counts. (#32723)
* Fix error log when recycling something with small material counts.

MaterialStorageSystem.SpawnMultipleFromMaterial now doesn't call StackSystem.SpawnMultiple if it tries to spawn zero. This happens because the recycler calls SpawnMultipleFromMaterial for everything recycled, even if the amount it has stored is < the amount for one sheet.

* Update Content.Server/Materials/MaterialStorageSystem.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-10-10 03:29:26 +02:00
PJBot
ac16a05fef Automatic changelog update 2024-10-09 18:02:38 +00:00
beck-thompson
327466a6e2 Plushies can now have pAIs stuffed into them (v2)! (#30805)
* First commit

* I forgot silly me

* Actually added comments

* spellin

* fixes

* more blacklists

* Minor fixes

* Speech Verb also changes now

* Simple name stuff

* Other fixes

* remove one line of whitespace

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2024-10-09 20:01:32 +02:00
chavonadelal
6d99597349 Job title localization (#32338)
* Job title localization

* Correcting fields
2024-10-09 17:05:36 +02:00
Ed
844b4d4616 underworld (#485) 2024-10-09 16:19:38 +03:00
PJBot
fc1c709d44 Automatic changelog update 2024-10-09 12:45:45 +00:00
beck-thompson
5a41cc81b3 Decrease price of radio jammer from 4 tc -> 3 tc (#32472)
* First commit

* increase price by one tc
2024-10-09 14:44:38 +02:00
lzk
d450b613d6 fix typo (#32712) 2024-10-09 14:41:07 +02:00
PJBot
b657aba2e1 Automatic changelog update 2024-10-09 11:56:57 +00:00
SlamBamActionman
ddaa0e83c6 Add admin log for codewords (#32531)
* initial commit

* Delta review
2024-10-09 13:55:48 +02:00
Pieter-Jan Briers
1dbbf315c7 Update submodule to v236.1.0 (#32704) 2024-10-09 13:41:03 +02:00
Ed
b9df2a495a some blacksmith bandage (#483)
* anvil

* iron and copper ore walls + procedural cave update

* vladimirs rocks

* furnace

* Revert "furnace"

This reverts commit f378e4fb85.

* Revert "vladimirs rocks"

This reverts commit b2c6a193fc.

* Revert "iron and copper ore walls + procedural cave update"

This reverts commit dc6d70b10e.

* Revert "anvil"

This reverts commit 3ab1649992.

* Reapply "anvil"

This reverts commit 91c226d18d.

* huh

* fuck anvil

* Revert "fuck anvil"

This reverts commit 3c3a87c25e.

* Revert "huh"

This reverts commit 7fb44b4908.

* fix comment

* Reapply "iron and copper ore walls + procedural cave update"

This reverts commit 891c3ba0aa.

* Reapply "vladimirs rocks"

This reverts commit 4f2eaef6c1.

* Reapply "furnace"

This reverts commit 9d7e042ddf.

* Update anvil.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* Update workbenchs.yml

* EVIL anvil

* Update workbenchs.yml
2024-10-09 13:40:06 +03:00
PJBot
357e998cbb Automatic changelog update 2024-10-08 23:30:49 +00:00
beck-thompson
384ff7e8f6 Add pumpkin pie! (#32623)
* first commit

* Licence fix

* rosysyntax licence change (permission granted!)

* simplify

* Better wording
2024-10-09 10:29:41 +11:00
Nim
1ca7456363 AI Factions (#481)
* factions

* CP14

* Update ai_factions.yml

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
2024-10-08 22:52:46 +03:00
PJBot
dc2899c274 Automatic changelog update 2024-10-08 18:30:44 +00:00
K-Dynamic
6f9b4f4226 CHIMP and APE particle speed increase (#32690)
* swap omega and delta particle colours

* remove upgrade doafter

* explicit syndicate chip description

* Syndicate CHIMP stealth

* fast projectile speed

* 10 to 12 shots

* buff heat dmg

* upgrade chip mentions omega particle

* Revert "remove upgrade doafter"

This reverts commit 8a321980b7a384daca06215656494e0c116e7333.

* Revert "explicit syndicate chip description"

This reverts commit c803c773739a61fc5b3a6cb90810622a6d5846c9.

* Revert "Syndicate CHIMP stealth"

This reverts commit 698a108580892addabf8d51494a72e1ee651b8e6.

* Revert "10 to 12 shots"

This reverts commit 858ac0392be0549eb0a288648413d1020cabae1a.

* Revert "swap omega and delta particle colours"

This reverts commit 4c000b2f110a5d35f317cb61cb5b03ea32841ad5.

* Revert "buff heat dmg"

This reverts commit 02a8690dafbd41631b098e51ef9afba5b6ee6ac4.
2024-10-08 20:29:37 +02:00
PJBot
28f576dae8 Automatic changelog update 2024-10-08 09:54:56 +00:00
Plykiya
1366f6b405 Replace the Librarian's round-start D10 with a D20 (#32648)
Replace D10 with a D20
2024-10-08 11:53:50 +02:00
averystarbit
0e0887bd49 Added proper capitalisation for supervisors when entering the game (#32683)
changed punctuation for jobs, added proper capitalization and comma usa
2024-10-08 11:52:46 +02:00
PJBot
1f04117edf Automatic changelog update 2024-10-08 09:52:31 +00:00
shamp
922dd0bce6 Fix cloth dupe (#32685)
Update curtains.yml
2024-10-08 11:51:23 +02:00
lzk
33042b00d0 Fix cryo locale again (#32654)
* Fix cryo locale again

* yeep
2024-10-08 11:50:54 +02:00
Ubaser
38fd54a1bf Update Core (#32665)
* add

* remove invalids

* fix

* yes
2024-10-07 19:59:06 -06:00
PJBot
34df781668 Automatic changelog update 2024-10-07 22:43:51 +00:00
Saphire Lattice
f22f9e39c5 Change minibomb to be explosion resistant and start timer on damage (#32429)
* Make minibomb explosion resistant and trigger timer on damage

* Tune damage behaviour and threshold for minibomb
2024-10-08 00:42:42 +02:00
Pieter-Jan Briers
eecbfb63a0 Move client dumpentities command to "DEBUG" (#32687) 2024-10-07 19:19:42 +02:00
Scribbles0
6b10e00da4 oasis update (#32679)
sec apcs and ai core wiring
2024-10-06 19:24:44 -06:00
PJBot
a6c468b697 Automatic changelog update 2024-10-06 13:34:08 +00:00
Golinth
46a2eb545e Fully Revert Clown Waddling (#32652)
Fully revert Clown Waddling (revival of #29161)

A sad day, see #29156 for discussion
2024-10-06 15:33:02 +02:00
PJBot
35fc1b4037 Automatic changelog update 2024-10-06 12:49:56 +00:00
Арт
867efe8b5b Add flowers to loadout (#32097)
* Add_Poppy&Lily

* Add_FlowerWreath

* Add_Headflowers

Sprites, meta, prototype

* Id_Changes

* Changes

* Update_Sprite

* Desc_Change

* Scale_Change

* Sprite_Scaling
2024-10-06 14:48:49 +02:00
Spessmann
126c1786de Cog update fix (#32657)
* fixes for cog

* ok fixed that
2024-10-05 20:30:10 -06:00
PJBot
386e59b462 Automatic changelog update 2024-10-05 18:45:55 +00:00
slarticodefast
eb4e422cb0 fix light bulbs not fitting into the trash bag (#32452)
fix trash
2024-10-05 12:44:47 -06:00
Spessmann
2287df13bd Cog engineering DIY update (#32651)
engineers forced to do work, sobbing
2024-10-05 12:08:39 -06:00
TakoDragon
db64ad9c4d Marathon Removed double walls and a wrong cable (#32603)
removed double walls and cable fucks
2024-10-04 17:18:46 -06:00
NotSoDamn
644d7ab682 BaseAdvancedPen migration (#32638)
* Update migration.yml

* Update Resources/migration.yml

Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>

---------

Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
2024-10-04 23:13:30 +03:00
PJBot
ec4acdebbf Automatic changelog update 2024-10-04 09:35:55 +00:00
Saphire Lattice
94c8018ff3 Change the syndicate charge to start a timer on signal (#32423)
* Change the syndicate charge to start a timer on signal

* Actually add the component in question

* Add default link for TimerStart signal
2024-10-04 11:34:48 +02:00
PJBot
fbb6f17add Automatic changelog update 2024-10-04 08:44:51 +00:00
Saphire Lattice
88f060d51a Make the explosions throw the container/item they originated from (#32428)
Extra fun if it's something that can trigger multiple times
2024-10-04 10:43:45 +02:00
PJBot
8b65186bfa Automatic changelog update 2024-10-04 05:40:57 +00:00
SoulFN
9e054c1f9d Give dragon pull ability (#32568) 2024-10-04 15:39:50 +10:00
PJBot
423d394af1 Automatic changelog update 2024-10-04 02:56:43 +00:00
Plykiya
295e63193c Two additional checks to prevent FTLing stations (#32558)
Add two additional checks to prevent FTLing
2024-10-04 12:55:36 +10:00
Jezithyr
570c166517 Disable Auto-Publish (#32627)
disable auto publish
2024-10-04 03:16:32 +02:00
PJBot
7edd1c6d52 Automatic changelog update 2024-10-04 01:14:09 +00:00
Southbridge
c0d67429ab Box Station - Connected Recycler, rerouted power for Singlo Substation, added law boards to AI upload room (#32608)
* Fixed #32443 and #30375

* Fixed duplicate ID issue, and added lawboards to resolve #32581

* Removed Overlord board and added Crewsimov

* Removed invalid UserInterface component
2024-10-03 19:12:59 -06:00
758 changed files with 102029 additions and 23667 deletions

View File

@@ -5,8 +5,8 @@ concurrency:
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
# schedule:
# - cron: '0 10 * * *'
jobs:
build:

View File

@@ -1,149 +0,0 @@
using System.Numerics;
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
base.Initialize();
SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}
private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StartWaddling((GetEntity(msg.Entity), comp));
}
private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StopWaddling((GetEntity(msg.Entity), comp));
}
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
{
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
if (_gravity.IsWeightless(entity.Owner))
return;
if (!_actionBlocker.CanMove(entity.Owner, mover))
return;
// Do nothing if buckled in
if (_buckle.IsBuckled(entity.Owner))
return;
// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(entity.Owner))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
}
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
{
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
}
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
{
if (args.Key != entity.Comp.KeyName)
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
_animation.Stop(entity.Owner, entity.Comp.KeyName);
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
}
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
{
entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation()
{
Length = TimeSpan.FromSeconds(len),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
}
},
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};
_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}

View File

@@ -0,0 +1,22 @@
<Control xmlns="https://spacestation14.io">
<GridContainer Columns="6">
<TextureRect Name="GoldView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="GoldText"
Margin="0,0,5,0"/>
<TextureRect Name="SilverView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="SilverText"
Margin="0,0,5,0"/>
<TextureRect Name="CopperView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="CopperText"
Margin="0,0,5,0"/>
</GridContainer>
</Control>

View File

@@ -0,0 +1,46 @@
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14PriceControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly SpriteSystem _sprite;
public CP14PriceControl(int price)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entity.System<SpriteSystem>();
var rsiPath = new ResPath("_CP14/Interface/Misc/coins.rsi");
var total = price;
var gp = total / 100;
total %= 100;
var sp = total / 10;
total %= 10;
var cp = total;
CopperView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "c"));
CopperText.Text = cp.ToString();
SilverView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "s"));
SilverText.Text = sp.ToString();
GoldView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "g"));
GoldText.Text = gp.ToString();
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared._CP14.TravelingStoreShip;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._CP14.TravelingStoreShip;
public sealed class CP14StoreBoundUserInterface : BoundUserInterface
{
private CP14StoreWindow? _window;
public CP14StoreBoundUserInterface(EntityUid owner, [NotNull] Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<CP14StoreWindow>();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
switch (state)
{
case CP14StoreUiState storeState:
_window?.UpdateUI(storeState);
break;
}
}
}

View File

@@ -0,0 +1,15 @@
<Control xmlns="https://spacestation14.io">
<Button Name="ProductButton" Access="Public">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<TextureRect Name="View"
MinSize="48 48"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Stretch="KeepAspectCentered"/>
<RichTextLabel Name="ProductName" VerticalAlignment="Center" Access="Public"/>
<BoxContainer Name="PriceHolder" VerticalAlignment="Center" HorizontalExpand="True" HorizontalAlignment="Right"/>
</BoxContainer>
</BoxContainer>
</Button>
</Control>

View File

@@ -0,0 +1,44 @@
using Content.Shared._CP14.TravelingStoreShip;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14StoreProductControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
private readonly SpriteSystem _sprite;
public CP14StoreProductControl(CP14StoreUiProductEntry entry)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entity.System<SpriteSystem>();
UpdateName(entry.Name);
UpdateView(entry.Icon);
UpdatePrice(entry.Price);
}
private void UpdatePrice(int price)
{
PriceHolder.RemoveAllChildren();
PriceHolder.AddChild(new CP14PriceControl(price));
}
private void UpdateName(string name)
{
ProductName.Text = $"[bold]{name}[/bold]";
}
private void UpdateView(SpriteSpecifier spriteSpecifier)
{
View.Texture = _sprite.Frame0(spriteSpecifier);
}
}

View File

@@ -0,0 +1,31 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'cp14-store-ui-title'}"
MinSize="800 600"
SetSize="800 600">
<BoxContainer Orientation="Horizontal">
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
<!-- Product list (left side UI) -->
<TabContainer SizeFlagsStretchRatio="0.5" Name="Tabs" HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="BuyProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
</ScrollContainer>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="SellProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
</ScrollContainer>
</TabContainer>
<!-- Station trading data (right side UI) -->
<BoxContainer SizeFlagsStretchRatio="0.5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 10 0">
<controls:StripeBack>
<PanelContainer>
<Label Text="{Loc 'cp14-store-ui-order'}" Align="Center" Margin="0 5 0 3"/>
</PanelContainer>
</controls:StripeBack>
<Label Name="TravelTimeLabel" Text="00:00" HorizontalAlignment="Center" HorizontalExpand="True" Margin="0 15 0 0"/>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5"/>
<RichTextLabel Name="SelectedName" HorizontalExpand="True" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5"/>
<RichTextLabel Name="SelectedDesc" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top" Margin="5"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,79 @@
using Content.Shared._CP14.TravelingStoreShip;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14StoreWindow : DefaultWindow
{
[Dependency] private readonly IGameTiming _timing = default!;
private TimeSpan? _nextTravelTime;
private bool _onStation;
public CP14StoreWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Tabs.SetTabTitle(0, Loc.GetString("cp14-store-ui-tab-buy"));
Tabs.SetTabTitle(1, Loc.GetString("cp14-store-ui-tab-sell"));
}
public void UpdateUI(CP14StoreUiState state)
{
UpdateProducts(state);
_nextTravelTime = state.NextTravelTime;
_onStation = state.OnStation;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
//Updating time
if (_nextTravelTime is not null)
{
var time = _nextTravelTime.Value - _timing.CurTime;
TravelTimeLabel.Text =
$"{Loc.GetString(_onStation ? "cp14-store-ui-next-travel-out" : "cp14-store-ui-next-travel-in")} {Math.Max(time.Minutes, 0):00}:{Math.Max(time.Seconds, 0):00}";
}
}
private void UpdateProducts(CP14StoreUiState state)
{
BuyProductsContainer.RemoveAllChildren();
SellProductsContainer.RemoveAllChildren();
foreach (var product in state.ProductsBuy)
{
var control = new CP14StoreProductControl(product);
control.ProductButton.OnPressed += _ =>
{
SelectProduct(product);
};
BuyProductsContainer.AddChild(control);
}
foreach (var product in state.ProductsSell)
{
var control = new CP14StoreProductControl(product);
control.ProductButton.OnPressed += _ =>
{
SelectProduct(product);
};
SellProductsContainer.AddChild(control);
}
}
private void SelectProduct(CP14StoreUiProductEntry? entry)
{
SelectedName.Text = entry is null ? string.Empty : $"[bold]{entry.Value.Name}[/bold]";
SelectedDesc.Text = entry is null ? string.Empty : entry.Value.Desc;
}
}

View File

@@ -1,4 +1,5 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.GameTicking;
@@ -120,8 +121,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
var roles = roleSys.MindGetAllRoles(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
var roles = roleSys.MindGetAllRoleInfo(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The second dummy player should be a medic
@@ -131,8 +132,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
roles = roleSys.MindGetAllRoles(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
roles = roleSys.MindGetAllRoleInfo(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The other two players should have just spawned in as normal.
@@ -141,13 +142,14 @@ public sealed class NukeOpsTest
void CheckDummy(int i)
{
var ent = dummyEnts[i];
var mind = mindSys.GetMind(ent)!.Value;
var mindCrew = mindSys.GetMind(ent)!.Value;
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
}
// The game rule exists, and all the stations/shuttles/maps are properly initialized
@@ -238,7 +240,8 @@ public sealed class NukeOpsTest
for (var i = 0; i < nukies.Length - 1; i++)
{
entMan.DeleteEntity(nukies[i]);
Assert.That(roundEndSys.IsRoundEndRequested, Is.False,
Assert.That(roundEndSys.IsRoundEndRequested,
Is.False,
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
}
// Delete the last nukie and make sure the round ends.

View File

@@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Preferences;
using Content.Shared.Roles.Jobs;
namespace Content.IntegrationTests.Tests.Internals;
@@ -25,10 +24,7 @@ public sealed class AutoInternalsTests
await server.WaitAssertion(() =>
{
var profile = new HumanoidCharacterProfile();
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
{
Prototype = "TestInternalsDummy"
}, profile, station: null);
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null);
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");

View File

@@ -1,10 +1,8 @@
#nullable enable
using System.Linq;
using Content.Server.Ghost;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Commands;
using Content.Server.Players;
using Content.Server.Roles;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -18,7 +16,6 @@ using Robust.Server.Console;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -287,27 +284,27 @@ public sealed partial class MindTests
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
var traitorRole = new TraitorRoleComponent();
var traitorRole = "MindRoleTraitor";
roleSystem.MindAddRole(mindId, traitorRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
var jobRole = new JobComponent();
var jobRole = "";
roleSystem.MindAddRole(mindId, jobRole);
roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
});
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
@@ -315,15 +312,15 @@ public sealed partial class MindTests
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
});
roleSystem.MindRemoveRole<JobComponent>(mindId);
roleSystem.MindRemoveRole<JobRoleComponent>(mindId);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
});
});

View File

@@ -3,7 +3,6 @@ using Content.Server.Station.Systems;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -68,10 +67,7 @@ public sealed class LoadoutTests
profile.SetLoadout(new RoleLoadout("LoadoutTester"));
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
{
Prototype = "LoadoutTester"
}, profile, station: null);
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null);
var slotQuery = inventorySystem.GetSlotEnumerator(tester);
var checkedCount = 0;

View File

@@ -67,7 +67,7 @@ namespace Content.Server.Access.Systems
if (!TryComp<IdCardComponent>(uid, out var idCard))
return;
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon);
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon);
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
}

View File

@@ -96,7 +96,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
PrivilegedIdIsAuthorized(uid, component),
true,
targetIdComponent.FullName,
targetIdComponent.JobTitle,
targetIdComponent.LocalizedJobTitle,
targetAccessComponent.Tags.ToList(),
possibleAccess,
jobProto,

View File

@@ -22,11 +22,17 @@ public sealed class TechAnomalySystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<TechAnomalyComponent, MapInitEvent>(OnTechMapInit);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<TechAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
}
private void OnTechMapInit(Entity<TechAnomalyComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextTimer = _timing.CurTime;
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View File

@@ -11,7 +11,6 @@ using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Shared.Antag;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
@@ -20,7 +19,6 @@ using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Whitelist;
using Robust.Server.Audio;
@@ -37,14 +35,14 @@ namespace Content.Server.Antag;
public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent>
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly GhostRoleSystem _ghostRole = default!;
[Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly LoadoutSystem _loadout = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
@@ -193,6 +191,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary>
/// Chooses antagonists from the given selection of players
/// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
{
if (ent.Comp.SelectionsComplete)
@@ -209,8 +210,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary>
/// Chooses antagonists from the given selection of players for the given antag definition.
/// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="def">The antagonist selection parameters and criteria</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def, bool midround = false)
public void ChooseAntags(Entity<AntagSelectionComponent> ent,
IList<ICommonSession> pool,
AntagSelectionDefinition def,
bool midround = false)
{
var playerPool = GetPlayerPool(ent, pool, def);
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
@@ -331,7 +338,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
EntityManager.AddComponents(player, def.Components);
// Equip the entity's RoleLoadout and LoadoutGroup
List<ProtoId<StartingGearPrototype>>? gear = new();
List<ProtoId<StartingGearPrototype>> gear = new();
if (def.StartingGear is not null)
gear.Add(def.StartingGear.Value);
@@ -340,8 +347,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
if (session != null)
{
var curMind = session.GetMind();
if (curMind == null ||
if (curMind == null ||
!TryComp<MindComponent>(curMind.Value, out var mindComp) ||
mindComp.OwnedEntity != antagEnt)
{
@@ -350,7 +357,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
}
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
_role.MindAddRoles(curMind.Value, def.MindComponents, null, true);
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing);
}

View File

@@ -3,7 +3,6 @@ using Content.Shared.Antag;
using Content.Shared.Destructible.Thresholds;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Player;
@@ -145,10 +144,17 @@ public partial struct AntagSelectionDefinition()
/// <summary>
/// Components added to the player's mind.
/// Do NOT use this to add role-type components. Add those as MindRoles instead
/// </summary>
[DataField]
public ComponentRegistry MindComponents = new();
/// <summary>
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
/// <summary>
/// A set of starting gear that's equipped to the player.
/// </summary>

View File

@@ -239,7 +239,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
Loc.GetString(
"earlyleave-cryo-announcement",
("character", name),
("entity", ent.Owner),
("entity", ent.Owner), // gender things for supporting downstreams with other languages
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"),
playDefaultSound: false

View File

@@ -420,6 +420,13 @@ public sealed partial class CargoSystem
return false;
_nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
var newBounty = new CargoBountyData(bounty, randomVal);
// This bounty id already exists! Probably because NameIdentifierSystem ran out of ids.
if (component.Bounties.Any(b => b.Id == newBounty.Id))
{
Log.Error("Failed to add bounty {ID} because another one with the same ID already existed!", newBounty.Id);
return false;
}
component.Bounties.Add(new CargoBountyData(bounty, randomVal));
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}");
component.TotalBounties++;

View File

@@ -231,7 +231,7 @@ namespace Content.Server.Cloning
// TODO: Ideally, components like this should be components on the mind entity so this isn't necessary.
// Add on special job components to the mob.
if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype))
if (_jobs.MindTryGetJob(mindEnt, out var prototype))
{
foreach (var special in prototype.Special)
{

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Destructible.Thresholds.Behaviors;
[DataDefinition]
public sealed partial class TimerStartBehavior : IThresholdBehavior
{
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
system.TriggerSystem.StartTimer(owner, cause);
}
}

View File

@@ -1,49 +1,45 @@
using System.Linq;
using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Localizations;
using Robust.Shared.Prototypes;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
public sealed partial class JobCondition : EntityEffectCondition
{
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
public override bool Condition(EntityEffectBaseArgs args)
{
{
args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
if (mindContainer != null && mindContainer.Mind != null)
if ( mindContainer is null
|| !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
return false;
foreach (var roleId in mind.MindRoles)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
{
foreach (var jobId in Job)
{
if (prototype.ID == jobId)
{
return true;
}
}
}
if(!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
continue;
if(!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole)
|| mindRole.JobPrototype is null)
continue;
if (Job.Contains(mindRole.JobPrototype.Value))
return true;
}
return false;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
}
}

View File

@@ -0,0 +1,15 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Explosion.Components
{
/// <summary>
/// Sends a trigger when signal is received.
/// </summary>
[RegisterComponent]
public sealed partial class TimerStartOnSignalComponent : Component
{
[DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string Port = "Timer";
}
}

View File

@@ -488,9 +488,12 @@ public sealed partial class ExplosionSystem
&& physics.BodyType == BodyType.Dynamic)
{
var pos = _transformSystem.GetWorldPosition(xform);
var dir = pos - epicenter.Position;
if (dir.IsLengthZero())
dir = _robustRandom.NextVector2().Normalized();
_throwingSystem.TryThrow(
uid,
pos - epicenter.Position,
dir,
physics,
xform,
_projectileQuery,

View File

@@ -11,6 +11,9 @@ namespace Content.Server.Explosion.EntitySystems
{
SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
SubscribeLocalEvent<TimerStartOnSignalComponent,SignalReceivedEvent>(OnTimerSignalReceived);
SubscribeLocalEvent<TimerStartOnSignalComponent,ComponentInit>(OnTimerSignalInit);
}
private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
@@ -24,5 +27,17 @@ namespace Content.Server.Explosion.EntitySystems
{
_signalSystem.EnsureSinkPorts(uid, component.Port);
}
private void OnTimerSignalReceived(EntityUid uid, TimerStartOnSignalComponent component, ref SignalReceivedEvent args)
{
if (args.Port != component.Port)
return;
StartTimer(uid, args.Trigger);
}
private void OnTimerSignalInit(EntityUid uid, TimerStartOnSignalComponent component, ComponentInit args)
{
_signalSystem.EnsureSinkPorts(uid, component.Port);
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.Discord;
using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Maps;
using Content.Server.Roles;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.GameTicking;
@@ -26,6 +27,7 @@ namespace Content.Server.GameTicking
public sealed partial class GameTicker
{
[Dependency] private readonly DiscordWebhook _discord = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
private static readonly Counter RoundNumberMetric = Metrics.CreateCounter(
@@ -339,8 +341,23 @@ namespace Content.Server.GameTicking
RunLevel = GameRunLevel.PostRound;
ShowRoundEndScoreboard(text);
SendRoundEndDiscordMessage();
try
{
ShowRoundEndScoreboard(text);
}
catch (Exception e)
{
Log.Error($"Error while showing round end scoreboard: {e}");
}
try
{
SendRoundEndDiscordMessage();
}
catch (Exception e)
{
Log.Error($"Error while sending round end Discord message: {e}");
}
}
public void ShowRoundEndScoreboard(string text = "")
@@ -373,7 +390,7 @@ namespace Content.Server.GameTicking
var userId = mind.UserId ?? mind.OriginalOwnerUserId;
var connected = false;
var observer = HasComp<ObserverRoleComponent>(mindId);
var observer = _role.MindHasRole<ObserverRoleComponent>(mindId);
// Continuing
if (userId != null && _playerManager.ValidSessionId(userId.Value))
{
@@ -400,7 +417,7 @@ namespace Content.Server.GameTicking
_pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true);
}
var roles = _roles.MindGetAllRoles(mindId);
var roles = _roles.MindGetAllRoleInfo(mindId);
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
{

View File

@@ -3,8 +3,6 @@ using System.Linq;
using System.Numerics;
using Content.Server.Administration.Managers;
using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Shuttles.Components;
using Content.Server.Spawners.Components;
using Content.Server.Speech.Components;
using Content.Server.Station.Components;
@@ -224,13 +222,12 @@ namespace Content.Server.GameTicking
_mind.SetUserId(newMind, data.UserId);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
var job = new JobComponent {Prototype = jobId};
_roles.MindAddRole(newMind, job, silent: silent);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player);
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character);
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character);
DebugTools.AssertNotNull(mobMaybe);
var mob = mobMaybe!.Value;
@@ -271,13 +268,17 @@ namespace Content.Server.GameTicking
_stationJobs.TryAssignJob(station, jobPrototype, player.UserId);
if (lateJoin)
{
_adminLogger.Add(LogType.LateJoin,
LogImpact.Medium,
$"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
}
else
{
_adminLogger.Add(LogType.RoundStartJoin,
LogImpact.Medium,
$"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
}
// Make sure they're aware of extended access.
if (Comp<StationJobsComponent>(station).ExtendedAccess
@@ -363,7 +364,7 @@ namespace Content.Server.GameTicking
var (mindId, mindComp) = _mind.CreateMind(player.UserId, name);
mind = (mindId, mindComp);
_mind.SetUserId(mind.Value, player.UserId);
_roles.MindAddRole(mind.Value, new ObserverRoleComponent());
_roles.MindAddRole(mind.Value, "MindRoleObserver");
}
var ghost = _ghost.SpawnGhost(mind.Value);

View File

@@ -9,7 +9,6 @@ using Content.Server.RoundEnd;
using Content.Server.Shuttles.Events;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs;
@@ -31,9 +30,9 @@ namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly StoreSystem _store = default!;
@@ -57,6 +56,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeOperativeComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified);
SubscribeLocalEvent<NukeopsRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<ConsoleFTLAttemptEvent>(OnShuttleFTLAttempt);
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt);
@@ -65,7 +66,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeopsRuleComponent, RuleLoadedGridsEvent>(OnRuleLoadedGrids);
}
protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
protected override void Started(EntityUid uid,
NukeopsRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
@@ -85,7 +88,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
}
#region Event Handlers
protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
NukeopsRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}");
@@ -227,7 +232,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// If the disk is currently at Central Command, the crew wins - just slightly.
// This also implies that some nuclear operatives have died.
SetWinType(ent, diskAtCentCom
SetWinType(ent,
diskAtCentCom
? WinType.CrewMinor
: WinType.OpsMinor);
ent.Comp.WinConditions.Add(diskAtCentCom
@@ -456,8 +462,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
: WinCondition.AllNukiesDead);
SetWinType(ent, WinType.CrewMajor, false);
_roundEndSystem.DoRoundEndBehavior(
nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement);
_roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior,
nukeops.EvacShuttleTime,
nukeops.RoundEndTextSender,
nukeops.RoundEndTextShuttleCall,
nukeops.RoundEndTextAnnouncement);
// prevent it called multiple times
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
@@ -465,16 +474,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
if (ent.Comp.TargetStation is not { } station)
return;
var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target";
_antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome",
("station", station),
_antag.SendBriefing(args.Session,
Loc.GetString("nukeops-welcome",
("station", target),
("name", Name(ent))),
Color.Red,
ent.Comp.GreetSoundNotification);
}
private void OnGetBriefing(Entity<NukeopsRoleComponent> role, ref GetBriefingEvent args)
{
// TODO Different character screen briefing for the 3 nukie types
args.Append(Loc.GetString("nukeops-briefing"));
}
/// <remarks>
/// Is this method the shitty glue holding together the last of my sanity? yes.
/// Do i have a better solution? not presently.

View File

@@ -15,7 +15,6 @@ using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs;
@@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules;
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
{
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly EuiManager _euiMan = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
@@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly IGameTiming _timing = default!;
//Used in OnPostFlash, no reference to the rule component is available
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
@@ -59,9 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
{
base.Initialize();
SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
}
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
@@ -85,7 +87,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
}
}
protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
RevolutionaryRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
base.AppendRoundEndText(uid, component, gameRule, ref args);
@@ -101,7 +105,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count)));
foreach (var (mind, data, name) in sessionData)
{
var count = CompOrNull<RevolutionaryRoleComponent>(mind)?.ConvertedCount ?? 0;
_role.MindHasRole<RevolutionaryRoleComponent>(mind, out var role);
var count = CompOrNull<RevolutionaryRoleComponent>(role)?.ConvertedCount ?? 0;
args.AddLine(Loc.GetString("rev-headrev-name-user",
("name", name),
("username", data.UserName),
@@ -113,10 +119,8 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
var head = HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity);
var ent = args.Mind.Comp.OwnedEntity;
var head = HasComp<HeadRevolutionaryComponent>(ent);
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
}
@@ -145,15 +149,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
if (ev.User != null)
{
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
_adminLogManager.Add(LogType.Mind,
LogImpact.Medium,
$"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
if (_mind.TryGetRole<RevolutionaryRoleComponent>(ev.User.Value, out var headrev))
headrev.ConvertedCount++;
if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
{
if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out _, out var role))
role.Value.Comp.ConvertedCount++;
}
}
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
{
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId });
_role.MindAddRole(mindId, "MindRoleRevolutionary");
}
if (mind?.Session != null)

View File

@@ -1,18 +1,12 @@
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Roles;
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Robust.Shared.Random;
namespace Content.Server.GameTicking.Rules;
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
{
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
public override void Initialize()
@@ -24,32 +18,33 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
}
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
// Greeting upon thief activation
private void AfterAntagSelected(Entity<ThiefRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{
if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind))
return;
//Generate objectives
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
var ent = args.EntityUid;
_antag.SendBriefing(ent, MakeBriefing(ent), null, null);
}
//Add mind briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args)
// Character screen briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(thief.Owner, out var mind) || mind.OwnedEntity == null)
return;
var ent = args.Mind.Comp.OwnedEntity;
args.Append(MakeBriefing(mind.OwnedEntity.Value));
if (ent is null)
return;
args.Append(MakeBriefing(ent.Value));
}
private string MakeBriefing(EntityUid thief)
private string MakeBriefing(EntityUid ent)
{
var isHuman = HasComp<HumanoidAppearanceComponent>(thief);
var isHuman = HasComp<HumanoidAppearanceComponent>(ent);
var briefing = isHuman
? Loc.GetString("thief-role-greeting-human")
: Loc.GetString("thief-role-greeting-animal");
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
if (isHuman)
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
return briefing;
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Administration.Logs;
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
@@ -5,12 +6,11 @@ using Content.Server.Objectives;
using Content.Server.PDA.Ringer;
using Content.Server.Roles;
using Content.Server.Traitor.Uplink;
using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.PDA;
using Content.Shared.Radio;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Roles.RoleCodeword;
@@ -25,29 +25,29 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{
private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b");
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
}
protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
SetCodewords(component);
SetCodewords(component, args.RuleEntity);
}
private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
@@ -55,9 +55,10 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
MakeTraitor(args.EntityUid, ent);
}
private void SetCodewords(TraitorRuleComponent component)
private void SetCodewords(TraitorRuleComponent component, EntityUid ruleEntity)
{
component.Codewords = GenerateTraitorCodewords(component);
_adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for game rule {ToPrettyString(ruleEntity)}: {string.Join(", ", component.Codewords)}");
}
public string[] GenerateTraitorCodewords(TraitorRuleComponent component)
@@ -76,7 +77,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
{
//Grab the mind if it wasnt provided
//Grab the mind if it wasn't provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
return false;
@@ -88,7 +89,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{
// Calculate the amount of currency on the uplink.
var startingBalance = component.StartingBalance;
if (_jobs.MindTryGetJob(mindId, out _, out var prototype))
if (_jobs.MindTryGetJob(mindId, out var prototype))
startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
// creadth: we need to create uplink for the antag.
@@ -107,14 +108,18 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
component.TraitorMinds.Add(mindId);
// Assign briefing
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
//Since this provides neither an antag/job prototype, nor antag status/roletype,
//and is intrinsically related to the traitor role
//it does not need to be a separate Mind Role Entity
_roleSystem.MindHasRole<TraitorRoleComponent>(mindId, out var traitorRole);
if (traitorRole is not null)
{
Briefing = briefing
}, mind, true);
AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
}
// Send codewords to only the traitor client
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found

View File

@@ -13,6 +13,7 @@ using Content.Shared.Mind;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Roles;
using Content.Shared.Zombies;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@@ -22,15 +23,16 @@ namespace Content.Server.GameTicking.Rules;
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
public override void Initialize()
{
@@ -41,23 +43,20 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
}
private void OnGetBriefing(EntityUid uid, InitialInfectedRoleComponent component, ref GetBriefingEvent args)
private void OnGetBriefing(Entity<InitialInfectedRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
if (HasComp<ZombieRoleComponent>(uid)) // don't show both briefings
return;
args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
if (!_roles.MindHasRole<ZombieRoleComponent>(args.Mind.Owner))
args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
}
private void OnGetBriefing(EntityUid uid, ZombieRoleComponent component, ref GetBriefingEvent args)
private void OnGetBriefing(Entity<ZombieRoleComponent> role, ref GetBriefingEvent args)
{
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
args.Append(Loc.GetString("zombie-infection-greeting"));
}
protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule,
protected override void AppendRoundEndText(EntityUid uid,
ZombieRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
base.AppendRoundEndText(uid, component, gameRule, ref args);

View File

@@ -1,11 +1,13 @@
namespace Content.Server.Ghost.Roles;
using Content.Shared.Roles;
namespace Content.Server.Ghost.Roles;
/// <summary>
/// This is used for round end display of ghost roles.
/// It may also be used to ensure some ghost roles count as antagonists in future.
/// </summary>
[RegisterComponent]
public sealed partial class GhostRoleMarkerRoleComponent : Component
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
{
[DataField("name")] public string? Name;
}

View File

@@ -71,18 +71,22 @@ public sealed class GhostRoleSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage);
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
@@ -509,7 +513,11 @@ public sealed class GhostRoleSystem : EntitySystem
var newMind = _mindSystem.CreateMind(player.UserId,
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
_roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName });
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind, out _, out var markerRole))
markerRole.Value.Comp.Name = role.RoleName;
_mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob);
@@ -602,10 +610,7 @@ public sealed class GhostRoleSystem : EntitySystem
if (ghostRole.JobProto != null)
{
if (HasComp<JobComponent>(args.Mind))
_roleSystem.MindRemoveRole<JobComponent>(args.Mind);
_roleSystem.MindAddRole(args.Mind, new JobComponent { Prototype = ghostRole.JobProto });
_roleSystem.MindAddJobRole(args.Mind, args.Mind, silent:false,ghostRole.JobProto);
}
ghostRole.Taken = true;

View File

@@ -166,7 +166,7 @@ public sealed class IdentitySystem : SharedIdentitySystem
if (_idCard.TryFindIdCard(target, out var id))
{
presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
presumedJob = id.Comp.JobTitle?.ToLowerInvariant();
presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
}
// If it didn't find a job, that's fine.

View File

@@ -5,6 +5,7 @@ using Content.Server.Stack;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Destructible;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Kitchen;
@@ -122,6 +123,9 @@ namespace Content.Server.Kitchen.EntitySystems
if (solution.Volume > containerSolution.AvailableVolume)
continue;
var dev = new DestructionEventArgs();
RaiseLocalEvent(item, dev);
QueueDel(item);
}

View File

@@ -175,6 +175,10 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
var materialPerStack = composition.MaterialComposition[materialProto.ID];
var amountToSpawn = amount / materialPerStack;
overflowMaterial = amount - amountToSpawn * materialPerStack;
if (amountToSpawn == 0)
return new List<EntityUid>();
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
}

View File

@@ -363,8 +363,8 @@ public sealed class SuitSensorSystem : EntitySystem
{
if (card.Comp.FullName != null)
userName = card.Comp.FullName;
if (card.Comp.JobTitle != null)
userJob = card.Comp.JobTitle;
if (card.Comp.LocalizedJobTitle != null)
userJob = card.Comp.LocalizedJobTitle;
userJobIcon = card.Comp.JobIcon;
foreach (var department in card.Comp.JobDepartments)

View File

@@ -43,7 +43,7 @@ namespace Content.Server.Mind.Commands
builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity);
var roles = _entities.System<SharedRoleSystem>();
foreach (var role in roles.MindGetAllRoles(mindId))
foreach (var role in roles.MindGetAllRoleInfo(mindId))
{
builder.AppendFormat("{0} ", role.Name);
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Movement.Systems;
namespace Content.Server.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem;

View File

@@ -14,6 +14,7 @@ public sealed class NameIdentifierSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly ILogManager _logManager = default!;
/// <summary>
/// Free IDs available per <see cref="NameIdentifierGroupPrototype"/>.
@@ -118,23 +119,39 @@ public sealed class NameIdentifierSystem : EntitySystem
private void InitialSetupPrototypes()
{
foreach (var proto in _prototypeManager.EnumeratePrototypes<NameIdentifierGroupPrototype>())
{
AddGroup(proto);
}
EnsureIds();
}
private void AddGroup(NameIdentifierGroupPrototype proto)
private void FillGroup(NameIdentifierGroupPrototype proto, List<int> values)
{
var values = new List<int>(proto.MaxValue - proto.MinValue);
values.Clear();
for (var i = proto.MinValue; i < proto.MaxValue; i++)
{
values.Add(i);
}
_robustRandom.Shuffle(values);
CurrentIds.Add(proto.ID, values);
}
private List<int> GetOrCreateIdList(NameIdentifierGroupPrototype proto)
{
if (!CurrentIds.TryGetValue(proto.ID, out var ids))
{
ids = new List<int>(proto.MaxValue - proto.MinValue);
CurrentIds.Add(proto.ID, ids);
}
return ids;
}
private void EnsureIds()
{
foreach (var proto in _prototypeManager.EnumeratePrototypes<NameIdentifierGroupPrototype>())
{
var ids = GetOrCreateIdList(proto);
FillGroup(proto, ids);
}
}
private void OnReloadPrototypes(PrototypesReloadedEventArgs ev)
@@ -159,19 +176,20 @@ public sealed class NameIdentifierSystem : EntitySystem
foreach (var proto in set.Modified.Values)
{
var name_proto = (NameIdentifierGroupPrototype) proto;
// Only bother adding new ones.
if (CurrentIds.ContainsKey(proto.ID))
continue;
AddGroup((NameIdentifierGroupPrototype) proto);
var ids = GetOrCreateIdList(name_proto);
FillGroup(name_proto, ids);
}
}
private void CleanupIds(RoundRestartCleanupEvent ev)
{
foreach (var values in CurrentIds.Values)
{
_robustRandom.Shuffle(values);
}
EnsureIds();
}
}

View File

@@ -33,6 +33,7 @@ using System.Linq;
using Content.Shared.Containers.ItemSlots;
using Robust.Server.GameObjects;
using Content.Shared.Whitelist;
using Content.Shared.Destructible;
namespace Content.Server.Nutrition.EntitySystems;
@@ -335,6 +336,9 @@ public sealed class FoodSystem : EntitySystem
if (ev.Cancelled)
return;
var dev = new DestructionEventArgs();
RaiseLocalEvent(food, dev);
if (component.Trash.Count == 0)
{
QueueDel(food);

View File

@@ -89,7 +89,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
foreach (var mind in allHumans)
{
// RequireAdminNotify used as a cheap way to check for command department
if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
if (_job.MindTryGetJob(mind, out var prototype) && prototype.RequireAdminNotify)
allHeads.Add(mind);
}

View File

@@ -1,9 +1,10 @@
using Content.Server.Objectives.Components;
using Content.Server.Roles;
using Content.Server.Warps;
using Content.Shared.Objectives.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Roles;
using Robust.Shared.Random;
using Content.Server.Roles;
namespace Content.Server.Objectives.Systems;
@@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly NumberObjectiveSystem _number = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize()
{
@@ -46,10 +48,8 @@ public sealed class NinjaConditionsSystem : EntitySystem
// spider charge
private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args)
{
if (args.Cancelled || !HasComp<NinjaRoleComponent>(args.MindId))
{
if (args.Cancelled || !_roles.MindHasRole<NinjaRoleComponent>(args.MindId))
return;
}
// choose spider charge detonation point
var warps = new List<EntityUid>();

View File

@@ -21,7 +21,7 @@ public sealed class NotCommandRequirementSystem : EntitySystem
return;
// cheap equivalent to checking that job department is command, since all command members require admin notification when leaving
if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify)
if (_job.MindTryGetJob(args.MindId, out var prototype) && prototype.RequireAdminNotify)
args.Cancelled = true;
}
}

View File

@@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems;
/// </summary>
public sealed class NotJobRequirementSystem : EntitySystem
{
[Dependency] private readonly SharedJobSystem _jobs = default!;
public override void Initialize()
{
base.Initialize();
@@ -20,11 +22,10 @@ public sealed class NotJobRequirementSystem : EntitySystem
if (args.Cancelled)
return;
// if player has no job then don't care
if (!TryComp<JobComponent>(args.MindId, out var job))
return;
_jobs.MindTryGetJob(args.MindId, out var proto);
if (job.Prototype == comp.Job)
// if player has no job then don't care
if (proto is not null && proto.ID == comp.Job)
args.Cancelled = true;
}
}

View File

@@ -22,8 +22,6 @@ public sealed class RoleRequirementSystem : EntitySystem
if (args.Cancelled)
return;
// this whitelist trick only works because roles are components on the mind and not entities
// if that gets reworked then this will need changing
if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId))
args.Cancelled = true;
}

View File

@@ -192,7 +192,7 @@ namespace Content.Server.PDA
{
ActualOwnerName = pda.OwnerName,
IdOwner = id?.FullName,
JobTitle = id?.JobTitle,
JobTitle = id?.LocalizedJobTitle,
StationAlertLevel = pda.StationAlertLevel,
StationAlertColor = pda.StationAlertColor
},

View File

@@ -30,14 +30,15 @@ namespace Content.Server.Players.PlayTimeTracking;
/// </summary>
public sealed class PlayTimeTrackingSystem : EntitySystem
{
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IAfkManager _afk = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
public override void Initialize()
{
@@ -101,10 +102,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
public IEnumerable<string> GetTimedRoles(EntityUid mindId)
{
var ev = new MindGetAllRolesEvent(new List<RoleInfo>());
RaiseLocalEvent(mindId, ref ev);
foreach (var role in ev.Roles)
foreach (var role in _roles.MindGetAllRoleInfo(mindId))
{
if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId))
continue;

View File

@@ -10,3 +10,5 @@ public sealed partial class CommandStaffComponent : Component
{
}
//TODO this should probably be on a mind role, not the mob

View File

@@ -4,9 +4,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Role used to keep track of space dragons for antag purposes.
/// Added to mind role entities to tag that they are a space dragon.
/// </summary>
[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist]
public sealed partial class DragonRoleComponent : AntagonistRoleComponent
[RegisterComponent, Access(typeof(DragonSystem))]
public sealed partial class DragonRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,8 +2,11 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are an initial infected.
/// </summary>
[RegisterComponent]
public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -30,7 +30,7 @@ public sealed class JobSystem : SharedJobSystem
if (!_mind.TryGetSession(mindId, out var session))
return;
if (!MindTryGetJob(mindId, out _, out var prototype))
if (!MindTryGetJob(mindId, out var prototype))
return;
_chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name",
@@ -47,6 +47,6 @@ public sealed class JobSystem : SharedJobSystem
if (MindHasJobWithId(mindId, jobPrototypeId))
return;
_roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId });
_roles.MindAddJobRole(mindId, null, false, jobPrototypeId);
}
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a space ninja.
/// </summary>
[RegisterComponent]
public sealed partial class NinjaRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -3,9 +3,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind entities to tag that they are a nuke operative.
/// Added to mind role entities to tag that they are a nuke operative.
/// </summary>
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent
[RegisterComponent]
public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -45,7 +45,7 @@ namespace Content.Server.Roles
var roles = _entityManager.System<SharedRoleSystem>();
var jobs = _entityManager.System<SharedJobSystem>();
if (jobs.MindHasJobWithId(mind, args[1]))
roles.MindRemoveRole<JobComponent>(mind.Value);
roles.MindTryRemoveRole<JobRoleComponent>(mind.Value);
}
}
}

View File

@@ -3,10 +3,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind entities to tag that they are a Revolutionary.
/// Added to mind role entities to tag that they are a Revolutionary.
/// </summary>
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent
[RegisterComponent]
public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent
{
/// <summary>
/// For headrevs, how many people you have converted.

View File

@@ -1,32 +1,40 @@
using Content.Shared.Mind;
using Content.Shared.Roles;
namespace Content.Server.Roles;
public sealed class RoleSystem : SharedRoleSystem
{
public override void Initialize()
{
// TODO make roles entities
base.Initialize();
SubscribeAntagEvents<DragonRoleComponent>();
SubscribeAntagEvents<InitialInfectedRoleComponent>();
SubscribeAntagEvents<NinjaRoleComponent>();
SubscribeAntagEvents<NukeopsRoleComponent>();
SubscribeAntagEvents<RevolutionaryRoleComponent>();
SubscribeAntagEvents<SubvertedSiliconRoleComponent>();
SubscribeAntagEvents<TraitorRoleComponent>();
SubscribeAntagEvents<ZombieRoleComponent>();
SubscribeAntagEvents<ThiefRoleComponent>();
}
public string? MindGetBriefing(EntityUid? mindId)
{
if (mindId == null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null;
}
TryComp<MindComponent>(mindId.Value, out var mindComp);
if (mindComp is null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null;
}
var ev = new GetBriefingEvent();
RaiseLocalEvent(mindId.Value, ref ev);
// This is on the event because while this Entity<T> is also present on every Mind Role Entity's MindRoleComp
// getting to there from a GetBriefing event subscription can be somewhat boilerplate
// and this needs to be looked up for the event anyway so why calculate it again later
ev.Mind = (mindId.Value, mindComp);
// Briefing is no longer raised on the mind entity itself
// because all the components that briefings subscribe to should be on Mind Role Entities
foreach(var role in mindComp.MindRoles)
{
RaiseLocalEvent(role, ref ev);
}
return ev.Briefing;
}
}
@@ -38,8 +46,16 @@ public sealed class RoleSystem : SharedRoleSystem
[ByRefEvent]
public sealed class GetBriefingEvent
{
/// <summary>
/// The text that will be shown on the Character Screen
/// </summary>
public string? Briefing;
/// <summary>
/// The Mind to whose Mind Role Entities the briefing is sent to
/// </summary>
public Entity<MindComponent> Mind;
public GetBriefingEvent(string? briefing = null)
{
Briefing = briefing;

View File

@@ -2,7 +2,10 @@
namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a hacked borg.
/// </summary>
[RegisterComponent]
public sealed partial class SubvertedSiliconRoleComponent : AntagonistRoleComponent
public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a thief.
/// </summary>
[RegisterComponent]
public sealed partial class ThiefRoleComponent : AntagonistRoleComponent
public sealed partial class ThiefRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class TraitorRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a syndicate traitor.
/// </summary>
[RegisterComponent]
public sealed partial class TraitorRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist]
public sealed partial class ZombieRoleComponent : AntagonistRoleComponent
/// <summary>
/// Added to mind role entities to tag that they are a zombie.
/// </summary>
[RegisterComponent]
public sealed partial class ZombieRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -124,6 +124,9 @@ public sealed partial class ShuttleConsoleSystem
if (!TryComp(shuttleUid, out ShuttleComponent? shuttleComp))
return;
if (shuttleComp.Enabled == false)
return;
// Check shuttle can even FTL
if (!_shuttle.CanFTL(shuttleUid.Value, out var reason))
{

View File

@@ -244,18 +244,28 @@ public sealed partial class ShuttleSystem
/// </summary>
public bool CanFTL(EntityUid shuttleUid, [NotNullWhen(false)] out string? reason)
{
// Currently in FTL already
if (HasComp<FTLComponent>(shuttleUid))
{
reason = Loc.GetString("shuttle-console-in-ftl");
return false;
}
if (FTLMassLimit > 0 &&
TryComp(shuttleUid, out PhysicsComponent? shuttlePhysics) &&
shuttlePhysics.Mass > FTLMassLimit)
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttlePhysics))
{
reason = Loc.GetString("shuttle-console-mass");
return false;
// Static physics type is set when station anchor is enabled
if (shuttlePhysics.BodyType == BodyType.Static)
{
reason = Loc.GetString("shuttle-console-static");
return false;
}
// Too large to FTL
if (FTLMassLimit > 0 && shuttlePhysics.Mass > FTLMassLimit)
{
reason = Loc.GetString("shuttle-console-mass");
return false;
}
}
if (HasComp<PreventPilotComponent>(shuttleUid))

View File

@@ -28,12 +28,12 @@ namespace Content.Server.Silicons.Laws;
public sealed class SiliconLawSystem : SharedSiliconLawSystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
/// <inheritdoc/>
public override void Initialize()
@@ -178,10 +178,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _))
return;
if (_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
return;
_roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole });
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
}
public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null)

View File

@@ -12,10 +12,10 @@ namespace Content.Server.Spawners.EntitySystems;
public sealed class ContainerSpawnPointSystem : EntitySystem
{
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
@@ -32,7 +32,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
// If it's just a spawn pref check if it's for cryo (silly).
if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep &&
(!_proto.TryIndex(args.Job?.Prototype, out var jobProto) || jobProto.JobEntity == null))
(!_proto.TryIndex(args.Job, out var jobProto) || jobProto.JobEntity == null))
{
return;
}
@@ -49,7 +49,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (spawnPoint.SpawnType == SpawnPointType.Unset)
{
// make sure we also check the job here for various reasons.
if (spawnPoint.Job == null || spawnPoint.Job == args.Job?.Prototype)
if (spawnPoint.Job == null || spawnPoint.Job == args.Job)
possibleContainers.Add((uid, spawnPoint, container, xform));
continue;
}
@@ -61,7 +61,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job &&
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
(args.Job == null || spawnPoint.Job == args.Job))
{
possibleContainers.Add((uid, spawnPoint, container, xform));
}

View File

@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job &&
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
(args.Job == null || spawnPoint.Job == args.Job))
{
possiblePositions.Add(xform.Coordinates);
}

View File

@@ -18,13 +18,10 @@ using Content.Shared.Humanoid.Prototypes;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Content.Shared.StatusIcon;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
@@ -42,19 +39,19 @@ namespace Content.Server.Station.Systems;
[PublicAPI]
public sealed class StationSpawningSystem : SharedStationSpawningSystem
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
[Dependency] private readonly ActorSystem _actors = default!;
[Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!;
[Dependency] private readonly CP14ExpeditionSystem _CP14expedition = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly PdaSystem _pdaSystem = default!;
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private bool _randomizeCharacters;
@@ -77,7 +74,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <remarks>
/// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable.
/// </remarks>
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, JobComponent? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, ProtoId<JobPrototype>? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
{
if (station != null && !Resolve(station.Value, ref stationSpawning))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
@@ -105,12 +102,12 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <returns>The spawned entity</returns>
public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates,
JobComponent? job,
ProtoId<JobPrototype>? job,
HumanoidCharacterProfile? profile,
EntityUid? station,
EntityUid? entity = null)
{
_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype);
_prototypeManager.TryIndex(job ?? string.Empty, out var prototype);
RoleLoadout? loadout = null;
// Need to get the loadout up-front to handle names if we use an entity spawn override.
@@ -204,9 +201,9 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
return entity.Value;
}
private void DoJobSpecials(JobComponent? job, EntityUid entity)
private void DoJobSpecials(ProtoId<JobPrototype>? job, EntityUid entity)
{
if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype))
if (!_prototypeManager.TryIndex(job ?? string.Empty, out JobPrototype? prototype))
return;
foreach (var jobSpecial in prototype.Special)
@@ -273,7 +270,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// <summary>
/// The job to use, if any.
/// </summary>
public readonly JobComponent? Job;
public readonly ProtoId<JobPrototype>? Job;
/// <summary>
/// The profile to use, if any.
/// </summary>
@@ -283,7 +280,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// </summary>
public readonly EntityUid? Station;
public PlayerSpawningEvent(JobComponent? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
public PlayerSpawningEvent(ProtoId<JobPrototype>? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
{
Job = job;
HumanoidCharacterProfile = humanoidCharacterProfile;

View File

@@ -33,16 +33,16 @@ public sealed partial class BuyerAntagCondition : ListingCondition
return true;
var roleSystem = ent.System<SharedRoleSystem>();
var roles = roleSystem.MindGetAllRoles(mindId);
var roles = roleSystem.MindGetAllRoleInfo(mindId);
if (Blacklist != null)
{
foreach (var role in roles)
{
if (role.Component is not AntagonistRoleComponent blacklistantag)
if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue;
if (blacklistantag.PrototypeId != null && Blacklist.Contains(blacklistantag.PrototypeId))
if (Blacklist.Contains(role.Prototype))
return false;
}
}
@@ -52,10 +52,11 @@ public sealed partial class BuyerAntagCondition : ListingCondition
var found = false;
foreach (var role in roles)
{
if (role.Component is not AntagonistRoleComponent antag)
if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue;
if (antag.PrototypeId != null && Whitelist.Contains(antag.PrototypeId))
if (Whitelist.Contains(role.Prototype))
found = true;
}
if (!found)

View File

@@ -37,13 +37,13 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
return true;
var jobs = ent.System<SharedJobSystem>();
jobs.MindTryGetJob(mindId, out var job, out _);
jobs.MindTryGetJob(mindId, out var job);
if (Blacklist != null && job?.Prototype != null)
if (Blacklist != null && job != null)
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID))
if (department.Roles.Contains(job.ID) && Blacklist.Contains(department.ID))
return false;
}
}
@@ -52,11 +52,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
{
var found = false;
if (job?.Prototype != null)
if (job != null)
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID))
if (department.Roles.Contains(job.ID) && Whitelist.Contains(department.ID))
{
found = true;
break;

View File

@@ -34,17 +34,17 @@ public sealed partial class BuyerJobCondition : ListingCondition
return true;
var jobs = ent.System<SharedJobSystem>();
jobs.MindTryGetJob(mindId, out var job, out _);
jobs.MindTryGetJob(mindId, out var job);
if (Blacklist != null)
{
if (job?.Prototype != null && Blacklist.Contains(job.Prototype))
if (job is not null && Blacklist.Contains(job.ID))
return false;
}
if (Whitelist != null)
{
if (job?.Prototype == null || !Whitelist.Contains(job.Prototype))
if (job == null || !Whitelist.Contains(job.ID))
return false;
}

View File

@@ -6,6 +6,7 @@ using Content.Shared.Examine;
using Content.Shared.Foldable;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Roles;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Thief.Systems;
@@ -18,7 +19,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize()
{
base.Initialize();
@@ -37,7 +38,7 @@ public sealed class ThiefBeaconSystem : EntitySystem
return;
var mind = _mind.GetMind(args.User);
if (!HasComp<ThiefRoleComponent>(mind))
if (mind == null || !_roles.MindHasRole<ThiefRoleComponent>(mind.Value))
return;
var user = args.User;

View File

@@ -11,7 +11,6 @@ using Content.Server.Mind.Commands;
using Content.Server.NPC;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Server.Speech.Components;
using Content.Server.Temperature.Components;
using Content.Shared.CombatMode;
@@ -47,18 +46,18 @@ namespace Content.Server.Zombies;
/// </remarks>
public sealed partial class ZombieSystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly NpcFactionSystem _faction = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
/// <summary>
/// Handles an entity turning into a zombie when they die or go into crit
@@ -234,7 +233,7 @@ public sealed partial class ZombieSystem
if (hasMind && _mind.TryGetSession(mindId, out var session))
{
//Zombie role for player manifest
_roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId });
_roles.MindAddRole(mindId, "MindRoleZombie", mind: null, silent: true);
//Greeting message for new bebe zombers
_chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting"));

View File

@@ -0,0 +1,216 @@
using Content.Server.Popups;
using Content.Server.Stack;
using Content.Shared._CP14.Currency;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server._CP14.Currency;
public sealed partial class CP14CurrencySystem : CP14SharedCurrencySystem
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14CurrencyComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<CP14CurrencyConverterComponent, ExaminedEvent>(OnConverterExamine);
SubscribeLocalEvent<CP14CurrencyConverterComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<CP14CurrencyConverterComponent, GetVerbsEvent<Verb>>(OnGetVerb);
}
private void OnExamine(Entity<CP14CurrencyComponent> currency, ref ExaminedEvent args)
{
var total = GetTotalCurrency(currency, currency.Comp);
var push = Loc.GetString("cp14-currency-examine-title");
push += GetCurrencyPrettyString(total);
args.PushMarkup(push);
}
private void OnConverterExamine(Entity<CP14CurrencyConverterComponent> ent, ref ExaminedEvent args)
{
var push = $"{Loc.GetString("cp14-currency-converter-examine-title")} {GetCurrencyPrettyString(ent.Comp.Balance)}";
args.PushMarkup(push);
}
private void OnInteractUsing(Entity<CP14CurrencyConverterComponent> ent, ref InteractUsingEvent args)
{
if (!TryComp<CP14CurrencyComponent>(args.Used, out var currency))
return;
if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.Used))
return;
var delta = GetTotalCurrency(args.Used);
ent.Comp.Balance += delta;
QueueDel(args.Used);
_popup.PopupEntity(Loc.GetString("cp14-currency-converter-insert", ("cash", delta)), ent, args.User);
_audio.PlayPvs(ent.Comp.InsertSound, ent, AudioParams.Default.WithMaxDistance(3));
}
private void OnGetVerb(Entity<CP14CurrencyConverterComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
var transform = Transform(ent);
var coord = transform.Coordinates.Offset(transform.LocalRotation.RotateVec(ent.Comp.SpawnOffset));
Verb copperVerb = new()
{
Text = Loc.GetString("cp14-currency-converter-get-cp"),
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/_CP14/Objects/Economy/cp_coin.rsi/coin10.png")),
Category = VerbCategory.CP14CurrencyConvert,
Priority = 1,
CloseMenu = false,
Act = () =>
{
if (ent.Comp.Balance < CP.Value)
return;
ent.Comp.Balance -= CP.Value;
var newEnt = Spawn(CP.Key, coord);
_stack.TryMergeToContacts(newEnt);
_audio.PlayPvs(ent.Comp.InsertSound, ent, AudioParams.Default.WithMaxDistance(3).WithPitchScale(0.9f));
},
};
args.Verbs.Add(copperVerb);
Verb silverVerb = new()
{
Text = Loc.GetString("cp14-currency-converter-get-sp"),
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/_CP14/Objects/Economy/sp_coin.rsi/coin10.png")),
Category = VerbCategory.CP14CurrencyConvert,
Priority = 2,
CloseMenu = false,
Act = () =>
{
if (ent.Comp.Balance < SP.Value)
return;
ent.Comp.Balance -= SP.Value;
var newEnt = Spawn(SP.Key, coord);
_stack.TryMergeToContacts(newEnt);
_audio.PlayPvs(ent.Comp.InsertSound, ent, AudioParams.Default.WithMaxDistance(3).WithPitchScale(1.1f));
},
};
args.Verbs.Add(silverVerb);
Verb goldVerb = new()
{
Text = Loc.GetString("cp14-currency-converter-get-gp"),
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/_CP14/Objects/Economy/gp_coin.rsi/coin10.png")),
Category = VerbCategory.CP14CurrencyConvert,
Priority = 3,
CloseMenu = false,
Act = () =>
{
if (ent.Comp.Balance < GP.Value)
return;
ent.Comp.Balance -= GP.Value;
var newEnt = Spawn(GP.Key, coord);
_stack.TryMergeToContacts(newEnt);
_audio.PlayPvs(ent.Comp.InsertSound, ent, AudioParams.Default.WithMaxDistance(3).WithPitchScale(1.3f));
},
};
args.Verbs.Add(goldVerb);
Verb platinumVerb = new()
{
Text = Loc.GetString("cp14-currency-converter-get-pp"),
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/_CP14/Objects/Economy/pp_coin.rsi/coin10.png")),
Category = VerbCategory.CP14CurrencyConvert,
Priority = 4,
CloseMenu = false,
Act = () =>
{
if (ent.Comp.Balance < PP.Value)
return;
ent.Comp.Balance -= PP.Value;
var newEnt = Spawn(PP.Key, coord);
_stack.TryMergeToContacts(newEnt);
_audio.PlayPvs(ent.Comp.InsertSound, ent, AudioParams.Default.WithMaxDistance(3).WithPitchScale(1.5f));
},
};
args.Verbs.Add(platinumVerb);
}
public HashSet<EntityUid> GenerateMoney(EntProtoId currencyType, int target, EntityCoordinates coordinates)
{
return GenerateMoney(currencyType, target, coordinates, out _);
}
public HashSet<EntityUid> GenerateMoney(EntProtoId currencyType, int target, EntityCoordinates coordinates, out int remainder)
{
remainder = target;
HashSet<EntityUid> spawns = new();
if (!_proto.TryIndex(currencyType, out var indexedCurrency))
return spawns;
var ent = Spawn(currencyType, coordinates);
if (ProcessEntity(ent, ref remainder, spawns))
return spawns;
while (remainder > 0)
{
var newEnt = Spawn(currencyType, coordinates);
if (ProcessEntity(newEnt, ref remainder, spawns))
break;
}
return spawns;
}
private bool ProcessEntity(EntityUid ent, ref int remainder, HashSet<EntityUid> spawns)
{
var singleCurrency = GetTotalCurrency(ent);
if (singleCurrency > remainder)
{
QueueDel(ent);
return true;
}
spawns.Add(ent);
remainder -= singleCurrency;
if (TryComp<StackComponent>(ent, out var stack))
{
AdjustStack(ent, stack, singleCurrency, ref remainder);
}
return false;
}
private void AdjustStack(EntityUid ent, StackComponent stack, float singleCurrency, ref int remainder)
{
var singleStackCurrency = singleCurrency / stack.Count;
var stackLeftSpace = stack.MaxCountOverride - stack.Count;
if (stackLeftSpace is not null)
{
var addedStack = MathF.Min((float)stackLeftSpace, MathF.Floor(remainder / singleStackCurrency));
if (addedStack > 0)
{
_stack.SetCount(ent, stack.Count + (int)addedStack);
remainder -= (int)(addedStack * singleStackCurrency);
}
}
}
}

View File

@@ -9,12 +9,6 @@ public sealed partial class CP14CurrencyCollectConditionComponent : Component
[DataField]
public int Currency = 1000;
/// <summary>
/// Limits the goal to collecting values from a specific category.
/// </summary>
[DataField]
public string? Category;
[DataField(required: true)]
public LocId ObjectiveText;

View File

@@ -13,7 +13,7 @@ public sealed class CP14CurrencyCollectConditionSystem : EntitySystem
{
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly CP14CurrencySystem _currency = default!;
[Dependency] private readonly CP14SharedCurrencySystem _currency = default!;
private EntityQuery<ContainerManagerComponent> _containerQuery;
@@ -35,7 +35,7 @@ public sealed class CP14CurrencyCollectConditionSystem : EntitySystem
private void OnAfterAssign(Entity<CP14CurrencyCollectConditionComponent> condition, ref ObjectiveAfterAssignEvent args)
{
_metaData.SetEntityName(condition.Owner, Loc.GetString(condition.Comp.ObjectiveText), args.Meta);
_metaData.SetEntityDescription(condition.Owner, Loc.GetString(condition.Comp.ObjectiveDescription, ("coins", _currency.GetPrettyCurrency(condition.Comp.Currency))), args.Meta);
_metaData.SetEntityDescription(condition.Owner, Loc.GetString(condition.Comp.ObjectiveDescription, ("coins", _currency.GetCurrencyPrettyString(condition.Comp.Currency))), args.Meta);
_objectives.SetIcon(condition.Owner, condition.Comp.ObjectiveSprite);
}
@@ -71,7 +71,7 @@ public sealed class CP14CurrencyCollectConditionSystem : EntitySystem
foreach (var entity in container.ContainedEntities)
{
// check if this is the item
count += CheckCurrency(entity, condition);
count += _currency.GetTotalCurrency(entity);
// if it is a container check its contents
if (_containerQuery.TryGetComponent(entity, out var containerManager))
@@ -88,7 +88,7 @@ public sealed class CP14CurrencyCollectConditionSystem : EntitySystem
private void CheckEntity(EntityUid entity, CP14CurrencyCollectConditionComponent condition, ref Stack<ContainerManagerComponent> containerStack, ref int counter)
{
// check if this is the item
counter += CheckCurrency(entity, condition);
counter += _currency.GetTotalCurrency(entity);
//we don't check the inventories of sentient entity
if (!TryComp<MindContainerComponent>(entity, out _))
@@ -98,16 +98,4 @@ public sealed class CP14CurrencyCollectConditionSystem : EntitySystem
containerStack.Push(containerManager);
}
}
private int CheckCurrency(EntityUid entity, CP14CurrencyCollectConditionComponent condition)
{
// check if this is the target
if (!TryComp<CP14CurrencyComponent>(entity, out var target))
return 0;
if (target.Category != condition.Category)
return 0;
return _currency.GetTotalCurrency(entity);
}
}

View File

@@ -130,7 +130,7 @@ public sealed class CP14ExpeditionSystem : EntitySystem
var possiblePositions = new List<EntityCoordinates>();
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
{
if (ev.Job != null && spawnPoint.Job != ev.Job.Prototype)
if (ev.Job != null && spawnPoint.Job != ev.Job.Value)
continue;
if (xform.GridUid != gridUid)

View File

@@ -0,0 +1,87 @@
using System.Numerics;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Shared._CP14.TravelingStoreShip;
using Content.Shared._CP14.TravelingStoreShip.Prototype;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Server._CP14.TravelingStoreShip;
public sealed partial class CP14CargoSystem
{
private EntityQuery<ArrivalsBlacklistComponent> _blacklistQuery;
private void InitializeShuttle()
{
_blacklistQuery = GetEntityQuery<ArrivalsBlacklistComponent>();
SubscribeLocalEvent<CP14TravelingStoreShipComponent, FTLCompletedEvent>(OnFTLCompleted);
}
private void UpdateShuttle()
{
var query = EntityQueryEnumerator<CP14StationTravelingStoreShipTargetComponent>();
while (query.MoveNext(out var uid, out var ship))
{
if (_timing.CurTime < ship.NextTravelTime || ship.NextTravelTime == TimeSpan.Zero)
continue;
if (Transform(ship.Shuttle).MapUid == Transform(ship.TradePostMap).MapUid)
{
// if landed on trade post
ship.NextTravelTime = _timing.CurTime + ship.StationWaitTime;
SendShuttleToStation((uid, ship));
}
else
{
// if landed on station
ship.NextTravelTime = _timing.CurTime + ship.TradePostWaitTime;
SendShuttleToTradepost((uid, ship));
}
}
}
private void SendShuttleToStation(Entity<CP14StationTravelingStoreShipTargetComponent> station, float startupTime = 0f)
{
var targetPoints = new List<EntityUid>();
var targetEnumerator = EntityQueryEnumerator<CP14TravelingStoreShipFTLTargetComponent, TransformComponent>(); //TODO - different method position location
while (targetEnumerator.MoveNext(out var uid, out _, out _))
{
targetPoints.Add(uid);
}
if (targetPoints.Count == 0)
return;
var target = _random.Pick(targetPoints);
var targetXform = Transform(target);
var shuttleComp = Comp<ShuttleComponent>(station.Comp.Shuttle);
_shuttles.FTLToCoordinates(station.Comp.Shuttle, shuttleComp, targetXform.Coordinates, targetXform.LocalRotation, hyperspaceTime: 5f, startupTime: startupTime);
}
private void SendShuttleToTradepost(Entity<CP14StationTravelingStoreShipTargetComponent> station)
{
var shuttleComp = Comp<ShuttleComponent>(station.Comp.Shuttle);
_shuttles.FTLToCoordinates(station.Comp.Shuttle, shuttleComp, new EntityCoordinates(station.Comp.TradePostMap, Vector2.Zero), Angle.Zero, hyperspaceTime: 5f);
}
private void OnFTLCompleted(Entity<CP14TravelingStoreShipComponent> ent, ref FTLCompletedEvent args)
{
if (!TryComp<CP14StationTravelingStoreShipTargetComponent>(ent.Comp.Station, out var station))
return;
if (Transform(ent).MapUid == Transform(station.TradePostMap).MapUid) //Landed on tradepost
{
station.OnStation = false;
SellingThings((ent.Comp.Station, station));
UpdateStorePositions((ent.Comp.Station, station));
}
else //Landed on station
{
station.OnStation = true;
}
UpdateAllStores();
}
}

View File

@@ -0,0 +1,87 @@
using System.Text;
using Content.Shared._CP14.TravelingStoreShip;
using Content.Shared.UserInterface;
namespace Content.Server._CP14.TravelingStoreShip;
public sealed partial class CP14CargoSystem
{
public void InitializeStore()
{
SubscribeLocalEvent<CP14CargoStoreComponent, BeforeActivatableUIOpenEvent>(OnBeforeUIOpen);
}
private void TryInitStore(Entity<CP14CargoStoreComponent> ent)
{
//TODO: There's no support for multiple stations. (settlements).
var stations = _station.GetStations();
if (stations.Count == 0)
return;
if (!TryComp<CP14StationTravelingStoreShipTargetComponent>(stations[0], out var station))
return;
ent.Comp.Station = new Entity<CP14StationTravelingStoreShipTargetComponent>(stations[0], station);
}
private void OnBeforeUIOpen(Entity<CP14CargoStoreComponent> ent, ref BeforeActivatableUIOpenEvent args)
{
if (ent.Comp.Station is null)
TryInitStore(ent);
UpdateUIProducts(ent);
}
//TODO: redo
private void UpdateAllStores()
{
var query = EntityQueryEnumerator<CP14CargoStoreComponent>();
while (query.MoveNext(out var uid, out var store))
{
UpdateUIProducts((uid, store));
}
}
private void UpdateUIProducts(Entity<CP14CargoStoreComponent> ent)
{
if (ent.Comp.Station is null)
return;
var prodBuy = new HashSet<CP14StoreUiProductEntry>();
var prodSell = new HashSet<CP14StoreUiProductEntry>();
foreach (var proto in ent.Comp.Station.Value.Comp.CurrentBuyPositions)
{
if (!_proto.TryIndex(proto.Key, out var indexedProto))
continue;
var name = Loc.GetString(indexedProto.Name);
var desc = new StringBuilder();
desc.Append(Loc.GetString(indexedProto.Desc) + "\n");
foreach (var service in indexedProto.Services)
{
desc.Append(service.GetDescription(_proto, EntityManager));
}
prodBuy.Add(new CP14StoreUiProductEntry(proto.Key.Id, indexedProto.Icon, name, desc.ToString(), proto.Value));
}
foreach (var proto in ent.Comp.Station.Value.Comp.CurrentSellPositions)
{
if (!_proto.TryIndex(proto.Key, out var indexedProto))
continue;
var name = Loc.GetString(indexedProto.Name);
var desc = new StringBuilder();
desc.Append(Loc.GetString(indexedProto.Desc) + "\n");
desc.Append(indexedProto.Service.GetDescription(_proto, EntityManager));
prodSell.Add(new CP14StoreUiProductEntry(proto.Key.Id, indexedProto.Icon, name, desc.ToString(), proto.Value));
}
var stationComp = ent.Comp.Station.Value.Comp;
_userInterface.SetUiState(ent.Owner, CP14StoreUiKey.Key, new CP14StoreUiState(prodBuy, prodSell, stationComp.OnStation, stationComp.NextTravelTime));
}
}

View File

@@ -0,0 +1,205 @@
using Content.Server._CP14.Currency;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Events;
using Content.Server.Station.Systems;
using Content.Shared._CP14.Currency;
using Content.Shared._CP14.TravelingStoreShip;
using Content.Shared._CP14.TravelingStoreShip.Prototype;
using Content.Shared.Storage.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.TravelingStoreShip;
public sealed partial class CP14CargoSystem : CP14SharedCargoSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly ShuttleSystem _shuttles = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly CP14CurrencySystem _currency = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
private EntityQuery<TransformComponent> _xformQuery;
private IEnumerable<CP14StoreBuyPositionPrototype>? _buyProto;
private IEnumerable<CP14StoreSellPositionPrototype>? _sellProto;
public override void Initialize()
{
base.Initialize();
InitializeStore();
InitializeShuttle();
_xformQuery = GetEntityQuery<TransformComponent>();
_buyProto = _proto.EnumeratePrototypes<CP14StoreBuyPositionPrototype>();
_sellProto = _proto.EnumeratePrototypes<CP14StoreSellPositionPrototype>();
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnProtoReload);
SubscribeLocalEvent<CP14StationTravelingStoreShipTargetComponent, StationPostInitEvent>(OnPostInit);
}
private void OnProtoReload(PrototypesReloadedEventArgs ev)
{
_buyProto = _proto.EnumeratePrototypes<CP14StoreBuyPositionPrototype>();
_sellProto = _proto.EnumeratePrototypes<CP14StoreSellPositionPrototype>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateShuttle();
}
private void OnPostInit(Entity<CP14StationTravelingStoreShipTargetComponent> station, ref StationPostInitEvent args)
{
if (!Deleted(station.Comp.Shuttle))
return;
var tradepostMap = _mapManager.CreateMap();
if (!_loader.TryLoad(tradepostMap, station.Comp.ShuttlePath.ToString(), out var shuttleUids))
return;
var shuttle = shuttleUids[0];
station.Comp.Shuttle = shuttle;
station.Comp.TradePostMap = _mapManager.GetMapEntityId(tradepostMap);
var travelingStoreShipComp = EnsureComp<CP14TravelingStoreShipComponent>(station.Comp.Shuttle);
travelingStoreShipComp.Station = station;
station.Comp.NextTravelTime = _timing.CurTime + TimeSpan.FromSeconds(10f);
UpdateStorePositions(station);
}
private void UpdateStorePositions(Entity<CP14StationTravelingStoreShipTargetComponent> station)
{
station.Comp.CurrentBuyPositions.Clear();
station.Comp.CurrentSellPositions.Clear();
if (_buyProto is not null)
{
foreach (var buyPos in _buyProto)
{
station.Comp.CurrentBuyPositions.Add(buyPos, buyPos.Price.Next(_random));
}
}
if (_sellProto is not null)
{
foreach (var sellPos in _sellProto)
{
station.Comp.CurrentSellPositions.Add(sellPos, sellPos.Price.Next(_random));
}
}
}
private void SellingThings(Entity<CP14StationTravelingStoreShipTargetComponent> station)
{
var shuttle = station.Comp.Shuttle;
//Get all entities sent to trading posts
var toSell = new HashSet<EntityUid>();
var query = EntityQueryEnumerator<CP14SellingPalettComponent, TransformComponent>();
while (query.MoveNext(out var uid, out _, out var palletXform))
{
if (palletXform.ParentUid != shuttle || !palletXform.Anchored)
continue;
var sentEntities = new HashSet<EntityUid>();
_lookup.GetEntitiesInRange(uid, 1, sentEntities, LookupFlags.Dynamic | LookupFlags.Sundries);
foreach (var ent in sentEntities)
{
if (toSell.Contains(ent) || !_xformQuery.TryGetComponent(ent, out _))
continue;
toSell.Add(ent);
}
}
var cash = 0;
foreach (var sellPos in station.Comp.CurrentSellPositions)
{
if (!_proto.TryIndex(sellPos.Key, out var indexedPos))
continue;
while (indexedPos.Service.TrySell(EntityManager, toSell))
{
cash += sellPos.Value;
}
}
var moneyBox = GetMoneyBox(station);
if (moneyBox is not null)
{
var coord = Transform(moneyBox.Value).Coordinates;
if (cash > 0)
{
var coins = _currency.GenerateMoney(CP14SharedCurrencySystem.PP.Key, cash, coord, out var remainder);
cash = remainder;
foreach (var coin in coins)
{
_storage.Insert(moneyBox.Value, coin, out _);
}
}
if (cash > 0)
{
var coins = _currency.GenerateMoney(CP14SharedCurrencySystem.GP.Key, cash, coord, out var remainder);
cash = remainder;
foreach (var coin in coins)
{
_storage.Insert(moneyBox.Value, coin, out _);
}
}
if (cash > 0)
{
var coins = _currency.GenerateMoney(CP14SharedCurrencySystem.SP.Key, cash, coord, out var remainder);
cash = remainder;
foreach (var coin in coins)
{
_storage.Insert(moneyBox.Value, coin, out _);
}
}
if (cash > 0)
{
var coins = _currency.GenerateMoney(CP14SharedCurrencySystem.CP.Key, cash, coord);
foreach (var coin in coins)
{
_storage.Insert(moneyBox.Value, coin, out _);
}
}
}
}
private EntityUid? GetMoneyBox(Entity<CP14StationTravelingStoreShipTargetComponent> station)
{
var query = EntityQueryEnumerator<CP14CargoMoneyBoxComponent, TransformComponent>();
while (query.MoveNext(out var uid, out _, out var xform))
{
if (xform.GridUid != station.Comp.Shuttle)
continue;
return uid;
}
return null;
}
}

View File

@@ -0,0 +1,8 @@
namespace Content.Server._CP14.TravelingStoreShip;
[RegisterComponent, Access(typeof(CP14CargoSystem)), AutoGenerateComponentPause]
public sealed partial class CP14TravelingStoreShipComponent : Component
{
[DataField]
public EntityUid Station;
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server._CP14.TravelingStoreShip;
/// <summary>
/// One of the possible points where an traveling store ship might land
/// </summary>
[RegisterComponent, Access(typeof(CP14CargoSystem))]
public sealed partial class CP14TravelingStoreShipFTLTargetComponent : Component
{
}

View File

@@ -30,7 +30,6 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<StackComponent> _stackQuery;
@@ -86,6 +85,7 @@ public sealed partial class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
}
var resultEntity = Spawn(_proto.Index(args.Recipe).Result);
_stack.TryMergeToContacts(resultEntity);
_solutionContainer.TryGetSolution(resultEntity, recipe.Solution, out var resultSoln, out var resultSolution);
if (recipe.TryMergeSolutions && resultSoln is not null)

View File

@@ -20,7 +20,12 @@ public sealed partial class IdCardComponent : Component
[DataField]
[AutoNetworkedField]
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)]
public string? JobTitle;
public LocId? JobTitle;
private string? _jobTitle;
[Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWriteExecute)]
public string? LocalizedJobTitle { set => _jobTitle = value; get => _jobTitle ?? Loc.GetString(JobTitle ?? string.Empty); }
/// <summary>
/// The state of the job icon rsi.

View File

@@ -67,7 +67,7 @@ public sealed class IdExaminableSystem : EntitySystem
private string GetNameAndJob(IdCardComponent id)
{
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
var val = string.IsNullOrWhiteSpace(id.FullName)
? Loc.GetString(id.NameLocId,

View File

@@ -116,6 +116,7 @@ public abstract class SharedIdCardSystem : EntitySystem
/// </summary>
/// <remarks>
/// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
/// Actually works with the LocalizedJobTitle DataField and not with JobTitle.
/// </remarks>
public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null)
{
@@ -134,9 +135,9 @@ public abstract class SharedIdCardSystem : EntitySystem
jobTitle = null;
}
if (id.JobTitle == jobTitle)
if (id.LocalizedJobTitle == jobTitle)
return true;
id.JobTitle = jobTitle;
id.LocalizedJobTitle = jobTitle;
Dirty(uid, id);
UpdateEntityName(uid, id);
@@ -238,7 +239,7 @@ public abstract class SharedIdCardSystem : EntitySystem
if (!Resolve(uid, ref id))
return;
var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})";
var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
var val = string.IsNullOrWhiteSpace(id.FullName)
? Loc.GetString(id.NameLocId,
@@ -251,7 +252,7 @@ public abstract class SharedIdCardSystem : EntitySystem
private static string ExtractFullTitle(IdCardComponent idCardComponent)
{
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.JobTitle ?? string.Empty)})"
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})"
.Trim();
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.ChangeNameInContainer;
/// <summary>
/// An entity with this component will get its name and verb chaned to the container it's inside of. E.g, if your a
/// pAI that has this component and are inside a lizard plushie, your name when talking will be "lizard plushie".
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ChangeNameInContainerSystem))]
public sealed partial class ChangeVoiceInContainerComponent : Component
{
/// <summary>
/// A whitelist of containers that will change the name.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Chat;
using Robust.Shared.Containers;
using Content.Shared.Whitelist;
using Content.Shared.Speech;
namespace Content.Shared.ChangeNameInContainer;
public sealed partial class ChangeNameInContainerSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChangeVoiceInContainerComponent, TransformSpeakerNameEvent>(OnTransformSpeakerName);
}
private void OnTransformSpeakerName(Entity<ChangeVoiceInContainerComponent> ent, ref TransformSpeakerNameEvent args)
{
if (!_container.TryGetContainingContainer((ent, null, null), out var container)
|| _whitelist.IsWhitelistFail(ent.Comp.Whitelist, container.Owner))
return;
args.VoiceName = Name(container.Owner);
if (TryComp<SpeechComponent>(container.Owner, out var speechComp))
args.SpeechVerb = speechComp.SpeechVerb;
}
}

View File

@@ -1,36 +0,0 @@
using System.Numerics;
using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// Defines something as causing waddling when worn.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class WaddleWhenWornComponent : Component
{
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f);
/// <summary>
/// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary>
[DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f;
/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField, AutoNetworkedField]
public float AnimationLength = 0.66f;
/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Inventory.Events;
namespace Content.Shared.Clothing.EntitySystems;
public sealed class WaddleClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotEquippedEvent args)
{
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Wearer);
waddleAnimComp.AnimationLength = comp.AnimationLength;
waddleAnimComp.HopIntensity = comp.HopIntensity;
waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
}
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotUnequippedEvent args)
{
RemComp<WaddleAnimationComponent>(args.Wearer);
}
}

View File

@@ -14,7 +14,6 @@ public sealed partial class MindContainerComponent : Component
/// The mind controlling this mob. Can be null.
/// </summary>
[DataField, AutoNetworkedField]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public EntityUid? Mind { get; set; }
/// <summary>
@@ -35,7 +34,6 @@ public sealed partial class MindContainerComponent : Component
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("ghostOnShutdown")]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public bool GhostOnShutdown { get; set; } = true;
}

View File

@@ -1,4 +1,3 @@
using Content.Shared.Actions;
using Content.Shared.GameTicking;
using Content.Shared.Mind.Components;
using Robust.Shared.GameStates;
@@ -87,17 +86,21 @@ public sealed partial class MindComponent : Component
/// <summary>
/// Prevents user from ghosting out
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("preventGhosting")]
[DataField]
public bool PreventGhosting { get; set; }
/// <summary>
/// Prevents user from suiciding
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("preventSuicide")]
[DataField]
public bool PreventSuicide { get; set; }
/// <summary>
/// Mind Role Entities belonging to this Mind
/// </summary>
[DataField, AutoNetworkedField]
public List<EntityUid> MindRoles = new List<EntityUid>();
/// <summary>
/// The session of the player owning this mind.
/// Can be null, in which case the player is currently not logged in.

View File

@@ -1,74 +0,0 @@
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components;
/// <summary>
/// Declares that an entity has started to waddle like a duck/clown.
/// </summary>
/// <param name="entity">The newly be-waddled.</param>
[Serializable, NetSerializable]
public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity = entity;
}
/// <summary>
/// Declares that an entity has stopped waddling like a duck/clown.
/// </summary>
/// <param name="entity">The former waddle-er.</param>
[Serializable, NetSerializable]
public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity = entity;
}
/// <summary>
/// Defines something as having a waddle animation when it moves.
/// </summary>
[RegisterComponent, AutoGenerateComponentState]
public sealed partial class WaddleAnimationComponent : Component
{
/// <summary>
/// What's the name of this animation? Make sure it's unique so it can play along side other animations.
/// This prevents someone accidentally causing two identical waddling effects to play on someone at the same time.
/// </summary>
[DataField]
public string KeyName = "Waddle";
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f);
/// <summary>
/// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary>
[DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f;
/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField, AutoNetworkedField]
public float AnimationLength = 0.66f;
/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;
/// <summary>
/// Stores which step we made last, so if someone cancels out of the animation mid-step then restarts it looks more natural.
/// </summary>
public bool LastStep;
/// <summary>
/// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state.
/// </summary>
[AutoNetworkedField]
public bool IsCurrentlyWaddling;
}

View File

@@ -1,106 +0,0 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Robust.Shared.Timing;
namespace Content.Shared.Movement.Systems;
public abstract class SharedWaddleAnimationSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
// Startup
SubscribeLocalEvent<WaddleAnimationComponent, ComponentStartup>(OnComponentStartup);
// Start moving possibilities
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StoodEvent>(OnStood);
// Stop moving possibilities
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref StunnedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref DownedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref BuckledEvent _) => StopWaddling(ent));
SubscribeLocalEvent<WaddleAnimationComponent, GravityChangedEvent>(OnGravityChanged);
}
private void OnGravityChanged(Entity<WaddleAnimationComponent> ent, ref GravityChangedEvent args)
{
if (!args.HasGravity && ent.Comp.IsCurrentlyWaddling)
StopWaddling(ent);
}
private void OnComponentStartup(Entity<WaddleAnimationComponent> entity, ref ComponentStartup args)
{
if (!TryComp<InputMoverComponent>(entity.Owner, out var moverComponent))
return;
// If the waddler is currently moving, make them start waddling
if ((moverComponent.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.AnyDirection)
{
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}
private void OnMovementInput(Entity<WaddleAnimationComponent> entity, ref MoveInputEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && entity.Comp.IsCurrentlyWaddling)
{
StopWaddling(entity);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (entity.Comp.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void OnStood(Entity<WaddleAnimationComponent> entity, ref StoodEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
{
return;
}
if ((mover.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.None)
return;
if (entity.Comp.IsCurrentlyWaddling)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
entity.Comp.IsCurrentlyWaddling = false;
RaiseNetworkEvent(new StoppedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}

View File

@@ -1,20 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Roles;
public abstract partial class AntagonistRoleComponent : Component
{
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
public string? PrototypeId;
}
/// <summary>
/// Mark the antagonist role component as being exclusive
/// IE by default other antagonists should refuse to select the same entity for a different antag role
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(AntagonistRoleComponent))]
public sealed partial class ExclusiveAntagonistAttribute : Attribute
{
}

View File

@@ -1,14 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind entities to hold the data for the player's current job.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class JobComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public ProtoId<JobPrototype>? Prototype;
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind role entities to mark them as a job role entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class JobRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -13,8 +13,10 @@ namespace Content.Shared.Roles.Jobs;
/// </summary>
public abstract class SharedJobSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedPlayerSystem _playerSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
private readonly Dictionary<string, string> _inverseTrackerLookup = new();
public override void Initialize()
@@ -100,32 +102,44 @@ public abstract class SharedJobSystem : EntitySystem
public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
{
return CompOrNull<JobComponent>(mindId)?.Prototype == prototypeId;
MindRoleComponent? comp = null;
if (mindId is null)
return false;
_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role);
if (role is null)
return false;
comp = role.Value.Comp;
return (comp.JobPrototype == prototypeId);
}
public bool MindTryGetJob(
[NotNullWhen(true)] EntityUid? mindId,
[NotNullWhen(true)] out JobComponent? comp,
[NotNullWhen(true)] out JobPrototype? prototype)
{
comp = null;
prototype = null;
MindTryGetJobId(mindId, out var protoId);
return TryComp(mindId, out comp) &&
comp.Prototype != null &&
_prototypes.TryIndex(comp.Prototype, out prototype);
return (_prototypes.TryIndex<JobPrototype>(protoId, out prototype) || prototype is not null);
}
public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId<JobPrototype>? job)
public bool MindTryGetJobId(
[NotNullWhen(true)] EntityUid? mindId,
out ProtoId<JobPrototype>? job)
{
if (!TryComp(mindId, out JobComponent? comp))
{
job = null;
return false;
}
job = null;
job = comp.Prototype;
return true;
if (mindId is null)
return false;
if (_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role))
job = role.Value.Comp.JobPrototype;
return (job is not null);
}
/// <summary>
@@ -134,7 +148,7 @@ public abstract class SharedJobSystem : EntitySystem
/// </summary>
public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name)
{
if (MindTryGetJob(mindId, out _, out var prototype))
if (MindTryGetJob(mindId, out var prototype))
{
name = prototype.LocalizedName;
return true;
@@ -161,7 +175,7 @@ public abstract class SharedJobSystem : EntitySystem
if (_playerSystem.ContentData(player) is not { Mind: { } mindId })
return true;
if (!MindTryGetJob(mindId, out _, out var prototype))
if (!MindTryGetJob(mindId, out var prototype))
return true;
return prototype.CanBeAntag;

View File

@@ -7,7 +7,7 @@ namespace Content.Shared.Roles;
/// </summary>
/// <param name="Roles">The list of roles on the player.</param>
[ByRefEvent]
public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
public readonly record struct MindGetAllRoleInfoEvent(List<RoleInfo> Roles);
/// <summary>
/// Returned by <see cref="MindGetAllRolesEvent"/> to give some information about a player's role.
@@ -17,4 +17,4 @@ public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
/// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param>
/// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param>
/// <param name="Prototype">The prototype ID of the role</param>
public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype);
public readonly record struct RoleInfo(string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype);

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