Skip to content

Commit

Permalink
New Species: Plasmaman (#1291)
Browse files Browse the repository at this point in the history
# Description

Adds the Plasmamen as a playable species. Plasmamen are a skeletal
species who depend on Plasma to live, and oxygen is highly fatal to
them. Being exposed to oxygen will set them on fire, unless they wear
their envirosuits.

## Species Guidebook

<img width=500px
src="https://github.com/user-attachments/assets/a1ef91ef-87b2-4ae0-8b5c-922a0c34777f">
<img width=500px
src="https://github.com/user-attachments/assets/110f0fa0-7dc4-410b-a2c0-a517f0311484">

**SPECIAL:**

- Plasmamen speak the language Calcic, a language they share with
Skeletons.

## Shitmed Integration

Plasmamen are the first ever species designed with Shitmed in mind, with
one of their core mechanics (self-ignition) powered entirely by Shitmed.

Whether or not a Plasmaman ignites from oxygen exposure depends only on
their body parts. A Plasmaman with only their head exposed will not burn
as much as an entirely naked Plasmaman. You can **transfer** Plasmaman
body parts to non-Plasmamen through **surgery** so that they also ignite
from oxygen exposure. Meanwhile, a Plasmaman with a non-Plasmaman head
can expose their head without self-igniting.


https://github.com/user-attachments/assets/0aa33070-04be-4ded-b668-3afb9f4ddd7c

## Technical Details

This also cherry-picks
space-wizards/space-station-14#28595 as a
quality-of-life feature to ensure Plasmamen keep their internals on upon
toggling their helmet with a breath mask on.

## TODO

### RELEASE-NECESSARY

<details>

- [x] Port more envirosuits / enviro helms (job-specific) and their
sprites
- [x] Remove breath masks from default plasmaman loadouts because the
envirohelms already allow them to breathe internals
- [x] Change default plasma tank to higher-capacity version
- [x] Prevent plasmamen from buying jumpsuits and helmets other than
envirosuits
- ~~[ ] **Client UI update for loadout groups min/max items and default
items**~~
- [x] Plasmaman-specific mask sprites from TG
- [x] Disable too cold alert for plasmamen
- [x] Create/port sprites for these jobs
  - [x] Courier
  - [x] Forensic Mantis
  - [x] Corpsman (Resprite security envirosuit)
  - [x] Prison Guard (Resprite security envirosuit)
- [x] Magistrate (No Paradise envirosuit so use new colorable
envirosuit)
  - [x] Blueshield (Port from Paradise and tg-ify?)
- [x] NanoTrasen Representative (No Paradise envirosuit so use new
colorable envirosuit)
- [x] Martial Artist (use new colorable envirosuit and make pure white)
  - [x] Musician (use new colorable envirosuit)
  - [x] Reporter (use new colorable envirosuit)
  - [x] Zookeeper (use new colorable envirosuit)
  - [x] Service Worker (use new colorable envirosuit)
  - [x] Gladiator
  - [x] Technical Assistant
  - [x] Medical Intern
  - [x] Acolyte / Research Assistant
  - [x] Security Cadet
  - [x] Assistant
- You know what. These intern jobs are fine. They can use their normal
equivalent's envirosuits.
  - [x] Logistics Officer (use new colorable envirosuit)
- [x] Adjust sprites to be closer to actual job
  - [x] Captain (Shift color to be closer to ss14 captain)
  - [x] ~~CMO (Remove yellow accents)~~
  - [x] Port HoP envirogloves sprite
- [x] unique sprite for self-extinguish verb
- [x] Refactor conditional gear stuff to live only in
StartingGearPrototype with `SubGear`
`List<ProtoId<StartingGearPrototype>>` field and `List<Requirement>`
field for sub-gear requirements
- [x] Add starting gear for paradox anomaly, and antags and ghost roles
  - [x] Paradox
  - [x] Nukies
  - [x] Disaster victims
  - [x] Listening post operative
- [x] Make all envirosuit helmets have a glowing (unshaded) visor
- [x] Envirosuit extinguish visuals
- [x] JobPrototype: AfterLoadoutSpecial
- [x] Set prisoner envirohelm battery to potato, command/sec/dignitary
to high-powered
  - [x] Set base envirosuit extinguishes to 4, sec 6 and command 8 
- [x] Improve plasmaman organ extraction experience
  - [x] Body parts now give 1 plasma sheet each, while Torso gives 3
  - [x] Organs can be juiced to get plasma
- [x] Make envirohelm flashlights battery-powered
- [x] Plasmamen visuals
- [x] Grayscale sprites for color customization, and set default
skintone color to Plasmaman classic skintone
  - [x] Plasmaman eye organ sprite
- [x] Add basic loadouts
- [x] Add way to refill envirosuit charges (refill at medical protolathe
after some research)

</details>

### Low Importance

<details>

- [x] Envirogloves
- [ ] (SCOPE CREEP) Plasma tanks sprite (only normal emergency/extended,
rather low priority)
- [ ] (SCOPE CREEP) Modify envirosuit helmet sprites to have a
transparent visor
- [ ] Glowing eyes/mouth marking
- [x] More cargo content with plasma tanks / envirosuits
  - [x] Plasmaman survival kit like slime
  - [x] Additional plasma tanks
  - [ ] (SCOPE CREEP) Plasmaman EVA suits
- [x] ~~Add envirosuits to clothesmate~~
- [x] Add more plasma tanks to random lockers and job lockers
- [x] Turn envirosuit auto-extinguish into extinguish action
- [x] move self-extinguish verb stuff to shared for prediction of the
verb
- [x] move self-extinguisher stuff away from extinguisher namespace
- [x] unique sprite for self-extinguish icon
  - [x] ~~IDEA: purple glowy fire extinguisher ~~
- [x] on self-extinguish, check for pressure immunity OR ignite from gas
immunity properly
- [x] See envirosuit extinguish charges in examine
- [x] Milk heals on ingestion
- [x] Plasma heals on ingestion
- [x] Self-ignition doesn't occur on a stasis bed
- [x] ~~Self-ignition doesn't occur when dead~~
- [x] Guidebook entry
- [x] Make self-ignition ignore damage resistances from fire suits
- [x] ~~Make self-ignition ignore damage resistances from armor~~
- [x] ~~Unable to rot?~~
- [x] Make the envirosuit helmet toggle on for the character dummy in
lobby
- [ ] (SCOPE CREEP) One additional Plasmaman trait
- [x] ~~Showers extinguish water as well as water tiles~~
- Unnecessary as stasis beds now prevent ignition, allowing surgery on a
plasmaman on stasis beds.
- [x] Unique punch animations for Plasmafire Punch/Toxoplasmic Punch
traits
- [x] Actually remove toxoplasmic it's just slop filler tbh
- [ ] Talk sounds
  - [ ] Normal
  - [ ] Question
  - [ ] Yell
- [x] Positive moodlet for drinking milk / more positive moodlet for
drinking plasma
- [x] Increase moodlet bonus and also minimum reagent required for the
plasma/milk moodlets
- [x] Increase fire rate base stacks on ignite cause putting out your
helmet for a few secs isn't that dangerous due to the fire stacks
immediately decaying
- [x] I think halving firestack fade from -0.1 to -0.05 might work to do
the same thing too
- [ ] (SCOPE CREEP) Get bone laugh sounds from monke
'monkestation/sound/voice/laugh/skeleton/skeleton_laugh.ogg'
- [ ] (SCOPE CREEP) When EVA plasmaman suit is added, 25% caustic resist
- [x]  Envirosuit helmet
  - [x] Equivalent of 100% bio / 100% fire / 75% acid resist
- [x] Envirosuit
  - [x] Equivalent of 100% bio / 100% fire / 75% acid resist
- [x] Envirogloves
  - [x] Equivalent of 100% bio / 95% fire / 95% acid resist
- [x] Put breath mask back on
- [x] Refactor: put body parts covered data into component instead of
being hardcoded

</details>

## Media

**Custom Plasmaman Outfits**

All of these use the same **absolutely massive** [envirosuit
RSI](https://github.com/angelofallars/Einstein-Engines/tree/0c3af432dfbbc41c1a705194ceb7930a64d5b356/Resources/Textures/Clothing/Uniforms/Envirosuits/color.rsi)
and [envirohelm
RSI](https://github.com/angelofallars/Einstein-Engines/tree/0c3af432dfbbc41c1a705194ceb7930a64d5b356/Resources/Textures/Clothing/Head/Envirohelms/color.rsi)
to quickly create the envirosuits that didn't exist in SS13 where the
envirosuit sprites were ported.

From Left to Right: Magistrate, Prison Guard, Boxer, Reporter, Logistics
Officer

<img width=200px
src="https://github.com/user-attachments/assets/bf990841-7d9e-4f4e-abae-8f29a3980ca1">
<img width=200px
src="https://github.com/user-attachments/assets/07ca7af7-4f43-4504-9eac-4ca9188ae98e">
<img width=200px
src="https://github.com/user-attachments/assets/0d20332c-826f-4fec-8396-74e84c23b074">
<img width=200px
src="https://github.com/user-attachments/assets/1634364e-7cb3-457b-b638-e1b562b7c0c5">
<img width=200px
src="https://github.com/user-attachments/assets/c2881764-f2fa-4e40-9fbf-35d1b717c432">

**Plasmaman Melee Attack**


https://github.com/user-attachments/assets/6e694f2c-3e03-40bf-ae27-fc58a3e4cb6c

**Chat bubble**

<img width=240px
src="https://github.com/user-attachments/assets/e3c17e6d-5050-410f-a42c-339f0bfa30a1">

**Plasmaman Body**

<img width=140px
src="https://github.com/user-attachments/assets/7ed90a47-9c33-487d-bd44-c50cec9f16dd">

With different colors:

<img width=140px
src="https://github.com/user-attachments/assets/0a28068e-7392-4062-950b-f60d2602da84">
<img width=140px
src="https://github.com/user-attachments/assets/9b652311-0305-4ec0-be60-e404697617a2">

**Skeleton Language**


![image](https://github.com/user-attachments/assets/89b2b047-3bfa-4106-926e-6c412ed6e57c)

**(Bonus) Skeleton chat bubble**

<img width=240px
src="https://github.com/user-attachments/assets/a2e2be5c-f3ae-49d9-b655-8688de45b512">

**Self-Extinguish**


https://github.com/user-attachments/assets/6c68e2ef-8010-4f00-8c24-dce8a8065be8

The self-extinguish is also accessible as a verb, which also means that
others can activate your self-extinguish if they open the strip menu.

<img width=200px
src="https://github.com/user-attachments/assets/291ab86d-2250-46ec-ae0c-80084ab04407">

The self-extinguish action has different icons depending on the status
of the self extinguish.

Left to right: Ready, On Cooldown, Out Of Charges

<img
src="https://github.com/user-attachments/assets/0340de8a-9440-43b1-8bff-1c8f962faa0c">

<img
src="https://github.com/user-attachments/assets/11f73558-6dc1-444d-b2ef-2f15f55174ca">

<img
src="https://github.com/user-attachments/assets/030ed737-f178-4c60-ba0c-109659e7d9cb">


**Envirosuit Extinguisher Refill**

<img width=300px
src="https://github.com/user-attachments/assets/9379294b-e3f3-436d-81bc-2584631869ef">
<img width=300px
src="https://github.com/user-attachments/assets/807b9e9e-7b4b-4593-aa1f-d9d24ac6985c">

**Loadouts**

<img width=400px
src="https://github.com/user-attachments/assets/55713b87-29bb-41b3-b7a3-88fbc6e5e797">
<img width=400px
src="https://github.com/user-attachments/assets/ab1757fa-9b70-4a66-b5ae-20fd9cabe935">
<img width=400px
src="https://github.com/user-attachments/assets/aacc4cf7-9ce1-4099-b8c7-108bef1f3bde">
<img width=400px
src="https://github.com/user-attachments/assets/58604dc2-82ef-4d42-b9e2-639548c93f40">

**Plasma Envirosuit Crate**
<img width=400px
src="https://github.com/user-attachments/assets/fa362387-9c10-47c3-b1af-2c11e6b00163">

<img width=400px
src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7">

**Internals Crate (Plasma)**
<img width=400px
src="https://github.com/user-attachments/assets/fcd4ff2e-09e9-423a-9b21-96817f6042a4">

<img width=400px
src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7">

**Glow In The Dark**

![image](https://github.com/user-attachments/assets/9728eb33-55d5-4f82-92ac-3a7756068577)


## Changelog

:cl: Skubman
- add: The Plasmaman species has arrived! They need to breathe plasma to
live, and a special jumpsuit to prevent oxygen from igniting them. In
exchange, they deal formidable unarmed Heat damage, are never hungry nor
thirsty, and are immune to cold and radiation damage. Read more about
Plasmamen in their Guidebook entry.
- tweak: Internals are no longer toggled off if you take your helmet off
but still have a gas mask on and vice versa.
- tweak: Paradox Anomalies will now spawn with the original person's
Loadout items.
- fix: Fixed prisoners not being able to have custom Loadout names and
descriptions, and heirlooms if they didn't have a backpack when joining.

---------

Signed-off-by: Skubman <[email protected]>
Signed-off-by: VMSolidus <[email protected]>
Co-authored-by: Plykiya <[email protected]>
Co-authored-by: VMSolidus <[email protected]>
  • Loading branch information
3 people authored Jan 25, 2025
1 parent 8c6f1a4 commit e68e0c3
Show file tree
Hide file tree
Showing 647 changed files with 13,246 additions and 129 deletions.
31 changes: 31 additions & 0 deletions Content.Client/Clothing/ClientClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,20 @@ private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEv
if (!inventorySlots.VisualLayerKeys.TryGetValue(args.Slot, out var revealedLayers))
return;

if (TryComp<HideLayerClothingComponent>(args.Equipment, out var hideLayer) &&
hideLayer.ClothingSlots != null)
{
foreach (var clothingSlot in hideLayer.ClothingSlots)
{
if (!inventorySlots.VisualLayerKeys.TryGetValue(clothingSlot, out var revealedLayersToShow))
continue;

foreach (var layerToShow in revealedLayersToShow)
component.LayerSetVisible(layerToShow, true);
}
inventorySlots.HiddenSlots.ExceptWith(hideLayer.ClothingSlots);
}

// Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this
// may eventually bloat the player with lots of invisible layers.
foreach (var layer in revealedLayers)
Expand Down Expand Up @@ -264,6 +278,20 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
return;
}

if (TryComp<HideLayerClothingComponent>(equipment, out var hideLayer) &&
hideLayer.ClothingSlots != null)
{
foreach (var clothingSlot in hideLayer.ClothingSlots)
{
if (!inventorySlots.VisualLayerKeys.TryGetValue(clothingSlot, out var revealedLayersToHide))
continue;

foreach (var layerToHide in revealedLayersToHide)
sprite.LayerSetVisible(layerToHide, false);
}
inventorySlots.HiddenSlots.UnionWith(hideLayer.ClothingSlots);
}

var displacementData = inventory.Displacements.GetValueOrDefault(slot);

if (clothingComponent.RenderLayer != null)
Expand Down Expand Up @@ -313,6 +341,9 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
if (slot == Jumpsuit)
layerData.Shader ??= "StencilDraw";

if (inventorySlots.HiddenSlots.Contains(slot))
layerData.Visible = false;

sprite.LayerSetData(index, layerData);
layer.Offset += slotDef.Offset;

Expand Down
12 changes: 6 additions & 6 deletions Content.Client/Effects/ColorFlashEffectSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public override void Initialize()
SubscribeLocalEvent<ColorFlashEffectComponent, AnimationCompletedEvent>(OnEffectAnimationCompleted);
}

public override void RaiseEffect(Color color, List<EntityUid> entities, Filter filter)
public override void RaiseEffect(Color color, List<EntityUid> entities, Filter filter, float? animationLength = null)
{
if (!_timing.IsFirstTimePredicted)
return;

OnColorFlashEffect(new ColorFlashEffectEvent(color, GetNetEntityList(entities)));
OnColorFlashEffect(new ColorFlashEffectEvent(color, GetNetEntityList(entities), animationLength));
}

private void OnEffectAnimationCompleted(EntityUid uid, ColorFlashEffectComponent component, AnimationCompletedEvent args)
Expand All @@ -48,15 +48,15 @@ private void OnEffectAnimationCompleted(EntityUid uid, ColorFlashEffectComponent
RemCompDeferred<ColorFlashEffectComponent>(uid);
}

private Animation? GetDamageAnimation(EntityUid uid, Color color, SpriteComponent? sprite = null)
private Animation? GetDamageAnimation(EntityUid uid, Color color, SpriteComponent? sprite = null, float? animationLength = null)
{
if (!Resolve(uid, ref sprite, false))
return null;

// 90% of them are going to be this so why allocate a new class.
return new Animation
{
Length = TimeSpan.FromSeconds(AnimationLength),
Length = TimeSpan.FromSeconds(animationLength ?? AnimationLength),
AnimationTracks =
{
new AnimationTrackComponentProperty
Expand All @@ -67,7 +67,7 @@ private void OnEffectAnimationCompleted(EntityUid uid, ColorFlashEffectComponent
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(color, 0f),
new AnimationTrackProperty.KeyFrame(sprite.Color, AnimationLength)
new AnimationTrackProperty.KeyFrame(sprite.Color, animationLength ?? AnimationLength)
}
}
}
Expand Down Expand Up @@ -112,7 +112,7 @@ private void OnColorFlashEffect(ColorFlashEffectEvent ev)
sprite.Color = effect.Color;
}

var animation = GetDamageAnimation(ent, color, sprite);
var animation = GetDamageAnimation(ent, color, sprite, ev.AnimationLength);

if (animation == null)
continue;
Expand Down
7 changes: 7 additions & 0 deletions Content.Client/Inventory/InventorySlotsComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ public sealed partial class InventorySlotsComponent : Component
[ViewVariables]
[Access(typeof(ClientInventorySystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public readonly Dictionary<string, HashSet<string>> VisualLayerKeys = new();

/// <summary>
/// The slots whose associated visual layers are hidden.
/// </summary>
[ViewVariables]
[Access(typeof(ClientInventorySystem), Other = AccessPermissions.ReadWriteExecute)]
public readonly HashSet<string> HiddenSlots = new();
}
6 changes: 6 additions & 0 deletions Content.Client/Light/Components/LightFadeComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ public sealed partial class LightFadeComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("duration")]
public float Duration = 0.5f;

// <summary>
// The duration of the fade-in effect before starting the fade out effect.
// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
public float RampUpDuration = 0f;
}
3 changes: 2 additions & 1 deletion Content.Client/Light/EntitySystems/LightFadeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ private void OnFadeStartup(EntityUid uid, LightFadeComponent component, Componen
InterpolationMode = AnimationInterpolationMode.Cubic,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(light.Energy, 0f),
new AnimationTrackProperty.KeyFrame(0f, 0f),
new AnimationTrackProperty.KeyFrame(light.Energy, component.RampUpDuration),
new AnimationTrackProperty.KeyFrame(0f, component.Duration)
}
}
Expand Down
11 changes: 10 additions & 1 deletion Content.Client/Lobby/LobbyUIController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Content.Client.Inventory;
using Content.Client.Lobby.UI;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Station;
using Content.Shared.CCVar;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Clothing.Loadouts.Systems;
Expand Down Expand Up @@ -44,6 +45,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly SharedLoadoutSystem _loadouts = default!;
[UISystemDependency] private readonly StationSpawningSystem _stationSpawning = default!;

private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor;
Expand Down Expand Up @@ -273,6 +275,7 @@ public void GiveDummyJobClothes(EntityUid dummy, JobPrototype job, HumanoidChara
return;

var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
gear = _stationSpawning.ApplySubGear(gear, profile, job);

foreach (var slot in slots)
{
Expand All @@ -289,6 +292,12 @@ public void GiveDummyJobClothes(EntityUid dummy, JobPrototype job, HumanoidChara
}
}

/// Applies loadouts to the dummy.
public void GiveDummyLoadout(EntityUid dummy, JobPrototype job, HumanoidCharacterProfile profile)
{
_loadouts.ApplyCharacterLoadout(dummy, job, profile, _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted(), out _);
}

/// Loads the profile onto a dummy entity
public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, bool jobClothes, bool loadouts)
{
Expand All @@ -312,7 +321,7 @@ public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, bool jobC
if (jobClothes)
GiveDummyJobClothes(dummyEnt, job, humanoid);
if (loadouts)
_loadouts.ApplyCharacterLoadout(dummyEnt, job, humanoid, _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted(), out _);
GiveDummyLoadout(dummyEnt, job, humanoid);
}

return dummyEnt;
Expand Down
15 changes: 15 additions & 0 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,20 @@ private void ReloadPreview()
SpriteView.SetEntity(PreviewDummy);
}

/// Reloads the dummy entity's clothes for preview
private void ReloadClothes()
{
if (Profile == null)
return;

_controller.RemoveDummyClothes(PreviewDummy);
var job = _controller.GetPreferredJob(Profile);
if (ShowClothes.Pressed)
_controller.GiveDummyJobClothes(PreviewDummy, job, Profile);
if (ShowLoadouts.Pressed)
_controller.GiveDummyLoadout(PreviewDummy, job, Profile);
}

/// Resets the profile to the defaults
public void ResetToDefault()
{
Expand Down Expand Up @@ -1265,6 +1279,7 @@ private void SetSpecies(string newSpecies)
UpdateSpeciesGuidebookIcon();
IsDirty = true;
ReloadProfilePreview();
ReloadClothes(); // Species may have job-specific gear, reload the clothes
}

private void SetName(string newName)
Expand Down
5 changes: 5 additions & 0 deletions Content.Client/SelfExtinguisher/SelfExtinguisherSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared.SelfExtinguisher;

namespace Content.Server.SelfExtinguisher;

public sealed partial class SelfExtinguisherSystem : SharedSelfExtinguisherSystem { }
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,15 @@ public void UpdateIcons()
if (_action.BackgroundOn != null)
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
}
else if (_action.IconDisabled is { } iconDisabled && !_action.Enabled)
SetActionIcon(_spriteSys.Frame0(iconDisabled));
else if (_action.IconCooldown is { } iconCooldown && _action.Cooldown is { } cooldown &&
cooldown.End > IoCManager.Resolve<IGameTiming>().CurTime)
SetActionIcon(_spriteSys.Frame0(iconCooldown));
else
{
SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null);
_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
}

_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
}

public void UpdateBackground()
Expand Down
1 change: 1 addition & 0 deletions Content.IntegrationTests/Tests/StoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ await server.WaitAssertion(() =>

var prototypeCost = prototype.Cost[UplinkSystem.TelecrystalCurrencyPrototype];
var discountDownTo = prototype.DiscountDownTo[UplinkSystem.TelecrystalCurrencyPrototype];

Assert.That(plainDiscountedCost.Value, Is.GreaterThanOrEqualTo(discountDownTo.Value), $"Expected discounted cost to be greater then DiscountDownTo value. ({itemId})");
Assert.That(plainDiscountedCost.Value, Is.LessThan(prototypeCost.Value), $"Expected discounted cost to be lower then prototype cost. ({itemId})");

Expand Down
4 changes: 4 additions & 0 deletions Content.Server/Administration/Commands/SetOutfitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Station;
using Robust.Shared.Console;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
Expand Down Expand Up @@ -92,6 +93,9 @@ public static bool SetOutfit(EntityUid target, string gear, IEntityManager entit
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
var prefs = preferencesManager.GetPreferences(userId);
profile = prefs.SelectedCharacter as HumanoidCharacterProfile;

if (profile != null)
startingGear = IoCManager.Resolve<IEntityManager>().System<SharedStationSpawningSystem>().ApplySubGear(startingGear, profile);
}

var invSystem = entityManager.System<InventorySystem>();
Expand Down
7 changes: 7 additions & 0 deletions Content.Server/Atmos/Components/FlammableComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ public sealed partial class FlammableComponent : Component
[DataField]
public bool CanExtinguish = true;

/// <summary>
/// Should the component ignore fire protection when on fire?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool IgnoreFireProtection = false;

/// <summary>
/// How many firestacks should be applied to component when being set on fire?
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions Content.Server/Atmos/Components/IgniteFromGasComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Content.Shared._Shitmed.Targeting;
using Content.Shared.Atmos;

namespace Content.Server.Atmos.Components;

/// <summary>
/// Component that can be used to add (or remove) fire stacks when exposed to a type of gas, unless wearing ignition immunity.
/// Don't add this component directly, instead attach a body part that has IgniteFromGasPartComponent.
/// </summary>
[RegisterComponent]
public sealed partial class IgniteFromGasComponent : Component
{
/// <summary>
/// What type of gas triggers ignition.
/// Right now only one type of gas can be set, instead of different gasses per each body part.
/// </summary>
[DataField]
public Gas Gas;

/// <summary>
/// The total calculated fire stacks to apply every second without immunity.
/// </summary>
[DataField]
public float FireStacksPerUpdate = 0f;

/// <summary>
/// If this entity is currently not self-igniting.
/// </summary>
public bool HasImmunity => FireStacksPerUpdate == 0;

/// <summary>
/// The base amount of fire stacks to apply every second without immunity.
/// </summary>
[DataField]
public float BaseFireStacksPerUpdate = 0.13f;

/// <summary>
/// The body parts that are vulnerable to ignition when exposed, and their fire stack values.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Dictionary<TargetBodyPart, float> IgnitableBodyParts = new();

/// <summary>
/// How many moles of the gas is needed to trigger ignition.
/// </summary>
[DataField]
public float MolesToIgnite = 0.5f;
}
16 changes: 16 additions & 0 deletions Content.Server/Atmos/Components/IgniteFromGasImmunityComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Content.Shared._Shitmed.Targeting;

namespace Content.Server.Atmos.Components;

/// <summary>
/// Component that is used on clothing to prevent ignition when exposed to a specific gas.
/// </summary>
[RegisterComponent]
public sealed partial class IgniteFromGasImmunityComponent : Component
{
// <summary>
// Which body parts are given ignition immunity.
// </summary>
[DataField(required: true)]
public HashSet<TargetBodyPart> Parts;
}
23 changes: 23 additions & 0 deletions Content.Server/Atmos/Components/IgniteFromGasPartComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Shared.Atmos;

namespace Content.Server.Atmos.Components;

/// <summary>
/// Component that can be used on body parts to add fire stacks and trigger ignition
/// when the body part is exposed to a type of gas, unless wearing ignition immunity.
/// </summary>
[RegisterComponent]
public sealed partial class IgniteFromGasPartComponent : Component
{
/// <summary>
/// What type of gas triggers ignition.
/// </summary>
[DataField(required: true)]
public Gas Gas;

/// <summary>
/// How many fire stacks this body part applies when exposed.
/// </summary>
[DataField]
public float FireStacks = 0.02f;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ private void InitializeBreathTool()
SubscribeLocalEvent<BreathToolComponent, ComponentShutdown>(OnBreathToolShutdown);
}

private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args)
private void OnBreathToolShutdown(Entity<BreathToolComponent> entity, ref ComponentShutdown args)
{
DisconnectInternals(component);
DisconnectInternals(entity);
}

public void DisconnectInternals(BreathToolComponent component)
public void DisconnectInternals(Entity<BreathToolComponent> entity)
{
var old = component.ConnectedInternalsEntity;
component.ConnectedInternalsEntity = null;
var old = entity.Comp.ConnectedInternalsEntity;
entity.Comp.ConnectedInternalsEntity = null;

if (TryComp<InternalsComponent>(old, out var internalsComponent))
{
_internals.DisconnectBreathTool((old.Value, internalsComponent));
_internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner);
}

component.IsFunctional = false;
entity.Comp.IsFunctional = false;
}
}
Loading

0 comments on commit e68e0c3

Please sign in to comment.