Merge pull request #735 from space-syndicate/upstream-sync

Upstream sync
This commit is contained in:
Morb
2023-01-13 06:18:58 +03:00
committed by GitHub
141 changed files with 14085 additions and 8892 deletions

View File

@@ -3,7 +3,7 @@
## Описание PR
<!-- Ниже опишите ваш Pull Request. Что он изменяет? На что еще это может повлиять? Постарайтесь описать все внесённые вами изменения! -->
**Скриншоты**
**Медиа**
<!-- Если приемлемо, добавьте скриншоты для демонстрации вашего PR. Если ваш PR представляет собой визуальное изменение, добавьте
скриншоты, иначе он может быть закрыт. -->
@@ -12,22 +12,25 @@
- [ ] PR полностью завершён и мне не нужна помощь чтобы его закончить.
- [ ] Я внимательно просмотрел все свои изменения и багов в них не нашёл.
- [ ] Я запускал локальный сервер со своими изменениями и всё протестировал.
- [ ] Я добавил скриншот/видео демонстрации PR в игре, **или** этот PR этого не требует.
**Изменения**
<!--
Здесь вы можете написать список изменений, который будет автоматически добавлен в игру, когда ваш PR будет принят
Поддерживается 4 типа значков: add, remove, tweak, fix. Выбрать правильные не должно составить для вас труда.
Здесь вы можете написать список изменений, который будет автоматически добавлен в игру, когда ваш PR будет принят.
Вы можете указать своё имя после символа :cl: именно оно будет отображаться в журнале изменений (иначе будет использоваться ваше имя на GitHub)
Например: :cl: Ian
В журнал изменений следует помещать только то, что действительно важно игрокам. Вещи вроде "Рефакторинг системы X" не должны быть в журнале изменений.
В журнал изменений следует помещать только то, что действительно важно игрокам.
В списке изменений тип значка не является часть предложения, поэтому явно указывайте - Добавлен, Удалён, Изменён.
плохо: - add: Новый инструмент для инженеров
хорошо: - add: Добавлен новый инструмент для инженеров
Вы можете указать своё имя после символа :cl: именно оно будет отображаться в журнале изменений (иначе будет использоваться ваше имя на GitHub)
Например: :cl: Ian
-->
:cl:
- add: Добавлено веселье!
- remove: Убрано веселье!
- tweak: Изменено веселье!
- fix: Исправлено веселье!

View File

@@ -82,7 +82,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
volumeLabel.HorizontalExpand = true;
volumeLabel.Margin = new Thickness(0, 4);
volumeLabel.Text = Loc.GetString("admin-solutions-window-volume-label",
("currentVolume", solution.CurrentVolume),
("currentVolume", solution.Volume),
("maxVolume", solution.MaxVolume));
var capacityBox = new BoxContainer();
@@ -116,16 +116,16 @@ namespace Content.Client.Administration.UI.ManageSolutions
private void UpdateThermalBox(Solution solution)
{
ThermalBox.DisposeAllChildren();
var heatCap = solution.GetHeatCapacity(null);
var specificHeatLabel = new Label();
specificHeatLabel.HorizontalExpand = true;
specificHeatLabel.Margin = new Thickness(0, 1);
specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", solution.SpecificHeat.ToString("G3")));
specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", heatCap.ToString("G3")));
var heatCapacityLabel = new Label();
heatCapacityLabel.HorizontalExpand = true;
heatCapacityLabel.Margin = new Thickness(0, 1);
heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", solution.HeatCapacity.ToString("G3")));
heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", (heatCap/solution.Volume.Float()).ToString("G3")));
// Temperature entry:
var temperatureBox = new BoxContainer();
@@ -161,7 +161,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
var thermalEnergySpin = new FloatSpinBox(1, 2);
thermalEnergySpin.HorizontalExpand = true;
thermalEnergySpin.Margin = new Thickness(0, 1);
thermalEnergySpin.Value = solution.ThermalEnergy;
thermalEnergySpin.Value = solution.Temperature * heatCap;
thermalEnergySpin.OnValueChanged += SetThermalEnergy;
thermalEnergyBox.AddChild(thermalEnergyLabel);

View File

@@ -9,42 +9,26 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
{
base.Initialize();
SubscribeLocalEvent<TypingIndicatorComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, TypingIndicatorComponent component, ComponentInit args)
{
if (!TryComp(uid, out SpriteComponent? sprite))
if (args.Sprite == null)
return;
if (!_prototypeManager.TryIndex<TypingIndicatorPrototype>(component.Prototype, out var proto))
{
Logger.Error($"Unknown typing indicator id: {component.Prototype}");
return;
}
var layer = sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
sprite.LayerSetRSI(layer, proto.SpritePath);
sprite.LayerSetState(layer, proto.TypingState);
sprite.LayerSetShader(layer, proto.Shader);
sprite.LayerSetOffset(layer, proto.Offset);
sprite.LayerSetVisible(layer, false);
}
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if (!TryComp(uid, out SpriteComponent? sprite))
return;
args.Component.TryGetData(TypingIndicatorVisuals.IsTyping, out bool isTyping);
if (sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer))
{
sprite.LayerSetVisible(layer, isTyping);
}
var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer);
if (!layerExists)
layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
args.Sprite.LayerSetRSI(layer, proto.SpritePath);
args.Sprite.LayerSetState(layer, proto.TypingState);
args.Sprite.LayerSetShader(layer, proto.Shader);
args.Sprite.LayerSetOffset(layer, proto.Offset);
args.Sprite.LayerSetVisible(layer, isTyping);
}
}

View File

@@ -1,4 +1,6 @@
using System.Linq;
using Content.Client.Administration.Managers;
using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Content.Shared.IdentityManagement;
using Robust.Client.GameObjects;
@@ -7,6 +9,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.Client.ContextMenu.UI
{
@@ -14,8 +17,11 @@ namespace Content.Client.ContextMenu.UI
{
public const string StyleClassEntityMenuCountText = "contextMenuCount";
[Dependency] private IEntityManager _entityManager = default!;
[Dependency] private IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private AdminSystem _adminSystem;
/// <summary>
/// The entity that can be accessed by interacting with this element.
@@ -37,6 +43,8 @@ namespace Content.Client.ContextMenu.UI
{
IoCManager.InjectDependencies(this);
_adminSystem = _entityManager.System<AdminSystem>();
CountLabel = new Label { StyleClasses = { StyleClassEntityMenuCountText } };
Icon.AddChild(new LayoutContainer() { Children = { EntityIcon, CountLabel } });
@@ -79,10 +87,19 @@ namespace Content.Client.ContextMenu.UI
EntityIcon.Sprite = _entityManager.GetComponentOrNull<ISpriteComponent>(entity);
var admin = IoCManager.Resolve<IClientAdminManager>();
if (_adminManager.HasFlag(AdminFlags.Admin | AdminFlags.Debug))
{
var representation = _entityManager.ToPrettyString(entity.Value);
var name = representation.Name;
var id = representation.Uid;
var prototype = representation.Prototype;
var playerName =
representation.Session?.Name ??
_adminSystem.PlayerList.FirstOrDefault(player => player.EntityUid == entity)?.Username;
var deleted = representation.Deleted;
if (admin.HasFlag(AdminFlags.Admin | AdminFlags.Debug))
Text = _entityManager.ToPrettyString(entity.Value);
Text = $"{name} ({id}{(representation.Prototype != null ? $", {prototype}" : "")}{(playerName != null ? $", {playerName}" : "")}){(deleted ? "D" : "")}";
}
else
Text = Identity.Name(entity.Value, _entityManager, _playerManager.LocalPlayer!.ControlledEntity!);
}

View File

@@ -5,11 +5,7 @@ using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.MobState.Components;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Content.Client.HealthOverlay.UI
@@ -22,7 +18,7 @@ namespace Content.Client.HealthOverlay.UI
public HealthOverlayGui(EntityUid entity)
{
IoCManager.InjectDependencies(this);
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(this);
UserInterfaceManager.WindowRoot.AddChild(this);
SeparationOverride = 0;
Orientation = LayoutOrientation.Vertical;

View File

@@ -1,10 +1,10 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Chemistry;
@@ -24,6 +24,25 @@ public sealed class SolutionSystemTests
solutions:
beaker:
maxVol: 50
- type: reagent
id: TestReagentA
name: nah
desc: nah
physicalDesc: nah
- type: reagent
id: TestReagentB
name: nah
desc: nah
physicalDesc: nah
- type: reagent
id: TestReagentC
specificHeat: 2.0
name: nah
desc: nah
physicalDesc: nah
";
[Test]
public async Task TryAddTwoNonReactiveReagent()
@@ -32,6 +51,7 @@ public sealed class SolutionSystemTests
var server = pairTracker.Pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();
var containerSystem = entityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
var testMap = await PoolManager.CreateTestMap(pairTracker);
var coordinates = testMap.GridCoords;
@@ -50,12 +70,12 @@ public sealed class SolutionSystemTests
Assert.That(containerSystem
.TryGetSolution(beaker, "beaker", out var solution));
solution.AddSolution(originalWater);
solution.AddSolution(originalWater, protoMan);
Assert.That(containerSystem
.TryAddSolution(beaker, solution, oilAdded));
solution.ContainsReagent("Water", out var water);
solution.ContainsReagent("Oil", out var oil);
solution.TryGetReagent("Water", out var water);
solution.TryGetReagent("Oil", out var oil);
Assert.That(water, Is.EqualTo(waterQuantity));
Assert.That(oil, Is.EqualTo(oilQuantity));
});
@@ -74,6 +94,7 @@ public sealed class SolutionSystemTests
var testMap = await PoolManager.CreateTestMap(pairTracker);
var entityManager = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();
var containerSystem = entityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
var coordinates = testMap.GridCoords;
@@ -91,12 +112,12 @@ public sealed class SolutionSystemTests
Assert.That(containerSystem
.TryGetSolution(beaker, "beaker", out var solution));
solution.AddSolution(originalWater);
solution.AddSolution(originalWater, protoMan);
Assert.That(containerSystem
.TryAddSolution(beaker, solution, oilAdded), Is.False);
solution.ContainsReagent("Water", out var water);
solution.ContainsReagent("Oil", out var oil);
solution.TryGetReagent("Water", out var water);
solution.TryGetReagent("Oil", out var oil);
Assert.That(water, Is.EqualTo(waterQuantity));
Assert.That(oil, Is.EqualTo(FixedPoint2.Zero));
});
@@ -113,13 +134,14 @@ public sealed class SolutionSystemTests
var entityManager = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();
var testMap = await PoolManager.CreateTestMap(pairTracker);
var containerSystem = entityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
var coordinates = testMap.GridCoords;
EntityUid beaker;
await server.WaitAssertion(() =>
await server.WaitAssertion((System.Action)(() =>
{
int ratio = 9;
int threshold = 20;
@@ -133,22 +155,22 @@ public sealed class SolutionSystemTests
Assert.That(containerSystem
.TryGetSolution(beaker, "beaker", out var solution));
solution.AddSolution(originalWater);
solution.AddSolution(originalWater, protoMan);
Assert.That(containerSystem
.TryMixAndOverflow(beaker, solution, oilAdded, threshold, out var overflowingSolution));
Assert.That(solution.CurrentVolume, Is.EqualTo(FixedPoint2.New(threshold)));
solution.ContainsReagent("Water", out var waterMix);
solution.ContainsReagent("Oil", out var oilMix);
Assert.That((FixedPoint2) solution.Volume, Is.EqualTo(FixedPoint2.New(threshold)));
solution.TryGetReagent("Water", out var waterMix);
solution.TryGetReagent("Oil", out var oilMix);
Assert.That(waterMix, Is.EqualTo(FixedPoint2.New(threshold / (ratio + 1))));
Assert.That(oilMix, Is.EqualTo(FixedPoint2.New(threshold / (ratio + 1) * ratio)));
Assert.That(overflowingSolution.CurrentVolume, Is.EqualTo(FixedPoint2.New(80)));
overflowingSolution.ContainsReagent("Water", out var waterOverflow);
overflowingSolution.ContainsReagent("Oil", out var oilOverFlow);
Assert.That((FixedPoint2) overflowingSolution.Volume, Is.EqualTo(FixedPoint2.New(80)));
overflowingSolution.TryGetReagent("Water", out var waterOverflow);
overflowingSolution.TryGetReagent("Oil", out var oilOverFlow);
Assert.That(waterOverflow, Is.EqualTo(waterQuantity - waterMix));
Assert.That(oilOverFlow, Is.EqualTo(oilQuantity - oilMix));
});
}));
await pairTracker.CleanReturnAsync();
}
@@ -161,6 +183,7 @@ public sealed class SolutionSystemTests
var server = pairTracker.Pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();
var containerSystem = entityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
var testMap = await PoolManager.CreateTestMap(pairTracker);
var coordinates = testMap.GridCoords;
@@ -181,7 +204,7 @@ public sealed class SolutionSystemTests
Assert.That(containerSystem
.TryGetSolution(beaker, "beaker", out var solution));
solution.AddSolution(originalWater);
solution.AddSolution(originalWater, protoMan);
Assert.That(containerSystem
.TryMixAndOverflow(beaker, solution, oilAdded, threshold, out _),
Is.False);
@@ -189,4 +212,43 @@ public sealed class SolutionSystemTests
await pairTracker.CleanReturnAsync();
}
[Test]
public async Task TestTemperatureCalculations()
{
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes });
var server = pairTracker.Pair.Server;
var protoMan = server.ResolveDependency<IPrototypeManager>();
const float temp = 100.0f;
// Adding reagent with adjusts temperature
await server.WaitAssertion(() =>
{
var solution = new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp };
Assert.That(solution.Temperature, Is.EqualTo(temp * 1));
solution.AddSolution(new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp * 3 }, protoMan);
Assert.That(solution.Temperature, Is.EqualTo(temp * 2));
solution.AddSolution(new Solution("TestReagentB", FixedPoint2.New(100)) { Temperature = temp * 5 }, protoMan);
Assert.That(solution.Temperature, Is.EqualTo(temp * 3));
});
// adding solutions combines thermal energy
await server.WaitAssertion(() =>
{
var solutionOne = new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp };
var solutionTwo = new Solution("TestReagentB", FixedPoint2.New(100)) { Temperature = temp };
solutionTwo.AddReagent("TestReagentC", FixedPoint2.New(100));
var thermalEnergyOne = solutionOne.GetHeatCapacity(protoMan) * solutionOne.Temperature;
var thermalEnergyTwo = solutionTwo.GetHeatCapacity(protoMan) * solutionTwo.Temperature;
solutionOne.AddSolution(solutionTwo, protoMan);
Assert.That(solutionOne.GetHeatCapacity(protoMan) * solutionOne.Temperature, Is.EqualTo(thermalEnergyOne + thermalEnergyTwo));
});
await pairTracker.CleanReturnAsync();
}
}

View File

@@ -46,7 +46,7 @@ namespace Content.Server.Administration.Commands
return;
}
if (solution.HeatCapacity <= 0.0f)
if (solution.GetHeatCapacity(null) <= 0.0f)
{
if(quantity != 0.0f)
{

View File

@@ -92,7 +92,7 @@ namespace Content.Server.Administration.Systems
{
_quickDialog.OpenDialog(player, "Subtle Message", "Message", "Popup Message", (string message, string popupMessage) =>
{
_prayerSystem.SendSubtleMessage(targetActor.PlayerSession, message, popupMessage == "" ? Loc.GetString("prayer-popup-subtle-default") : popupMessage);
_prayerSystem.SendSubtleMessage(targetActor.PlayerSession, player, message, popupMessage == "" ? Loc.GetString("prayer-popup-subtle-default") : popupMessage);
});
};
prayerVerb.Impact = LogImpact.Low;

View File

@@ -295,17 +295,17 @@ namespace Content.Server.Administration.Systems
return new WebhookPayload
{
Username = username,
AvatarUrl = _avatarUrl,
AvatarUrl = string.IsNullOrWhiteSpace(_avatarUrl) ? null : _avatarUrl,
Embeds = new List<Embed>
{
new Embed
new()
{
Description = messages,
Color = color,
Footer = new EmbedFooter
{
Text = $"{serverName} ({round})",
IconUrl = _footerIconUrl,
IconUrl = string.IsNullOrWhiteSpace(_footerIconUrl) ? null : _footerIconUrl
},
},
},
@@ -438,7 +438,7 @@ namespace Content.Server.Administration.Systems
public string Username { get; set; } = "";
[JsonPropertyName("avatar_url")]
public string AvatarUrl { get; set; } = "";
public string? AvatarUrl { get; set; } = "";
[JsonPropertyName("embeds")]
public List<Embed>? Embeds { get; set; } = null;
@@ -479,7 +479,7 @@ namespace Content.Server.Administration.Systems
public string Text { get; set; } = "";
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; } = "";
public string? IconUrl { get; set; }
public EmbedFooter()
{

View File

@@ -75,7 +75,7 @@ public sealed class AFKSystem : EntitySystem
foreach (var session in Filter.GetAllPlayers())
{
if (session.Status != SessionStatus.InGame) continue; // Corvax-Queue: Don't kick players in queue
if (session.Status != SessionStatus.InGame) continue;
var pSession = (IPlayerSession) session;
var isAfk = _afkManager.IsAfk(pSession);

View File

@@ -98,7 +98,7 @@ namespace Content.Server.Animals.Systems
if (!_solutionContainerSystem.TryGetRefillableSolution(ev.ContainerUid, out var targetSolution))
return;
var quantity = solution.TotalVolume;
var quantity = solution.Volume;
if(quantity == 0)
{
_popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, ev.UserUid);

View File

@@ -29,8 +29,9 @@ namespace Content.Server.Atmos.EntitySystems
if (airtight.FixAirBlockedDirectionInitialize)
{
var moveEvent = new MoveEvent(airtight.Owner, default, default, Angle.Zero, xform.LocalRotation, xform, false);
OnAirtightRotated(uid, airtight, ref moveEvent);
var moveEvent = new MoveEvent(uid, default, default, Angle.Zero, xform.LocalRotation, xform, false);
if (AirtightRotate(uid, airtight, ref moveEvent))
return;
}
UpdatePosition(airtight);
@@ -46,7 +47,7 @@ namespace Content.Server.Atmos.EntitySystems
if (MetaData(grid.Owner).EntityLifeStage > EntityLifeStage.MapInitialized) return;
}
SetAirblocked(airtight, false, xform);
SetAirblocked(uid, airtight, false, xform);
}
private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent airtight, ref AnchorStateChangedEvent args)
@@ -77,16 +78,23 @@ namespace Content.Server.Atmos.EntitySystems
}
private void OnAirtightRotated(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev)
{
AirtightRotate(uid, airtight, ref ev);
}
private bool AirtightRotate(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev)
{
if (!airtight.RotateAirBlocked || airtight.InitialAirBlockedDirection == (int)AtmosDirection.Invalid)
return;
return false;
airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation);
UpdatePosition(airtight, ev.Component);
RaiseLocalEvent(uid, new AirtightChanged(airtight), true);
var airtightEv = new AirtightChanged(uid, airtight);
RaiseLocalEvent(uid, ref airtightEv);
return true;
}
public void SetAirblocked(AirtightComponent airtight, bool airblocked, TransformComponent? xform = null)
public void SetAirblocked(EntityUid uid, AirtightComponent airtight, bool airblocked, TransformComponent? xform = null)
{
if (airtight.AirBlocked == airblocked)
return;
@@ -95,7 +103,8 @@ namespace Content.Server.Atmos.EntitySystems
airtight.AirBlocked = airblocked;
UpdatePosition(airtight, xform);
RaiseLocalEvent(airtight.Owner, new AirtightChanged(airtight), true);
var airtightEv = new AirtightChanged(uid, airtight);
RaiseLocalEvent(uid, ref airtightEv);
}
public void UpdatePosition(AirtightComponent airtight, TransformComponent? xform = null)
@@ -143,13 +152,6 @@ namespace Content.Server.Atmos.EntitySystems
}
}
public sealed class AirtightChanged : EntityEventArgs
{
public AirtightComponent Airtight;
public AirtightChanged(AirtightComponent airtight)
{
Airtight = airtight;
}
}
[ByRefEvent]
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight);
}

View File

@@ -76,7 +76,7 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
public void PlayGlobalSoundCommand(IConsoleShell shell, string argStr, string[] args)
{
Filter filter;
var audio = AudioParams.Default.WithVolume(-8);
var audio = AudioParams.Default;
bool replay = true;
@@ -139,6 +139,7 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
break;
}
audio = audio.AddVolume(-8);
PlayAdminGlobal(filter, args[0], audio, replay);
}
}

View File

@@ -23,8 +23,8 @@ namespace Content.Server.Body.Components
/// <summary>
/// Initial internal solution storage volume
/// </summary>
[DataField("maxVolume")]
public FixedPoint2 InitialMaxVolume { get; private set; } = FixedPoint2.New(50);
[DataField("initialMaxVolume", readOnly: true)]
public readonly FixedPoint2 InitialMaxVolume = FixedPoint2.New(50);
/// <summary>
/// Time in seconds between reagents being ingested and them being

View File

@@ -96,7 +96,7 @@ public sealed class BloodstreamSystem : EntitySystem
continue;
// First, let's refresh their blood if possible.
if (bloodstream.BloodSolution.CurrentVolume < bloodstream.BloodSolution.MaxVolume)
if (bloodstream.BloodSolution.Volume < bloodstream.BloodSolution.MaxVolume)
TryModifyBloodLevel(uid, bloodstream.BloodRefreshAmount, bloodstream);
// Next, let's remove some blood from them according to their bleed level.
@@ -258,7 +258,7 @@ public sealed class BloodstreamSystem : EntitySystem
if (!Resolve(uid, ref component))
return 0.0f;
return (component.BloodSolution.CurrentVolume / component.BloodSolution.MaxVolume).Float();
return component.BloodSolution.FillFraction;
}
public void SetBloodLossThreshold(EntityUid uid, float threshold, BloodstreamComponent? comp = null)
@@ -284,13 +284,13 @@ public sealed class BloodstreamSystem : EntitySystem
// since we also wanna handle moving it to the temporary solution
// and then spilling it if necessary.
var newSol = component.BloodSolution.SplitSolution(-amount);
component.BloodTemporarySolution.AddSolution(newSol);
component.BloodTemporarySolution.AddSolution(newSol, _prototypeManager);
if (component.BloodTemporarySolution.CurrentVolume > component.BleedPuddleThreshold)
if (component.BloodTemporarySolution.Volume > component.BleedPuddleThreshold)
{
// Pass some of the chemstream into the spilled blood.
var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.CurrentVolume / 10);
component.BloodTemporarySolution.AddSolution(temp);
var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10);
component.BloodTemporarySolution.AddSolution(temp, _prototypeManager);
_spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false);
component.BloodTemporarySolution.RemoveAllSolution();
}
@@ -324,11 +324,11 @@ public sealed class BloodstreamSystem : EntitySystem
component.ChemicalSolution.MaxVolume;
var tempSol = new Solution() { MaxVolume = max };
tempSol.AddSolution(component.BloodSolution);
tempSol.AddSolution(component.BloodSolution, _prototypeManager);
component.BloodSolution.RemoveAllSolution();
tempSol.AddSolution(component.BloodTemporarySolution);
tempSol.AddSolution(component.BloodTemporarySolution, _prototypeManager);
component.BloodTemporarySolution.RemoveAllSolution();
tempSol.AddSolution(component.ChemicalSolution);
tempSol.AddSolution(component.ChemicalSolution, _prototypeManager);
component.ChemicalSolution.RemoveAllSolution();
_spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true);
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Body.Organ;
@@ -47,7 +47,7 @@ namespace Content.Server.Body.Systems
delta.Increment(stomach.UpdateInterval);
if (delta.Lifetime > stomach.DigestionDelay)
{
if (stomachSolution.ContainsReagent(delta.ReagentId, out var quant))
if (stomachSolution.TryGetReagent(delta.ReagentId, out var quant))
{
if (quant > delta.Quantity)
quant = delta.Quantity;
@@ -89,8 +89,7 @@ namespace Content.Server.Body.Systems
private void OnComponentInit(EntityUid uid, StomachComponent component, ComponentInit args)
{
var solution = _solutionContainerSystem.EnsureSolution(uid, DefaultSolutionName);
solution.MaxVolume = component.InitialMaxVolume;
_solutionContainerSystem.EnsureSolution(uid, DefaultSolutionName, component.InitialMaxVolume, out _);
}
public bool CanTransferSolution(EntityUid uid, Solution solution,

View File

@@ -1,4 +1,5 @@
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using Robust.Server.GameObjects;
@@ -17,17 +18,17 @@ public sealed partial class BotanySystem
sprite.LayerSetState(0, seed.PlantIconState);
}
var solutionContainer = _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName);
solutionContainer.RemoveAllSolution();
Solution.ReagentQuantity[] reagents = new Solution.ReagentQuantity[seed.Chemicals.Count];
int i = 0;
foreach (var (chem, quantity) in seed.Chemicals)
{
var amount = FixedPoint2.New(quantity.Min);
if (quantity.PotencyDivisor > 0 && seed.Potency > 0)
amount += FixedPoint2.New(seed.Potency / quantity.PotencyDivisor);
amount = FixedPoint2.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max));
solutionContainer.MaxVolume += amount;
solutionContainer.AddReagent(chem, amount);
reagents[i++] = new(chem, amount);
}
_solutionContainerSystem.EnsureSolution(uid, produce.SolutionName, reagents);
}
}

View File

@@ -211,7 +211,7 @@ namespace Content.Server.Botany.Systems
var split =_solutionSystem.Drain(solutionEntity, solution, amount);
if (split.TotalVolume == 0)
if (split.Volume == 0)
{
_popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message",
("owner", args.Used)), args.User);
@@ -220,7 +220,7 @@ namespace Content.Server.Botany.Systems
_popupSystem.PopupCursor(Loc.GetString("plant-holder-component-spray-message",
("owner", uid),
("amount", split.TotalVolume)), args.User, PopupType.Medium);
("amount", split.Volume)), args.User, PopupType.Medium);
_solutionSystem.TryAddSolution(targetEntity, targetSolution, split);
@@ -284,7 +284,7 @@ namespace Content.Server.Botany.Systems
{
// This deliberately discards overfill.
_solutionSystem.TryAddSolution(args.Used, solution2,
_solutionSystem.SplitSolution(args.Used, solution2, solution2.TotalVolume));
_solutionSystem.SplitSolution(args.Used, solution2, solution2.Volume));
ForceUpdateByExternalCause(uid, component);
}
@@ -780,7 +780,7 @@ namespace Content.Server.Botany.Systems
if (!_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution))
return;
if (solution.TotalVolume > 0 && component.MutationLevel < 25)
if (solution.Volume > 0 && component.MutationLevel < 25)
{
var amt = FixedPoint2.New(1);
foreach (var (reagentId, quantity) in _solutionSystem.RemoveEachReagent(uid, solution, amt))

View File

@@ -1,7 +1,6 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Chat.TypingIndicator;
using Robust.Server.GameObjects;
using Robust.Shared.Players;
namespace Content.Server.Chat.TypingIndicator;

View File

@@ -1,4 +1,4 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Administration.Logs;
@@ -6,6 +6,7 @@ using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Foam;
using Content.Shared.Inventory;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.Components
{
@@ -14,6 +15,7 @@ namespace Content.Server.Chemistry.Components
public sealed class FoamSolutionAreaEffectComponent : SolutionAreaEffectComponent
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
public new const string SolutionName = "solutionArea";
@@ -25,7 +27,7 @@ namespace Content.Server.Chemistry.Components
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance) &&
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{
appearance.SetData(FoamVisuals.Color, solution.Color.WithAlpha(0.80f));
appearance.SetData(FoamVisuals.Color, solution.GetColor(_proto).WithAlpha(0.80f));
}
}
@@ -60,7 +62,7 @@ namespace Content.Server.Chemistry.Components
var bloodstreamSys = EntitySystem.Get<BloodstreamSystem>();
var cloneSolution = solution.Clone();
var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection),
var transferAmount = FixedPoint2.Min(cloneSolution.Volume * solutionFraction * (1 - protection),
bloodstream.ChemicalSolution.AvailableVolume);
var transferSolution = cloneSolution.SplitSolution(transferAmount);

View File

@@ -25,7 +25,7 @@ namespace Content.Server.Chemistry.Components
{
var solutionSys = _entMan.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
return solutionSys.TryGetSolution(Owner, SolutionName, out var solution)
? new HyposprayComponentState(solution.CurrentVolume, solution.MaxVolume)
? new HyposprayComponentState(solution.Volume, solution.MaxVolume)
: new HyposprayComponentState(FixedPoint2.Zero, FixedPoint2.Zero);
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Administration.Logs;
@@ -7,6 +7,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Smoking;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.Components
{
@@ -15,6 +16,7 @@ namespace Content.Server.Chemistry.Components
public sealed class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
public new const string SolutionName = "solutionArea";
@@ -24,7 +26,7 @@ namespace Content.Server.Chemistry.Components
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance) &&
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{
appearance.SetData(SmokeVisuals.Color, solution.Color);
appearance.SetData(SmokeVisuals.Color, solution.GetColor(_proto));
}
}
@@ -42,7 +44,7 @@ namespace Content.Server.Chemistry.Components
var chemistry = EntitySystem.Get<ReactiveSystem>();
var cloneSolution = solution.Clone();
var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.ChemicalSolution.AvailableVolume);
var transferAmount = FixedPoint2.Min(cloneSolution.Volume * solutionFraction, bloodstream.ChemicalSolution.AvailableVolume);
var transferSolution = cloneSolution.SplitSolution(transferAmount);
foreach (var reagentQuantity in transferSolution.Contents.ToArray())

View File

@@ -194,14 +194,14 @@ namespace Content.Server.Chemistry.Components
public void TryAddSolution(Solution solution)
{
if (solution.TotalVolume == 0)
if (solution.Volume == 0)
return;
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solutionArea))
return;
var addSolution =
solution.SplitSolution(FixedPoint2.Min(solution.TotalVolume, solutionArea.AvailableVolume));
solution.SplitSolution(FixedPoint2.Min(solution.Volume, solutionArea.AvailableVolume));
EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner, solutionArea, addSolution);

View File

@@ -66,7 +66,7 @@ namespace Content.Server.Chemistry.EntitySystems
var outputContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.OutputSlotName);
var bufferReagents = bufferSolution.Contents;
var bufferCurrentVolume = bufferSolution.CurrentVolume;
var bufferCurrentVolume = bufferSolution.Volume;
var state = new ChemMasterBoundUserInterfaceState(
chemMaster.Mode, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer),
@@ -288,7 +288,7 @@ namespace Content.Server.Chemistry.EntitySystems
return false;
}
if (solution.TotalVolume == 0)
if (solution.Volume == 0)
{
if (user.HasValue)
_popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-empty-text"), user.Value);
@@ -296,7 +296,7 @@ namespace Content.Server.Chemistry.EntitySystems
}
// ReSharper disable once InvertIf
if (neededVolume > solution.CurrentVolume)
if (neededVolume > solution.Volume)
{
if (user.HasValue)
_popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-low-text"), user.Value);
@@ -343,12 +343,12 @@ namespace Content.Server.Chemistry.EntitySystems
if (!TryComp(container, out ServerStorageComponent? storage))
return null;
var pills = storage.Storage?.ContainedEntities.Select(pill =>
var pills = storage.Storage?.ContainedEntities.Select((Func<EntityUid, (string, FixedPoint2 quantity)>) (pill =>
{
_solutionContainerSystem.TryGetSolution(pill, SharedChemMaster.PillSolutionName, out var solution);
var quantity = solution?.CurrentVolume ?? FixedPoint2.Zero;
return (Name(pill), quantity);
}).ToList();
var quantity = solution?.Volume ?? FixedPoint2.Zero;
return ((string, FixedPoint2 quantity))(Name(pill), quantity:(FixedPoint2) quantity);
})).ToList();
return pills is null
? null
@@ -360,7 +360,7 @@ namespace Content.Server.Chemistry.EntitySystems
var reagents = solution.Contents
.Select(reagent => (reagent.ReagentId, reagent.Quantity)).ToList();
return new ContainerInfo(name, true, solution.CurrentVolume, solution.MaxVolume, reagents);
return new ContainerInfo(name, true, solution.Volume, solution.MaxVolume, reagents);
}
}
}

View File

@@ -145,7 +145,7 @@ public sealed partial class ChemistrySystem
{
_solutions.TryGetSolution(uid, InjectorComponent.SolutionName, out var solution);
var currentVolume = solution?.CurrentVolume ?? FixedPoint2.Zero;
var currentVolume = solution?.Volume ?? FixedPoint2.Zero;
var maxVolume = solution?.MaxVolume ?? FixedPoint2.Zero;
args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState);
@@ -323,7 +323,7 @@ public sealed partial class ChemistrySystem
removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
_popup.PopupEntity(Loc.GetString("injector-component-inject-success-message",
("amount", removedSolution.TotalVolume),
("amount", removedSolution.Volume),
("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, user);
Dirty(component);
@@ -333,7 +333,7 @@ public sealed partial class ChemistrySystem
private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill)
{
if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
|| solution.CurrentVolume == 0)
|| solution.Volume == 0)
{
return;
}
@@ -363,7 +363,7 @@ public sealed partial class ChemistrySystem
}
_popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message",
("amount", removedSolution.TotalVolume),
("amount", removedSolution.Volume),
("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
Dirty(component);
@@ -374,7 +374,7 @@ public sealed partial class ChemistrySystem
{
// Automatically set syringe to draw after completely draining it.
if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)
&& solution.CurrentVolume == 0)
&& solution.Volume == 0)
{
component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw;
}
@@ -399,7 +399,7 @@ public sealed partial class ChemistrySystem
}
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.DrawAvailable, solution.AvailableVolume);
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.Volume, solution.AvailableVolume);
if (realTransferAmount <= 0)
{
@@ -424,7 +424,7 @@ public sealed partial class ChemistrySystem
}
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
("amount", removedSolution.TotalVolume),
("amount", removedSolution.Volume),
("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user);
Dirty(component);
@@ -436,7 +436,7 @@ public sealed partial class ChemistrySystem
var drawAmount = (float) transferAmount;
var bloodAmount = drawAmount;
var chemAmount = 0f;
if (stream.ChemicalSolution.CurrentVolume > 0f) // If they have stuff in their chem stream, we'll draw some of that
if (stream.ChemicalSolution.Volume > 0f) // If they have stuff in their chem stream, we'll draw some of that
{
bloodAmount = drawAmount * 0.85f;
chemAmount = drawAmount * 0.15f;

View File

@@ -77,7 +77,7 @@ namespace Content.Server.Chemistry.EntitySystems
_solutions.TryGetSolution(uid, component.SolutionName, out var hypoSpraySolution);
if (hypoSpraySolution == null || hypoSpraySolution.CurrentVolume == 0)
if (hypoSpraySolution == null || hypoSpraySolution.Volume == 0)
{
_popup.PopupCursor(Loc.GetString("hypospray-component-empty-message"), user);
return true;

View File

@@ -6,6 +6,7 @@ using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Emag.Systems;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -62,7 +63,7 @@ namespace Content.Server.Chemistry.EntitySystems
if (_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var solution))
{
var reagents = solution.Contents.Select(reagent => (reagent.ReagentId, reagent.Quantity)).ToList();
return new ContainerInfo(Name(container.Value), true, solution.CurrentVolume, solution.MaxVolume, reagents);
return new ContainerInfo(Name(container.Value), true, solution.Volume, solution.MaxVolume, reagents);
}
return null;

View File

@@ -110,7 +110,7 @@ public sealed partial class SolutionContainerSystem
{
return !TryGetDrainableSolution(uid, out var solution)
? FixedPoint2.Zero
: solution.CurrentVolume;
: solution.Volume;
}
public float PercentFull(EntityUid uid)
@@ -118,7 +118,7 @@ public sealed partial class SolutionContainerSystem
if (!TryGetDrainableSolution(uid, out var solution) || solution.MaxVolume.Equals(FixedPoint2.Zero))
return 0;
return ((solution.CurrentVolume.Float() / solution.MaxVolume.Float()) * 100);
return solution.FillFraction * 100;
}
public bool TryGetFitsInDispenser(EntityUid owner,

View File

@@ -51,13 +51,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem
foreach (var (name, solutionHolder) in component.Solutions)
{
solutionHolder.Name = name;
if (solutionHolder.MaxVolume == FixedPoint2.Zero)
{
solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume
? solutionHolder.TotalVolume
: solutionHolder.InitialMaxVolume;
}
solutionHolder.ValidateSolution();
UpdateAppearance(uid, solutionHolder);
}
}
@@ -70,14 +64,14 @@ public sealed partial class SolutionContainerSystem : EntitySystem
|| !solutionsManager.Solutions.TryGetValue(examinableComponent.Solution, out var solutionHolder))
return;
if (solutionHolder.Contents.Count == 0)
var primaryReagent = solutionHolder.GetPrimaryReagentId();
if (string.IsNullOrEmpty(primaryReagent))
{
args.PushText(Loc.GetString("shared-solution-container-component-on-examine-empty-container"));
return;
}
var primaryReagent = solutionHolder.GetPrimaryReagentId();
if (!_prototypeManager.TryIndex(primaryReagent, out ReagentPrototype? proto))
{
Logger.Error(
@@ -85,7 +79,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem
return;
}
var colorHex = solutionHolder.Color
var colorHex = solutionHolder.GetColor(_prototypeManager)
.ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
var messageString = "shared-solution-container-component-on-examine-main-text";
@@ -104,9 +98,9 @@ public sealed partial class SolutionContainerSystem : EntitySystem
|| !Resolve(uid, ref appearanceComponent, false))
return;
var filledVolumePercent = Math.Min(1.0f, solution.CurrentVolume.Float() / solution.MaxVolume.Float());
var filledVolumePercent = solution.FillFraction * 100;
appearanceComponent.SetData(SolutionContainerVisuals.VisualState,
new SolutionContainerVisualState(solution.Color, filledVolumePercent));
new SolutionContainerVisualState(solution.GetColor(_prototypeManager), filledVolumePercent));
}
/// <summary>
@@ -139,7 +133,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem
public void RemoveAllSolution(EntityUid uid, Solution solutionHolder)
{
if (solutionHolder.CurrentVolume == 0)
if (solutionHolder.Volume == 0)
return;
solutionHolder.RemoveAllSolution();
@@ -169,8 +163,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem
return;
targetSolution.MaxVolume = capacity;
if (capacity < targetSolution.CurrentVolume)
targetSolution.RemoveSolution(targetSolution.CurrentVolume - capacity);
if (capacity < targetSolution.Volume)
targetSolution.RemoveSolution(targetSolution.Volume - capacity);
UpdateChemicals(targetUid, targetSolution);
}
@@ -188,14 +182,20 @@ public sealed partial class SolutionContainerSystem : EntitySystem
out FixedPoint2 acceptedQuantity, float? temperature = null)
{
acceptedQuantity = targetSolution.AvailableVolume > quantity ? quantity : targetSolution.AvailableVolume;
targetSolution.AddReagent(reagentId, acceptedQuantity, temperature);
if (acceptedQuantity > 0)
UpdateChemicals(targetUid, targetSolution, true);
if (acceptedQuantity <= 0)
return quantity == 0;
if (temperature == null)
targetSolution.AddReagent(reagentId, acceptedQuantity);
else
targetSolution.AddReagent(_prototypeManager.Index<ReagentPrototype>(reagentId), acceptedQuantity, temperature.Value, _prototypeManager);
UpdateChemicals(targetUid, targetSolution, true);
return acceptedQuantity == quantity;
}
/// <summary>
/// Removes reagent of an Id to the container.
/// </summary>
@@ -224,10 +224,10 @@ public sealed partial class SolutionContainerSystem : EntitySystem
public bool TryAddSolution(EntityUid targetUid, Solution? targetSolution, Solution addedSolution)
{
if (targetSolution == null
|| !targetSolution.CanAddSolution(addedSolution) || addedSolution.TotalVolume == 0)
|| !targetSolution.CanAddSolution(addedSolution) || addedSolution.Volume == 0)
return false;
targetSolution.AddSolution(addedSolution);
targetSolution.AddSolution(addedSolution, _prototypeManager);
UpdateChemicals(targetUid, targetSolution, true);
return true;
}
@@ -245,13 +245,13 @@ public sealed partial class SolutionContainerSystem : EntitySystem
if (quantity < 0)
return TryTransferSolution(targetUid, sourceUid, target, source, -quantity);
quantity = FixedPoint2.Min(quantity, target.AvailableVolume, source.CurrentVolume);
quantity = FixedPoint2.Min(quantity, target.AvailableVolume, source.Volume);
if (quantity == 0)
return false;
// TODO after #12428 is merged, this should be made into a function that directly transfers reagents.
// currently this is quite inefficient.
target.AddSolution(source.SplitSolution(quantity));
// TODO This should be made into a function that directly transfers reagents. currently this is quite
// inefficient.
target.AddSolution(source.SplitSolution(quantity), _prototypeManager);
UpdateChemicals(sourceUid, source, false);
UpdateChemicals(targetUid, target, true);
@@ -294,16 +294,16 @@ public sealed partial class SolutionContainerSystem : EntitySystem
FixedPoint2 overflowThreshold,
[NotNullWhen(true)] out Solution? overflowingSolution)
{
if (addedSolution.TotalVolume == 0 || overflowThreshold > targetSolution.MaxVolume)
if (addedSolution.Volume == 0 || overflowThreshold > targetSolution.MaxVolume)
{
overflowingSolution = null;
return false;
}
targetSolution.AddSolution(addedSolution);
targetSolution.AddSolution(addedSolution, _prototypeManager);
UpdateChemicals(targetUid, targetSolution, true);
overflowingSolution = targetSolution.SplitSolution(FixedPoint2.Max(FixedPoint2.Zero,
targetSolution.CurrentVolume - overflowThreshold));
targetSolution.Volume - overflowThreshold));
return true;
}
@@ -320,14 +320,16 @@ public sealed partial class SolutionContainerSystem : EntitySystem
return solutionsMgr.Solutions.TryGetValue(name, out solution);
}
/// <summary>
/// Will ensure a solution is added to given entity even if it's missing solutionContainerManager
/// </summary>
/// <param name="uid">EntityUid to which to add solution</param>
/// <param name="name">name for the solution</param>
/// <param name="solutionsMgr">solution components used in resolves</param>
/// <param name="existed">true if the solution already existed</param>
/// <returns>solution</returns>
public Solution EnsureSolution(EntityUid uid, string name,
public Solution EnsureSolution(EntityUid uid, string name, out bool existed,
SolutionContainerManagerComponent? solutionsMgr = null)
{
if (!Resolve(uid, ref solutionsMgr, false))
@@ -335,15 +337,76 @@ public sealed partial class SolutionContainerSystem : EntitySystem
solutionsMgr = EntityManager.EnsureComponent<SolutionContainerManagerComponent>(uid);
}
if (!solutionsMgr.Solutions.ContainsKey(name))
if (!solutionsMgr.Solutions.TryGetValue(name, out var existing))
{
var newSolution = new Solution() { Name = name };
solutionsMgr.Solutions.Add(name, newSolution);
existed = false;
return newSolution;
}
return solutionsMgr.Solutions[name];
existed = true;
return existing;
}
/// <summary>
/// Will ensure a solution is added to given entity even if it's missing solutionContainerManager
/// </summary>
/// <param name="uid">EntityUid to which to add solution</param>
/// <param name="name">name for the solution</param>
/// <param name="solutionsMgr">solution components used in resolves</param>
/// <returns>solution</returns>
public Solution EnsureSolution(EntityUid uid, string name, SolutionContainerManagerComponent? solutionsMgr = null)
=> EnsureSolution(uid, name, out _, solutionsMgr);
/// <summary>
/// Will ensure a solution is added to given entity even if it's missing solutionContainerManager
/// </summary>
/// <param name="uid">EntityUid to which to add solution</param>
/// <param name="name">name for the solution</param>
/// <param name="minVol">Ensures that the solution's maximum volume is larger than this value./param>
/// <param name="solutionsMgr">solution components used in resolves</param>
/// <returns>solution</returns>
public Solution EnsureSolution(EntityUid uid, string name, FixedPoint2 minVol, out bool existed,
SolutionContainerManagerComponent? solutionsMgr = null)
{
if (!Resolve(uid, ref solutionsMgr, false))
{
solutionsMgr = EntityManager.EnsureComponent<SolutionContainerManagerComponent>(uid);
}
if (!solutionsMgr.Solutions.TryGetValue(name, out var existing))
{
var newSolution = new Solution() { Name = name };
solutionsMgr.Solutions.Add(name, newSolution);
existed = false;
newSolution.MaxVolume = minVol;
return newSolution;
}
existed = true;
existing.MaxVolume = FixedPoint2.Max(existing.MaxVolume, minVol);
return existing;
}
public Solution EnsureSolution(EntityUid uid, string name,
IEnumerable<Solution.ReagentQuantity> reagents,
bool setMaxVol = true,
SolutionContainerManagerComponent? solutionsMgr = null)
{
if (!Resolve(uid, ref solutionsMgr, false))
solutionsMgr = EntityManager.EnsureComponent<SolutionContainerManagerComponent>(uid);
if (!solutionsMgr.Solutions.TryGetValue(name, out var existing))
{
var newSolution = new Solution(reagents, setMaxVol);
solutionsMgr.Solutions.Add(name, newSolution);
return newSolution;
}
existing.SetContents(reagents, setMaxVol);
return existing;
}
/// <summary>
/// Removes an amount from all reagents in a solution, adding it to a new solution.
/// </summary>
@@ -446,10 +509,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem
/// <param name="thermalEnergy">The new value to set the thermal energy to.</param>
public void SetThermalEnergy(EntityUid owner, Solution solution, float thermalEnergy)
{
if (thermalEnergy == solution.ThermalEnergy)
return;
solution.ThermalEnergy = thermalEnergy;
var heatCap = solution.GetHeatCapacity(_prototypeManager);
solution.Temperature = heatCap == 0 ? 0 : thermalEnergy / heatCap;
UpdateChemicals(owner, solution, true);
}
@@ -464,7 +525,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem
if (thermalEnergy == 0.0f)
return;
solution.ThermalEnergy += thermalEnergy;
var heatCap = solution.GetHeatCapacity(_prototypeManager);
solution.Temperature += heatCap == 0 ? 0 : thermalEnergy / heatCap;
UpdateChemicals(owner, solution, true);
}

View File

@@ -52,7 +52,7 @@ namespace Content.Server.Chemistry.EntitySystems
}
var solRemoved = solution.SplitSolution(component.TransferAmount);
var solRemovedVol = solRemoved.TotalVolume;
var solRemovedVol = solRemoved.Volume;
var solToInject = solRemoved.SplitSolution(solRemovedVol * component.TransferEfficiency);

View File

@@ -54,7 +54,7 @@ public sealed class SolutionSpikableSystem : EntitySystem
return;
}
if (targetSolution.CurrentVolume == 0 && !spikableSource.IgnoreEmpty)
if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty)
{
_popupSystem.PopupEntity(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
return;
@@ -66,7 +66,7 @@ public sealed class SolutionSpikableSystem : EntitySystem
targetSolution.MaxVolume,
out var overflow))
{
if (overflow.TotalVolume > 0)
if (overflow.Volume > 0)
{
RaiseLocalEvent(target, new SolutionSpikeOverflowEvent(overflow));
}

View File

@@ -156,7 +156,7 @@ namespace Content.Server.Chemistry.EntitySystems
return FixedPoint2.Zero;
}
if (source.DrainAvailable == 0)
if (source.Volume == 0)
{
sourceEntity.PopupMessage(user,
Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)));
@@ -178,7 +178,7 @@ namespace Content.Server.Chemistry.EntitySystems
return FixedPoint2.Zero;
}
var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.DrainAvailable, target.AvailableVolume));
var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.Volume, target.AvailableVolume));
var solutionSystem = Get<SolutionContainerSystem>();
var solution = solutionSystem.Drain(sourceEntity, source, actualAmount);

View File

@@ -71,7 +71,7 @@ namespace Content.Server.Chemistry.EntitySystems
internal bool TryAddSolution(VaporComponent vapor, Solution solution)
{
if (solution.TotalVolume == 0)
if (solution.Volume == 0)
{
return false;
}
@@ -120,7 +120,7 @@ namespace Content.Server.Chemistry.EntitySystems
}
}
if (contents.CurrentVolume == 0)
if (contents.Volume == 0)
{
// Delete this
EntityManager.QueueDeleteEntity(entity);

View File

@@ -77,7 +77,7 @@ namespace Content.Server.Chemistry.ReactionEffects
if (args.Source == null)
return;
var splitSolution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(args.SolutionEntity, args.Source, args.Source.MaxVolume);
var splitSolution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(args.SolutionEntity, args.Source, args.Source.Volume);
// We take the square root so it becomes harder to reach higher amount values
var amount = (int) Math.Round(_rangeConstant + _rangeMultiplier*Math.Sqrt(args.Quantity.Float()));
amount = Math.Min(amount, _maxRange);
@@ -90,7 +90,7 @@ namespace Content.Server.Chemistry.ReactionEffects
// Weird formulas here but basically when amount increases, solutionFraction gets closer to 0 in a reciprocal manner
// _reagentDilutionFactor defines how fast solutionFraction gets closer to 0
float solutionFraction = 1 / (_reagentDilutionFactor*(amount) + 1);
splitSolution.RemoveSolution(splitSolution.TotalVolume * (1 - solutionFraction));
splitSolution.RemoveSolution(splitSolution.Volume * (1 - solutionFraction));
}
var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity);

View File

@@ -1,5 +1,7 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using static Robust.Shared.Physics.DynamicTree;
namespace Content.Server.Chemistry.ReactionEffects
{
@@ -28,13 +30,12 @@ namespace Content.Server.Chemistry.ReactionEffects
/// Adjusts the temperature of the solution involved in the reaction.
/// </summary>
[DataDefinition]
[Virtual]
public class AdjustSolutionTemperatureEffect : ReagentEffect
public sealed class AdjustSolutionTemperatureEffect : ReagentEffect
{
/// <summary>
/// The total change in the thermal energy of the solution.
/// The change in temperature.
/// </summary>
[DataField("delta", required: true)] protected float Delta;
[DataField("delta", required: true)] private float _delta;
/// <summary>
/// The minimum temperature this effect can reach.
@@ -51,47 +52,62 @@ namespace Content.Server.Chemistry.ReactionEffects
/// </summary>
[DataField("scaled")] private bool _scaled;
/// <summary>
///
/// </summary>
/// <param name="solution"></param>
/// <returns></returns>
protected virtual float GetDeltaT(Solution solution) => Delta;
public override void Effect(ReagentEffectArgs args)
{
var solution = args.Source;
if (solution == null)
if (solution == null || solution.Volume == 0)
return;
var deltaT = GetDeltaT(solution);
if (_scaled)
deltaT = deltaT * (float) args.Quantity;
if (deltaT == 0.0d)
return;
if (deltaT > 0.0d && solution.Temperature >= _maxTemp)
return;
if (deltaT < 0.0d && solution.Temperature <= _minTemp)
return;
solution.Temperature = MathF.Max(MathF.Min(solution.Temperature + deltaT, _minTemp), _maxTemp);
var deltaT = _scaled ? _delta * (float) args.Quantity : _delta;
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
}
}
/// <summary>
/// Adjusts the thermal energy of the solution involved in the reaction.
/// </summary>
public sealed class AdjustSolutionThermalEnergyEffect : AdjustSolutionTemperatureEffect
public sealed class AdjustSolutionThermalEnergyEffect : ReagentEffect
{
protected override float GetDeltaT(Solution solution)
/// <summary>
/// The change in energy.
/// </summary>
[DataField("delta", required: true)] private float _delta;
/// <summary>
/// The minimum temperature this effect can reach.
/// </summary>
[DataField("minTemp")] private float _minTemp = 0.0f;
/// <summary>
/// The maximum temperature this effect can reach.
/// </summary>
[DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity;
/// <summary>
/// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount.
/// </summary>
[DataField("scaled")] private bool _scaled;
public override void Effect(ReagentEffectArgs args)
{
var heatCapacity = solution.HeatCapacity;
if (heatCapacity == 0.0f)
return 0.0f;
return Delta / heatCapacity;
var solution = args.Source;
if (solution == null || solution.Volume == 0)
return;
if (_delta > 0 && solution.Temperature >= _maxTemp)
return;
if (_delta < 0 && solution.Temperature <= _minTemp)
return;
var heatCap = solution.GetHeatCapacity(null);
var deltaT = _scaled
? _delta / heatCap * (float) args.Quantity
: _delta / heatCap;
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
}
}
}

View File

@@ -1,28 +0,0 @@
using Content.Shared.Chemistry.Reagent;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <summary>
/// Requires the solution to be above or below a certain thermal energy.
/// Used for things like explosives.
/// </summary>
public sealed class SolutionThermalEnergy : ReagentEffectCondition
{
[DataField("min")]
public float Min = 0.0f;
[DataField("max")]
public float Max = float.PositiveInfinity;
public override bool Condition(ReagentEffectArgs args)
{
if (args.Source == null)
return false;
if (args.Source.ThermalEnergy < Min)
return false;
if (args.Source.ThermalEnergy > Max)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.Traits.Assorted;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects;
/// <summary>
/// Reset narcolepsy timer
/// </summary>
[UsedImplicitly]
public sealed class ResetNarcolepsy : ReagentEffect
{
/// <summary>
/// The # of seconds the effect resets the narcolepsy timer to
/// </summary>
[DataField("TimerReset")]
public int TimerReset = 600;
public override void Effect(ReagentEffectArgs args)
{
if (args.Scale != 1f)
return;
args.EntityManager.EntitySysManager.GetEntitySystem<NarcolepsySystem>().AdjustNarcolepsyTimer(args.SolutionEntity, TimerReset);
}
}

View File

@@ -1,6 +1,5 @@
using Content.Shared.GameTicking;
using Content.Shared.Damage;
using Content.Shared.Stacks;
using Content.Shared.Examine;
using Content.Shared.Cloning;
using Content.Shared.Atmos;
@@ -18,7 +17,6 @@ using Content.Shared.Chemistry.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Chat.Systems;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Materials;
using Content.Server.Stack;
using Content.Server.Jobs;
@@ -50,8 +48,6 @@ namespace Content.Server.Cloning
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedStackSystem _stackSystem = default!;
[Dependency] private readonly StackSystem _serverStackSystem = default!;
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
@@ -316,7 +312,7 @@ namespace Content.Server.Cloning
}
_spillableSystem.SpillAt(uid, bloodSolution, "PuddleBlood");
_serverStackSystem.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
_material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
clonePod.UsedBiomass = 0;
RemCompDeferred<ActiveCloningPodComponent>(uid);

View File

@@ -123,7 +123,7 @@ namespace Content.Server.Construction
{
if (!bodyQuery.TryGetComponent(ent, out var body) ||
!body.CanCollide ||
(!body.Hard && body.BodyType != BodyType.Static))
!body.Hard)
{
continue;
}

View File

@@ -22,11 +22,11 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
&& system.EntityManager.TryGetComponent(owner, out ExplosiveComponent? explosiveComponent))
{
// Don't explode if there's no solution
if (explodingSolution.CurrentVolume == 0)
if (explodingSolution.Volume == 0)
return;
// Scale the explosion intensity based on the remaining volume of solution
var explosionScaleFactor = (explodingSolution.CurrentVolume.Float() / explodingSolution.MaxVolume.Float());
var explosionScaleFactor = explodingSolution.FillFraction;
// TODO: Perhaps some of the liquid should be discarded as if it's being consumed by the explosion

View File

@@ -67,7 +67,7 @@ public sealed class DoorSystem : SharedDoorSystem
return;
if (door.ChangeAirtight && TryComp(uid, out AirtightComponent? airtight))
_airtightSystem.SetAirblocked(airtight, collidable);
_airtightSystem.SetAirblocked(uid, airtight, collidable);
// Pathfinding / AI stuff.
RaiseLocalEvent(new AccessReaderChangeEvent(uid, collidable));

View File

@@ -78,7 +78,7 @@ public sealed class FireExtinguisherSystem : EntitySystem
{
transfer = solTrans.TransferAmount;
}
transfer = FixedPoint2.Min(transfer, targetSolution.DrainAvailable);
transfer = FixedPoint2.Min(transfer, targetSolution.Volume);
if (transfer > 0)
{

View File

@@ -36,9 +36,6 @@ namespace Content.Server.Fluids.Components
[DataField("spillSound")]
public SoundSpecifier SpillSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
[ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 CurrentVolume => EntitySystem.Get<PuddleSystem>().CurrentVolume(Owner);
[DataField("overflowVolume")]
public FixedPoint2 OverflowVolume = DefaultOverflowVolume;

View File

@@ -85,11 +85,11 @@ namespace Content.Server.Fluids.EntitySystems
// the puddle's remaining volume (making it cleanly zero)
// the drain's remaining volume in its buffer.
var transferSolution = _solutionSystem.SplitSolution(puddle, puddleSolution,
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.CurrentVolume, drainSolution.AvailableVolume));
FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
_solutionSystem.TryAddSolution(drain.Owner, drainSolution, transferSolution);
if (puddleSolution.CurrentVolume <= 0)
if (puddleSolution.Volume <= 0)
{
QueueDel(puddle);
}

View File

@@ -33,12 +33,12 @@ namespace Content.Server.Fluids.EntitySystems
if (evaporationComponent.EvaporationToggle == true)
{
_solutionContainerSystem.SplitSolution(uid, solution,
FixedPoint2.Min(FixedPoint2.New(1), solution.CurrentVolume)); // removes 1 unit, or solution current volume, whichever is lower.
FixedPoint2.Min(FixedPoint2.New(1), solution.Volume)); // removes 1 unit, or solution current volume, whichever is lower.
}
evaporationComponent.EvaporationToggle =
solution.CurrentVolume > evaporationComponent.LowerLimit
&& solution.CurrentVolume < evaporationComponent.UpperLimit;
solution.Volume > evaporationComponent.LowerLimit
&& solution.Volume < evaporationComponent.UpperLimit;
}
}

View File

@@ -60,13 +60,13 @@ public sealed class MoppingSystem : EntitySystem
/// </summary>
private bool TryCreatePuddle(EntityUid user, EntityCoordinates clickLocation, AbsorbentComponent absorbent, Solution absorberSoln)
{
if (absorberSoln.CurrentVolume <= 0)
if (absorberSoln.Volume <= 0)
return false;
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
return false;
var releaseAmount = FixedPoint2.Min(absorbent.ResidueAmount, absorberSoln.CurrentVolume);
var releaseAmount = FixedPoint2.Min(absorbent.ResidueAmount, absorberSoln.Volume);
var releasedSolution = _solutionSystem.SplitSolution(absorbent.Owner, absorberSoln, releaseAmount);
_spillableSystem.SpillAt(mapGrid.GetTileRef(clickLocation), releasedSolution, PuddlePrototypeId);
_popups.PopupEntity(Loc.GetString("mopping-system-release-to-floor"), user, user);
@@ -84,7 +84,7 @@ public sealed class MoppingSystem : EntitySystem
if (!_solutionSystem.TryGetDrainableSolution(target, out var drainableSolution))
return false;
if (drainableSolution.CurrentVolume <= 0)
if (drainableSolution.Volume <= 0)
{
var msg = Loc.GetString("mopping-system-target-container-empty", ("target", target));
_popups.PopupEntity(msg, user, user);
@@ -93,7 +93,7 @@ public sealed class MoppingSystem : EntitySystem
// Let's transfer up to to half the tool's available capacity to the tool.
var quantity = FixedPoint2.Max(component.PickupAmount, absorberSoln.AvailableVolume / 2);
quantity = FixedPoint2.Min(quantity, drainableSolution.CurrentVolume);
quantity = FixedPoint2.Min(quantity, drainableSolution.Volume);
DoMopInteraction(user, used, target, component, drainable.Solution, quantity, 1, "mopping-system-drainable-success", component.TransferSound);
return true;
@@ -104,7 +104,7 @@ public sealed class MoppingSystem : EntitySystem
/// </summary>
private bool TryEmptyAbsorber(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, Solution absorberSoln)
{
if (absorberSoln.CurrentVolume <= 0 || !TryComp(target, out RefillableSolutionComponent? refillable))
if (absorberSoln.Volume <= 0 || !TryComp(target, out RefillableSolutionComponent? refillable))
return false;
if (!_solutionSystem.TryGetRefillableSolution(target, out var targetSolution))
@@ -128,7 +128,7 @@ public sealed class MoppingSystem : EntitySystem
}
float delay;
FixedPoint2 quantity = absorberSoln.CurrentVolume;
FixedPoint2 quantity = absorberSoln.Volume;
// TODO this really needs cleaning up. Less magic numbers, more data-fields.
@@ -171,7 +171,7 @@ public sealed class MoppingSystem : EntitySystem
if (!TryComp(target, out PuddleComponent? puddle))
return false;
if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || puddleSolution.TotalVolume <= 0)
if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || puddleSolution.Volume <= 0)
return false;
FixedPoint2 quantity;
@@ -186,12 +186,12 @@ public sealed class MoppingSystem : EntitySystem
}
// Can our absorber even absorb any liquid?
if (puddleSolution.TotalVolume <= lowerLimit)
if (puddleSolution.Volume <= lowerLimit)
{
// Cannot absorb any more liquid. So clearly the user wants to add liquid to the puddle... right?
// This is the old behavior and I CBF fixing this, for the record I don't like this.
quantity = FixedPoint2.Min(absorber.ResidueAmount, absorberSoln.CurrentVolume);
quantity = FixedPoint2.Min(absorber.ResidueAmount, absorberSoln.Volume);
if (quantity <= 0)
return false;
@@ -208,7 +208,7 @@ public sealed class MoppingSystem : EntitySystem
return true;
}
quantity = FixedPoint2.Min(absorber.PickupAmount, puddleSolution.TotalVolume - lowerLimit, absorberSoln.AvailableVolume);
quantity = FixedPoint2.Min(absorber.PickupAmount, puddleSolution.Volume - lowerLimit, absorberSoln.AvailableVolume);
if (quantity <= 0)
return false;

View File

@@ -1,4 +1,4 @@
using Content.Server.Fluids.Components;
using Content.Server.Fluids.Components;
using Content.Shared.Fluids;
using Robust.Server.Player;
using Robust.Shared.Map;
@@ -10,6 +10,7 @@ public sealed class PuddleDebugDebugOverlaySystem : SharedPuddleDebugOverlaySyst
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
private readonly HashSet<IPlayerSession> _playerObservers = new();
@@ -72,7 +73,8 @@ public sealed class PuddleDebugDebugOverlaySystem : SharedPuddleDebugOverlaySyst
continue;
var pos = xform.Coordinates.ToVector2i(EntityManager, _mapManager);
data.Add(new PuddleDebugOverlayData(pos, puddle.CurrentVolume));
var vol = _puddle.CurrentVolume(uid, puddle);
data.Add(new PuddleDebugOverlayData(pos, vol));
}
RaiseNetworkEvent(new PuddleOverlayDebugMessage(gridUid, data.ToArray()));

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Solution = Content.Shared.Chemistry.Components.Solution;
using Robust.Shared.Prototypes;
namespace Content.Server.Fluids.EntitySystems
{
@@ -19,6 +20,10 @@ namespace Content.Server.Fluids.EntitySystems
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly FluidSpreaderSystem _fluidSpreaderSystem = default!;
[Dependency] private readonly StepTriggerSystem _stepTrigger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
public static float PuddleVolume = 1000;
// Using local deletion queue instead of the standard queue so that we can easily "undelete" if a puddle
// loses & then gains reagents in a single tick.
@@ -47,8 +52,7 @@ namespace Content.Server.Fluids.EntitySystems
private void OnPuddleInit(EntityUid uid, PuddleComponent component, ComponentInit args)
{
var solution = _solutionContainerSystem.EnsureSolution(uid, component.SolutionName);
solution.MaxVolume = FixedPoint2.New(1000);
_solutionContainerSystem.EnsureSolution(uid, component.SolutionName, FixedPoint2.New(PuddleVolume), out _);
}
private void OnSolutionUpdate(EntityUid uid, PuddleComponent component, SolutionChangedEvent args)
@@ -56,7 +60,7 @@ namespace Content.Server.Fluids.EntitySystems
if (args.Solution.Name != component.SolutionName)
return;
if (args.Solution.CurrentVolume <= 0)
if (args.Solution.Volume <= 0)
{
_deletionQueue.Add(uid);
return;
@@ -77,10 +81,10 @@ namespace Content.Server.Fluids.EntitySystems
// Opacity based on level of fullness to overflow
// Hard-cap lower bound for visibility reasons
var volumeScale = CurrentVolume(puddleComponent.Owner, puddleComponent).Float() /
var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName);
var volumeScale = puddleSolution.Volume.Float() /
puddleComponent.OverflowVolume.Float() *
puddleComponent.OpacityModifier;
var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName);
bool isEvaporating;
@@ -91,21 +95,24 @@ namespace Content.Server.Fluids.EntitySystems
}
else isEvaporating = false;
appearance.SetData(PuddleVisuals.VolumeScale, volumeScale);
appearance.SetData(PuddleVisuals.CurrentVolume, puddleComponent.CurrentVolume);
appearance.SetData(PuddleVisuals.SolutionColor, puddleSolution.Color);
appearance.SetData(PuddleVisuals.IsEvaporatingVisual, isEvaporating);
var color = puddleSolution.GetColor(_protoMan);
_appearance.SetData(uid, PuddleVisuals.VolumeScale, volumeScale, appearance);
_appearance.SetData(uid, PuddleVisuals.CurrentVolume, puddleSolution.Volume, appearance);
_appearance.SetData(uid, PuddleVisuals.SolutionColor, color, appearance);
_appearance.SetData(uid, PuddleVisuals.IsEvaporatingVisual, isEvaporating, appearance);
}
private void UpdateSlip(EntityUid entityUid, PuddleComponent puddleComponent)
{
var vol = CurrentVolume(puddleComponent.Owner, puddleComponent);
if ((puddleComponent.SlipThreshold == FixedPoint2.New(-1) ||
CurrentVolume(puddleComponent.Owner, puddleComponent) < puddleComponent.SlipThreshold) &&
vol < puddleComponent.SlipThreshold) &&
TryComp(entityUid, out StepTriggerComponent? stepTrigger))
{
_stepTrigger.SetActive(entityUid, false, stepTrigger);
}
else if (CurrentVolume(puddleComponent.Owner, puddleComponent) >= puddleComponent.SlipThreshold)
else if (vol >= puddleComponent.SlipThreshold)
{
var comp = EnsureComp<StepTriggerComponent>(entityUid);
_stepTrigger.SetActive(entityUid, true, comp);
@@ -143,7 +150,7 @@ namespace Content.Server.Fluids.EntitySystems
return _solutionContainerSystem.TryGetSolution(puddleComponent.Owner, puddleComponent.SolutionName,
out var solution)
? solution.CurrentVolume
? solution.Volume
: FixedPoint2.Zero;
}
@@ -165,15 +172,16 @@ namespace Content.Server.Fluids.EntitySystems
if (!Resolve(puddleUid, ref puddleComponent))
return false;
if (addedSolution.TotalVolume == 0 ||
if (addedSolution.Volume == 0 ||
!_solutionContainerSystem.TryGetSolution(puddleComponent.Owner, puddleComponent.SolutionName,
out var solution))
{
return false;
}
solution.AddSolution(addedSolution);
solution.AddSolution(addedSolution, _protoMan);
_solutionContainerSystem.UpdateChemicals(puddleUid, solution, true);
if (checkForOverflow && IsOverflowing(puddleUid, puddleComponent))
{
_fluidSpreaderSystem.AddOverflowingPuddle(puddleComponent.Owner, puddleComponent);
@@ -215,7 +223,7 @@ namespace Content.Server.Fluids.EntitySystems
out var destSolution))
continue;
var takeAmount = FixedPoint2.Max(0, dividedVolume - destSolution.CurrentVolume);
var takeAmount = FixedPoint2.Max(0, dividedVolume - destSolution.Volume);
TryAddSolution(destPuddle.Owner, srcSolution.SplitSolution(takeAmount), false, false, destPuddle);
if (stillOverflowing != null && IsOverflowing(destPuddle.Owner, destPuddle))
{
@@ -223,7 +231,7 @@ namespace Content.Server.Fluids.EntitySystems
}
}
if (stillOverflowing != null && srcSolution.CurrentVolume > sourcePuddleComponent.OverflowVolume)
if (stillOverflowing != null && srcSolution.Volume > sourcePuddleComponent.OverflowVolume)
{
stillOverflowing.Add(srcPuddle);
}
@@ -241,7 +249,7 @@ namespace Content.Server.Fluids.EntitySystems
if (!Resolve(uid, ref puddle))
return false;
return CurrentVolume(uid, puddle) + solution.TotalVolume > puddle.OverflowVolume;
return CurrentVolume(uid, puddle) + solution.Volume > puddle.OverflowVolume;
}
/// <summary>

View File

@@ -67,11 +67,11 @@ public sealed class SpillableSystem : EntitySystem
if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution))
return;
if (solution.TotalVolume == 0)
if (solution.Volume == 0)
return;
// spill all solution on the player
var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable);
var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.Volume);
SpillAt(args.Equipee, drainedSolution, "PuddleSmear");
}
@@ -108,7 +108,7 @@ public sealed class SpillableSystem : EntitySystem
$"{ToPrettyString(uid):entity} spilled a solution {SolutionContainerSystem.ToPrettyString(solution):solution} on landing");
}
var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable);
var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.Volume);
SpillAt(drainedSolution, EntityManager.GetComponent<TransformComponent>(uid).Coordinates, "PuddleSmear");
}
@@ -123,7 +123,7 @@ public sealed class SpillableSystem : EntitySystem
if (TryComp<DrinkComponent>(args.Target, out var drink) && (!drink.Opened))
return;
if (solution.DrainAvailable == FixedPoint2.Zero)
if (solution.Volume == FixedPoint2.Zero)
return;
Verb verb = new();
@@ -134,7 +134,7 @@ public sealed class SpillableSystem : EntitySystem
verb.Act = () =>
{
var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target,
solution, solution.DrainAvailable);
solution, solution.Volume);
SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear");
};
}
@@ -176,7 +176,7 @@ public sealed class SpillableSystem : EntitySystem
public PuddleComponent? SpillAt(Solution solution, EntityCoordinates coordinates, string prototype,
bool overflow = true, bool sound = true, bool combine = true)
{
if (solution.TotalVolume == 0) return null;
if (solution.Volume == 0) return null;
if (!_mapManager.TryGetGrid(coordinates.GetGridUid(EntityManager), out var mapGrid))
@@ -204,7 +204,7 @@ public sealed class SpillableSystem : EntitySystem
public PuddleComponent? SpillAt(TileRef tileRef, Solution solution, string prototype,
bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true)
{
if (solution.TotalVolume <= 0) return null;
if (solution.Volume <= 0) return null;
// If space return early, let that spill go out into the void
if (tileRef.Tile.IsEmpty) return null;
@@ -226,7 +226,7 @@ public sealed class SpillableSystem : EntitySystem
}
// Tile reactions used up everything.
if (solution.CurrentVolume == FixedPoint2.Zero)
if (solution.Volume == FixedPoint2.Zero)
return null;
// Get normalized co-ordinate for spill location and spill it in the centre
@@ -268,11 +268,11 @@ public sealed class SpillableSystem : EntitySystem
component.CancelToken = null;
//solution gone by other means before doafter completes
if (ev.Solution == null || ev.Solution.CurrentVolume == 0)
if (ev.Solution == null || ev.Solution.Volume == 0)
return;
var puddleSolution = _solutionContainerSystem.SplitSolution(uid,
ev.Solution, ev.Solution.DrainAvailable);
ev.Solution, ev.Solution.Volume);
SpillAt(puddleSolution, Transform(component.Owner).Coordinates, "PuddleSmear");
}

View File

@@ -12,6 +12,7 @@ using Content.Shared.Vapor;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Fluids.EntitySystems;
@@ -19,6 +20,7 @@ namespace Content.Server.Fluids.EntitySystems;
public sealed class SpraySystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
@@ -51,7 +53,7 @@ public sealed class SpraySystem : EntitySystem
&& curTime < cooldown.CooldownEnd)
return;
if (solution.CurrentVolume <= 0)
if (solution.Volume <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), uid,
args.User);
@@ -76,7 +78,7 @@ public sealed class SpraySystem : EntitySystem
var threeQuarters = diffNorm * 0.75f;
var quarter = diffNorm * 0.25f;
var amount = Math.Max(Math.Min((solution.CurrentVolume / component.TransferAmount).Int(), component.VaporAmount), 1);
var amount = Math.Max(Math.Min((solution.Volume / component.TransferAmount).Int(), component.VaporAmount), 1);
var spread = component.VaporSpread / amount;
for (var i = 0; i < amount; i++)
@@ -94,7 +96,7 @@ public sealed class SpraySystem : EntitySystem
var newSolution = _solutionContainerSystem.SplitSolution(uid, solution, component.TransferAmount);
if (newSolution.TotalVolume <= FixedPoint2.Zero)
if (newSolution.Volume <= FixedPoint2.Zero)
break;
// Spawn the vapor cloud onto the grid/map the user is present on. Offset the start position based on how far the target destination is.
@@ -106,7 +108,7 @@ public sealed class SpraySystem : EntitySystem
if (TryComp(vapor, out AppearanceComponent? appearance))
{
appearance.SetData(VaporVisuals.Color, solution.Color.WithAlpha(1f));
appearance.SetData(VaporVisuals.Color, solution.GetColor(_proto).WithAlpha(1f));
appearance.SetData(VaporVisuals.State, true);
}

View File

@@ -87,7 +87,7 @@ namespace Content.Server.Kitchen.EntitySystems
if (TryComp<StackComponent>(item, out var stack))
{
var totalVolume = solution.TotalVolume * stack.Count;
var totalVolume = solution.Volume * stack.Count;
if (totalVolume <= 0)
continue;
@@ -102,7 +102,7 @@ namespace Content.Server.Kitchen.EntitySystems
}
else
{
if (solution.TotalVolume > containerSolution.AvailableVolume)
if (solution.Volume > containerSolution.AvailableVolume)
continue;
QueueDel(item);

View File

@@ -3,6 +3,7 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
namespace Content.Server.Kudzu;
@@ -28,9 +29,9 @@ public sealed class SpreaderSystem : EntitySystem
SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
}
private void OnAirtightChanged(AirtightChanged e)
private void OnAirtightChanged(ref AirtightChanged ev)
{
UpdateNearbySpreaders((e.Airtight).Owner, e.Airtight);
UpdateNearbySpreaders(ev.Entity, ev.Airtight);
}
private void SpreaderAddHandler(EntityUid uid, SpreaderComponent component, ComponentAdd args)
@@ -46,15 +47,21 @@ public sealed class SpreaderSystem : EntitySystem
if (!_mapManager.TryGetGrid(transform.GridUid, out var grid)) return;
var spreaderQuery = GetEntityQuery<SpreaderComponent>();
var tile = grid.TileIndicesFor(transform.Coordinates);
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!comp.AirBlockedDirection.IsFlagSet(direction)) continue;
foreach (var ent in grid.GetInDir(transform.Coordinates, direction.ToDirection()))
var directionEnumerator =
grid.GetAnchoredEntitiesEnumerator(SharedMapSystem.GetDirection(tile, direction.ToDirection()));
while (directionEnumerator.MoveNext(out var ent))
{
if (EntityManager.TryGetComponent<SpreaderComponent>(ent, out var s) && s.Enabled)
_edgeGrowths.Add(ent);
if (spreaderQuery.TryGetComponent(ent, out var s) && s.Enabled)
_edgeGrowths.Add(ent.Value);
}
}
}

View File

@@ -6,6 +6,9 @@ using Content.Server.Power.Components;
using Content.Server.Construction.Components;
using Content.Server.Stack;
using Content.Shared.Database;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server.Materials;
@@ -15,6 +18,7 @@ namespace Content.Server.Materials;
public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
@@ -32,7 +36,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
foreach (var (material, amount) in component.Storage)
{
_stackSystem.SpawnMultipleFromMaterial(amount, material, Transform(uid).Coordinates);
SpawnMultipleFromMaterial(amount, material, Transform(uid).Coordinates);
}
}
@@ -56,4 +60,42 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
$"{ToPrettyString(user):player} inserted {count} {ToPrettyString(toInsert):inserted} into {ToPrettyString(receiver):receiver}");
return true;
}
/// <summary>
/// Spawn an amount of a material in stack entities.
/// Note the 'amount' is material dependent.
/// 1 biomass = 1 biomass in its stack,
/// but 100 plasma = 1 sheet of plasma, etc.
/// </summary>
[PublicAPI]
public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
{
if (!_prototypeManager.TryIndex<MaterialPrototype>(material, out var stackType))
{
Logger.Error("Failed to index material prototype " + material);
return new List<EntityUid>();
}
return SpawnMultipleFromMaterial(amount, stackType, coordinates);
}
/// <summary>
/// Spawn an amount of a material in stack entities.
/// Note the 'amount' is material dependent.
/// 1 biomass = 1 biomass in its stack,
/// but 100 plasma = 1 sheet of plasma, etc.
/// </summary>
public List<EntityUid> SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
{
if (amount <= 0)
return new List<EntityUid>();
var entProto = _prototypeManager.Index<EntityPrototype>(materialProto.StackEntity);
if (!entProto.TryGetComponent<MaterialComponent>(out var material))
return new List<EntityUid>();
var materialPerStack = material.Materials[materialProto.ID];
var amountToSpawn = amount / materialPerStack;
return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
}
}

View File

@@ -17,8 +17,8 @@ using Content.Server.Body.Components;
using Content.Server.Climbing;
using Content.Server.Construction;
using Content.Server.DoAfter;
using Content.Server.Materials;
using Content.Server.Mind.Components;
using Content.Server.Stack;
using Content.Shared.Humanoid;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
@@ -32,7 +32,6 @@ namespace Content.Server.Medical.BiomassReclaimer
public sealed class BiomassReclaimerSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedJitteringSystem _jitteringSystem = default!;
[Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!;
@@ -44,6 +43,7 @@ namespace Content.Server.Medical.BiomassReclaimer
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!;
public override void Update(float frameTime)
{
@@ -76,7 +76,7 @@ namespace Content.Server.Medical.BiomassReclaimer
continue;
}
_stackSystem.SpawnMultipleFromMaterial((int) reclaimer.CurrentExpectedYield, "Biomass", Transform(reclaimer.Owner).Coordinates);
_material.SpawnMultipleFromMaterial(reclaimer.CurrentExpectedYield, "Biomass", Transform(reclaimer.Owner).Coordinates);
reclaimer.BloodReagent = null;
reclaimer.SpawnedEntities.Clear();

View File

@@ -3,6 +3,7 @@ using Content.Server.UserInterface;
using Content.Shared.Disease;
using Content.Shared.MedicalScanner;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Medical.Components
@@ -25,6 +26,18 @@ namespace Content.Server.Medical.Components
public CancellationTokenSource? CancelToken;
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key);
/// <summary>
/// Sound played on scanning begin
/// </summary>
[DataField("scanningBeginSound")]
public SoundSpecifier? ScanningBeginSound = null;
/// <summary>
/// Sound played on scanning end
/// </summary>
[DataField("scanningEndSound")]
public SoundSpecifier? ScanningEndSound = null;
/// <summary>
/// The disease this will give people.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System.Threading;
using System.Threading;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
@@ -204,7 +204,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution))
{
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
if (containerSolution.CurrentVolume == 0)
if (containerSolution.Volume == 0)
{
args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker"));
}

View File

@@ -7,6 +7,7 @@ using Content.Shared.Damage;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.MobState.Components;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
@@ -15,6 +16,7 @@ namespace Content.Server.Medical
{
public sealed class HealthAnalyzerSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -55,6 +57,9 @@ namespace Content.Server.Medical
return;
healthAnalyzer.CancelToken = new CancellationTokenSource();
_audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, healthAnalyzer.CancelToken.Token, target: args.Target)
{
BroadcastFinishedEvent = new TargetScanSuccessfulEvent(args.User, args.Target, healthAnalyzer),
@@ -69,6 +74,9 @@ namespace Content.Server.Medical
private void OnTargetScanSuccessful(TargetScanSuccessfulEvent args)
{
args.Component.CancelToken = null;
_audio.PlayPvs(args.Component.ScanningEndSound, args.User);
UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component);
// Below is for the traitor item
// Piggybacking off another component's doafter is complete CBT so I gave up

View File

@@ -71,11 +71,11 @@ namespace Content.Server.Nutrition.Components
}
if (TransferAmount == null)
return solution.CurrentVolume == 0 ? 0 : 1;
return solution.Volume == 0 ? 0 : 1;
return solution.CurrentVolume == 0
return solution.Volume == 0
? 0
: Math.Max(1, (int) Math.Ceiling((solution.CurrentVolume / (FixedPoint2)TransferAmount).Float()));
: Math.Max(1, (int) Math.Ceiling((solution.Volume / (FixedPoint2)TransferAmount).Float()));
}
}
}

View File

@@ -164,7 +164,7 @@ namespace Content.Server.Nutrition.EntitySystems
component.Opened = true;
UpdateAppearance(component);
var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.DrainAvailable);
var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.Volume);
_spillableSystem.SpillAt(uid, solution, "PuddleSmear");
_audio.PlayPvs(_audio.GetSound(component.BurstSound), uid, AudioParams.Default.WithVolume(-4));
@@ -240,7 +240,7 @@ namespace Content.Server.Nutrition.EntitySystems
}
if (!_solutionContainerSystem.TryGetDrainableSolution(drink.Owner, out var drinkSolution) ||
drinkSolution.DrainAvailable <= 0)
drinkSolution.Volume <= 0)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty",
("entity", EntityManager.GetComponent<MetaDataComponent>(drink.Owner).EntityName)), drink.Owner, user);
@@ -301,7 +301,7 @@ namespace Content.Server.Nutrition.EntitySystems
return;
args.Drink.CancelToken = null;
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable);
var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.Volume);
var drained = _solutionContainerSystem.Drain(args.Drink.Owner, args.DrinkSolution, transferAmount);
var forceDrink = uid != args.User;

View File

@@ -163,8 +163,8 @@ namespace Content.Server.Nutrition.EntitySystems
return;
var transferAmount = args.Food.TransferAmount != null
? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.CurrentVolume)
: args.FoodSolution.CurrentVolume;
? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.Volume)
: args.FoodSolution.Volume;
var split = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount);

View File

@@ -59,7 +59,7 @@ namespace Content.Server.Nutrition.EntitySystems
var sliceUid = EntityManager.SpawnEntity(component.Slice, transform.Coordinates);
var lostSolution = _solutionContainerSystem.SplitSolution(uid, solution,
solution.CurrentVolume / FixedPoint2.New(component.Count));
solution.Volume / FixedPoint2.New(component.Count));
// Fill new slice
FillSlice(sliceUid, lostSolution);

View File

@@ -117,12 +117,12 @@ namespace Content.Server.Nutrition.EntitySystems
var inhaledSolution = _solutionContainerSystem.SplitSolution(uid, solution, smokable.InhaleAmount * _timer);
if (solution.TotalVolume == FixedPoint2.Zero)
if (solution.Volume == FixedPoint2.Zero)
{
RaiseLocalEvent(uid, new SmokableSolutionEmptyEvent(), true);
}
if (inhaledSolution.TotalVolume == FixedPoint2.Zero)
if (inhaledSolution.Volume == FixedPoint2.Zero)
continue;
// This is awful. I hate this so much.

View File

@@ -39,7 +39,7 @@ namespace Content.Server.Nutrition.EntitySystems
public void UpdateTags(TrashOnEmptyComponent component, Solution solution)
{
if (solution.DrainAvailable <= 0)
if (solution.Volume <= 0)
{
_tagSystem.AddTag(component.Owner, "Trash");
return;

View File

@@ -147,8 +147,8 @@ public sealed class PayloadSystem : EntitySystem
|| !TryComp(beakerB, out FitsInDispenserComponent? compB)
|| !_solutionSystem.TryGetSolution(beakerA, compA.Solution, out var solutionA)
|| !_solutionSystem.TryGetSolution(beakerB, compB.Solution, out var solutionB)
|| solutionA.TotalVolume == 0
|| solutionB.TotalVolume == 0)
|| solutionA.Volume == 0
|| solutionB.Volume == 0)
{
return;
}
@@ -164,7 +164,7 @@ public sealed class PayloadSystem : EntitySystem
_solutionSystem.RemoveAllSolution(beakerB, solutionB);
// The grenade might be a dud. Redistribute solution:
var tmpSol = _solutionSystem.SplitSolution(beakerA, solutionA, solutionA.CurrentVolume * solutionB.MaxVolume / solutionA.MaxVolume);
var tmpSol = _solutionSystem.SplitSolution(beakerA, solutionA, solutionA.Volume * solutionB.MaxVolume / solutionA.MaxVolume);
_solutionSystem.TryAddSolution(beakerB, solutionB, tmpSol);
solutionA.MaxVolume -= solutionB.MaxVolume;
_solutionSystem.UpdateChemicals(beakerA, solutionA, false);

View File

@@ -120,7 +120,7 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args)
{
component.IsRigged = _solutionsSystem.TryGetSolution(uid, PowerCellComponent.SolutionName, out var solution)
&& solution.ContainsReagent("Plasma", out var plasma)
&& solution.TryGetReagent("Plasma", out var plasma)
&& plasma >= 5;
if (component.IsRigged)

View File

@@ -1,4 +1,5 @@
using Content.Server.Administration;
using Content.Server.Administration.Logs;
using Content.Server.Bible.Components;
using Content.Server.Chat.Managers;
using Content.Server.Popups;
@@ -19,6 +20,7 @@ namespace Content.Server.Prayer;
/// </remarks>
public sealed class PrayerSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly QuickDialogSystem _quickDialog = default!;
@@ -68,9 +70,10 @@ public sealed class PrayerSystem : EntitySystem
/// Subtly messages a player by giving them a popup and a chat message.
/// </summary>
/// <param name="target">The IPlayerSession that you want to send the message to</param>
/// <param name="source">The IPlayerSession that sent the message</param>
/// <param name="messageString">The main message sent to the player via the chatbox</param>
/// <param name="popupMessage">The popup to notify the player, also prepended to the messageString</param>
public void SendSubtleMessage(IPlayerSession target, string messageString, string popupMessage)
public void SendSubtleMessage(IPlayerSession target, IPlayerSession source, string messageString, string popupMessage)
{
if (target.AttachedEntity == null)
return;
@@ -79,6 +82,7 @@ public sealed class PrayerSystem : EntitySystem
_popupSystem.PopupEntity(popupMessage, target.AttachedEntity.Value, target, PopupType.Large);
_chatManager.ChatMessageToOne(ChatChannel.Local, messageString, message, EntityUid.Invalid, false, target.ConnectedClient);
_adminLogger.Add(LogType.AdminMessage, LogImpact.Low, $"{ToPrettyString(target.AttachedEntity.Value):player} received subtle message from {source.Name}: {message}");
}
/// <summary>
@@ -99,5 +103,6 @@ public sealed class PrayerSystem : EntitySystem
_popupSystem.PopupEntity(Loc.GetString("prayer-popup-notify-sent"), sender.AttachedEntity.Value, sender, PopupType.Medium);
_chatManager.SendAdminAnnouncement(Loc.GetString("prayer-chat-notify", ("name", sender.Name), ("message", message)));
_adminLogger.Add(LogType.AdminMessage, LogImpact.Low, $"{ToPrettyString(sender.AttachedEntity.Value):player} sent prayer: {message}");
}
}

View File

@@ -41,7 +41,7 @@ namespace Content.Server.RoundEnd
/// Countdown to use where there is no station alert countdown to be found.
/// </summary>
public TimeSpan DefaultCountdownDuration { get; set; } = TimeSpan.FromMinutes(4);
public TimeSpan DefaultRestartRoundDuration { get; set; } = TimeSpan.FromMinutes(1);
public TimeSpan DefaultRestartRoundDuration { get; set; } = TimeSpan.FromMinutes(2);
private CancellationTokenSource? _countdownTokenSource = null;
private CancellationTokenSource? _cooldownTokenSource = null;

View File

@@ -1,9 +1,11 @@
using Content.Server.Humanoid;
using Content.Server.Popups;
using Content.Server.Speech.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Humanoid;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -23,6 +25,7 @@ public sealed class VocalSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
@@ -91,6 +94,8 @@ public sealed class VocalSystem : EntitySystem
break;
}
_popupSystem.PopupEntity(Loc.GetString("scream-action-popup"), uid, PopupType.Medium);
return true;
}
}

View File

@@ -1,7 +1,6 @@
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
using Content.Shared.Materials;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -16,7 +15,6 @@ namespace Content.Server.Stack
public sealed class StackSystem : SharedStackSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedStackSystem _sharedStack = default!;
public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
@@ -91,78 +89,21 @@ namespace Content.Server.Stack
/// Say you want to spawn 97 units of something that has a max stack count of 30.
/// This would spawn 3 stacks of 30 and 1 stack of 7.
/// </summary>
public List<EntityUid> SpawnMultiple(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition)
{
var list = new List<EntityUid>();
if (amount <= 0)
return list;
// At least 1 is being spawned, we'll use the first to extract otherwise inaccessible information
// ??TODO??: Indexing the entity proto and extracting from its component registry could possibly be better?
// it doesn't look like it would save LOC even compressing this to a single loop and I'm not sure what other issues it might introduce
var firstSpawn = Spawn(materialProto.StackEntity, coordinates);
list.Add(firstSpawn);
if (!TryComp<StackComponent>(firstSpawn, out var stack) || stack.StackTypeId == null)
return list;
if (!TryComp<MaterialComponent>(firstSpawn, out var material))
return list;
int maxCountPerStack = _sharedStack.GetMaxCount(stack);
var materialPerStack = material.Materials[materialProto.ID];
var materialPerMaxCount = maxCountPerStack * materialPerStack;
// no material duping for you
if (amount < materialPerStack)
{
Del(firstSpawn);
return list;
}
if (amount > materialPerMaxCount)
{
SetCount(firstSpawn, maxCountPerStack, stack);
amount -= materialPerMaxCount;
} else
{
SetCount(firstSpawn, (amount / materialPerStack), stack);
amount = 0;
}
var proto = _prototypeManager.Index<EntityPrototype>(entityPrototype);
proto.TryGetComponent<StackComponent>(out var stack);
var maxCountPerStack = GetMaxCount(stack);
var spawnedEnts = new List<EntityUid>();
while (amount > 0)
{
var entity = Spawn(materialProto.StackEntity, coordinates);
list.Add(entity);
var nextStack = Comp<StackComponent>(entity);
if (amount > materialPerMaxCount)
{
SetCount(entity, materialPerMaxCount, nextStack);
amount -= materialPerMaxCount;
}
else
{
SetCount(entity, (amount / materialPerStack), nextStack);
amount = 0;
}
var entity = Spawn(entityPrototype, spawnPosition);
spawnedEnts.Add(entity);
var countAmount = Math.Min(maxCountPerStack, amount);
SetCount(entity, countAmount);
amount -= countAmount;
}
return list;
}
/// <summary>
/// Spawn an amount of a material in stack entities. Note the 'amount' is material dependent. 1 biomass = 1 biomass in its stack,
/// but 100 plasma = 1 sheet of plasma, etc.
/// </summary>
public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
{
if (!_prototypeManager.TryIndex<MaterialPrototype>(material, out var stackType))
{
Logger.Error("Failed to index material prototype " + material);
return new List<EntityUid>();
}
return SpawnMultiple(amount, stackType, coordinates);
return spawnedEnts;
}
private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args)

View File

@@ -200,7 +200,7 @@ public sealed partial class StoreSystem : EntitySystem
{
var cashId = proto.Cash[value];
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value));
var ents = _stack.SpawnMultipleFromMaterial(amountToSpawn, cashId, coordinates);
var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coordinates);
_hands.PickupOrDrop(buyer, ents.First());
amountRemaining -= value * amountToSpawn;
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems;
@@ -221,7 +221,7 @@ namespace Content.Server.Tools
&& _solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution)
&& _solutionContainerSystem.TryGetSolution(uid, welder.FuelSolution, out var welderSolution))
{
var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.DrainAvailable);
var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
if (trans > 0)
{
var drained = _solutionContainerSystem.Drain(target, targetSolution, trans);

View File

@@ -1,4 +1,4 @@
using Content.Shared.Bed.Sleep;
using Content.Shared.Bed.Sleep;
using Content.Shared.StatusEffect;
using Robust.Shared.Random;
@@ -26,6 +26,14 @@ public sealed class NarcolepsySystem : EntitySystem
_random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y);
}
public void AdjustNarcolepsyTimer(EntityUid uid, int TimerReset, NarcolepsyComponent? narcolepsy = null)
{
if (!Resolve(uid, ref narcolepsy, false))
return;
narcolepsy.NextIncidentTime = TimerReset;
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View File

@@ -250,9 +250,9 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
return;
var removedSolution = solutionContainer.SplitSolution(comp.TransferAmount * hitBloodstreams.Count);
var removedVol = removedSolution.TotalVolume;
var removedVol = removedSolution.Volume;
var solutionToInject = removedSolution.SplitSolution(removedVol * comp.TransferEfficiency);
var volPerBloodstream = solutionToInject.TotalVolume * (1 / hitBloodstreams.Count);
var volPerBloodstream = solutionToInject.Volume * (1 / hitBloodstreams.Count);
foreach (var bloodstream in hitBloodstreams)
{

View File

@@ -35,7 +35,7 @@ namespace Content.Server.Weapons.Ranged.Systems
if (!projectileSolutionContainers.Any())
return;
var solutionPerProjectile = ammoSolution.CurrentVolume * (1 / projectileSolutionContainers.Count);
var solutionPerProjectile = ammoSolution.Volume * (1 / projectileSolutionContainers.Count);
foreach (var (projectileUid, projectileSolution) in projectileSolutionContainers)
{

View File

@@ -5,6 +5,6 @@
/// and such.
/// </summary>
[RegisterComponent]
public sealed class ClearFixturesArtifactComponent : Component
public sealed class PhasingArtifactComponent : Component
{
}

View File

@@ -2,35 +2,35 @@
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Shared.Physics;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
/// <summary>
/// Handles allowing activated artifacts to phase through walls.
/// </summary>
public sealed class ClearFixturesArtifactSystem : EntitySystem
public sealed class PhasingArtifactSystem : EntitySystem
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ClearFixturesArtifactComponent, ArtifactActivatedEvent>(OnActivate);
SubscribeLocalEvent<PhasingArtifactComponent, ArtifactActivatedEvent>(OnActivate);
}
private void OnActivate(EntityUid uid, ClearFixturesArtifactComponent component, ArtifactActivatedEvent args)
private void OnActivate(EntityUid uid, PhasingArtifactComponent component, ArtifactActivatedEvent args)
{
if (!TryComp<FixturesComponent>(uid, out var fixtures))
if (!TryComp<FixturesComponent>(uid, out var fixtures) || !TryComp<PhysicsComponent>(uid, out var phys))
return;
foreach (var (_, fixture) in fixtures.Fixtures)
{
if (!fixture.Hard)
continue;
fixture.CollisionLayer = (int) CollisionGroup.None;
fixture.CollisionMask = (int) CollisionGroup.None;
_physics.SetHard(fixture, false, fixtures);
}
}
}

View File

@@ -80,5 +80,5 @@ public enum LogType
Stripping = 75,
Stamina = 76,
EntitySpawn = 77,
Container = 78,
AdminMessage = 78,
}

View File

@@ -1,9 +1,42 @@
namespace Content.Shared.Chat.TypingIndicator;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory.Events;
namespace Content.Shared.Chat.TypingIndicator;
/// <summary>
/// Sync typing indicator icon between client and server.
/// </summary>
public abstract class SharedTypingIndicatorSystem : EntitySystem
{
/// <summary>
/// Default ID of <see cref="TypingIndicatorPrototype"/>
/// </summary>
public const string InitialIndicatorId = "default";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TypingIndicatorClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<TypingIndicatorClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid uid, TypingIndicatorClothingComponent component, GotEquippedEvent args)
{
if (!TryComp<ClothingComponent>(uid, out var clothing) ||
!TryComp<TypingIndicatorComponent>(args.Equipee, out var indicator))
return;
var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags);
if (!isCorrectSlot) return;
indicator.Prototype = component.Prototype;
}
private void OnGotUnequipped(EntityUid uid, TypingIndicatorClothingComponent component, GotUnequippedEvent args)
{
if (!TryComp<TypingIndicatorComponent>(args.Equipee, out var indicator))
return;
indicator.Prototype = SharedTypingIndicatorSystem.InitialIndicatorId;
}
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Chat.TypingIndicator;
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedTypingIndicatorSystem))]
public sealed class TypingIndicatorClothingComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<TypingIndicatorPrototype>))]
public string Prototype = default!;
}

View File

@@ -14,7 +14,7 @@ public sealed class TypingIndicatorComponent : Component
/// <summary>
/// Prototype id that store all visual info about typing indicator.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
[ViewVariables(VVAccess.ReadWrite)]
[DataField("proto", customTypeSerializer: typeof(PrototypeIdSerializer<TypingIndicatorPrototype>))]
public string Prototype = "default";
public string Prototype = SharedTypingIndicatorSystem.InitialIndicatorId;
}

View File

@@ -1,98 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared.Chemistry.Components
{
public sealed partial class Solution
{
/// <summary>
/// If reactions will be checked for when adding reagents to the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canReact")]
public bool CanReact { get; set; } = true;
/// <summary>
/// If reactions can occur via mixing.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canMix")]
public bool CanMix { get; set; } = false;
/// <summary>
/// Volume needed to fill this container.
/// </summary>
[ViewVariables]
public FixedPoint2 AvailableVolume => MaxVolume - CurrentVolume;
public FixedPoint2 DrawAvailable => CurrentVolume;
public FixedPoint2 DrainAvailable => CurrentVolume;
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
/// <param name="solution">The solution that is trying to be added.</param>
/// <returns>If the solution can be fully added.</returns>
public bool CanAddSolution(Solution solution)
{
return solution.TotalVolume <= AvailableVolume;
}
[DataField("maxSpillRefill")]
public FixedPoint2 MaxSpillRefill { get; set; }
/// <summary>
/// Initially set <see cref="MaxVolume"/>. If empty will be calculated based
/// on sum of <see cref="Contents"/> fixed units.
/// </summary>
[DataField("maxVol")] public FixedPoint2 InitialMaxVolume;
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero;
[ViewVariables]
public FixedPoint2 CurrentVolume => TotalVolume;
/// <summary>
/// The total heat capacity of all reagents in the solution.
/// </summary>
[ViewVariables]
public float HeatCapacity => GetHeatCapacity();
/// <summary>
/// The average specific heat of all reagents in the solution.
/// </summary>
[ViewVariables]
public float SpecificHeat => HeatCapacity / (float) TotalVolume;
/// <summary>
/// The total thermal energy of the reagents in the solution.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float ThermalEnergy {
get { return Temperature * HeatCapacity; }
set { Temperature = ((HeatCapacity == 0.0f) ? 0.0f : (value / HeatCapacity)); }
}
/// <summary>
/// Returns the total heat capacity of the reagents in this solution.
/// </summary>
/// <returns>The total heat capacity of the reagents in this solution.</returns>
private float GetHeatCapacity()
{
var heatCapacity = 0.0f;
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach(var reagent in Contents)
{
if (!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
proto = new ReagentPrototype();
heatCapacity += (float) reagent.Quantity * proto.SpecificHeat;
}
return heatCapacity;
}
}
}

View File

@@ -1,12 +1,13 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Shared.Chemistry.Components
{
@@ -17,7 +18,8 @@ namespace Content.Shared.Chemistry.Components
[DataDefinition]
public sealed partial class Solution : IEnumerable<Solution.ReagentQuantity>, ISerializationHooks
{
// Most objects on the station hold only 1 or 2 reagents
// This is a list because it is actually faster to add and remove reagents from
// a list than a dictionary, though contains-reagent checks are slightly slower,
[DataField("reagents")]
public List<ReagentQuantity> Contents = new(2);
@@ -25,7 +27,41 @@ namespace Content.Shared.Chemistry.Components
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
public FixedPoint2 TotalVolume { get; set; }
public FixedPoint2 Volume { get; set; }
/// <summary>
/// Maximum volume this solution supports.
/// </summary>
/// <remarks>
/// A value of zero means the maximum will automatically be set equal to the current volume during
/// initialization. Note that most solution methods ignore max volume altogether, but various solution
/// systems use this.
/// </remarks>
[DataField("maxVol")]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero;
public float FillFraction => MaxVolume == 0 ? 1 : Volume.Float() / MaxVolume.Float();
/// <summary>
/// If reactions will be checked for when adding reagents to the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canReact")]
public bool CanReact { get; set; } = true;
/// <summary>
/// If reactions can occur via mixing.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canMix")]
public bool CanMix { get; set; } = false;
/// <summary>
/// Volume needed to fill this container.
/// </summary>
[ViewVariables]
public FixedPoint2 AvailableVolume => MaxVolume - Volume;
/// <summary>
/// The temperature of the reagents in the solution.
@@ -34,40 +70,155 @@ namespace Content.Shared.Chemistry.Components
[DataField("temperature")]
public float Temperature { get; set; } = 293.15f;
public Color Color => GetColor();
/// <summary>
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
/// </summary>
public string? Name;
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
public bool CanAddSolution(Solution solution)
{
return solution.Volume <= AvailableVolume;
}
/// <summary>
/// The total heat capacity of all reagents in the solution.
/// </summary>
[ViewVariables]
private float _heatCapacity;
/// <summary>
/// If true, then <see cref="_heatCapacity"/> needs to be recomputed.
/// </summary>
[ViewVariables]
private bool _heatCapacityDirty = true;
public void UpdateHeatCapacity(IPrototypeManager? protoMan)
{
IoCManager.Resolve(ref protoMan);
DebugTools.Assert(_heatCapacityDirty);
_heatCapacityDirty = false;
_heatCapacity = 0;
foreach (var reagent in Contents)
{
_heatCapacity += (float) reagent.Quantity * protoMan.Index<ReagentPrototype>(reagent.ReagentId).SpecificHeat;
}
}
public float GetHeatCapacity(IPrototypeManager? protoMan)
{
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
return _heatCapacity;
}
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
/// </summary>
public Solution() { }
public Solution() : this(2) // Most objects on the station hold only 1 or 2 reagents.
{
}
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
/// </summary>
public Solution(int capacity)
{
Contents = new(capacity);
}
/// <summary>
/// Constructs a solution containing 100% of a reagent (ex. A beaker of pure water).
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public Solution(string reagentId, FixedPoint2 quantity)
public Solution(string reagentId, FixedPoint2 quantity) : this()
{
AddReagent(reagentId, quantity);
}
public Solution(IEnumerable<ReagentQuantity> reagents, bool setMaxVol = true)
{
Contents = new(reagents);
Volume = FixedPoint2.Zero;
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (setMaxVol)
MaxVolume = Volume;
ValidateSolution();
}
public Solution(Solution solution)
{
Volume = solution.Volume;
_heatCapacity = solution._heatCapacity;
_heatCapacityDirty = solution._heatCapacityDirty;
Contents = solution.Contents.ShallowClone();
ValidateSolution();
}
public Solution Clone()
{
return new Solution(this);
}
[AssertionMethod]
public void ValidateSolution()
{
// sandbox forbids: [Conditional("DEBUG")]
#if DEBUG
// Correct volume
DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume);
// All reagents have at least some reagent present.
DebugTools.Assert(!Contents.Any(x => x.Quantity <= FixedPoint2.Zero));
// No duplicate reagent iDs
DebugTools.Assert(Contents.Select(x => x.ReagentId).ToHashSet().Count() == Contents.Count);
// If it isn't flagged as dirty, check heat capacity is correct.
if (!_heatCapacityDirty)
{
var cur = _heatCapacity;
_heatCapacityDirty = true;
UpdateHeatCapacity(null);
DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur));
}
#endif
}
void ISerializationHooks.AfterDeserialization()
{
TotalVolume = FixedPoint2.Zero;
Contents.ForEach(reagent => TotalVolume += reagent.Quantity);
Volume = FixedPoint2.Zero;
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (MaxVolume == FixedPoint2.Zero)
MaxVolume = Volume;
}
public bool ContainsReagent(string reagentId)
{
return ContainsReagent(reagentId, out _);
foreach (var reagent in Contents)
{
if (reagent.ReagentId == reagentId)
return true;
}
return false;
}
public bool ContainsReagent(string reagentId, out FixedPoint2 quantity)
public bool TryGetReagent(string reagentId, out FixedPoint2 quantity)
{
foreach (var reagent in Contents)
{
@@ -82,15 +233,22 @@ namespace Content.Shared.Chemistry.Components
return false;
}
public string GetPrimaryReagentId()
public string? GetPrimaryReagentId()
{
if (Contents.Count == 0)
return null;
ReagentQuantity max = default;
foreach (var reagent in Contents)
{
return "";
if (reagent.Quantity >= max.Quantity)
{
max = reagent;
}
}
var majorReagent = Contents.MaxBy(reagent => reagent.Quantity);
return majorReagent.ReagentId;
return max.ReagentId!;
}
/// <summary>
@@ -98,16 +256,16 @@ namespace Content.Shared.Chemistry.Components
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(string reagentId, FixedPoint2 quantity, float? temperature = null)
public void AddReagent(string reagentId, FixedPoint2 quantity, bool dirtyHeatCap = true)
{
if (quantity <= 0)
{
DebugTools.Assert(quantity == 0, "Attempted to add negative reagent quantity");
return;
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex(reagentId, out ReagentPrototype? proto))
proto = new ReagentPrototype();
}
var actualTemp = temperature ?? Temperature;
var oldThermalEnergy = Temperature * GetHeatCapacity();
var addedThermalEnergy = (float) quantity * proto.SpecificHeat * actualTemp;
Volume += quantity;
_heatCapacityDirty |= dirtyHeatCap;
for (var i = 0; i < Contents.Count; i++)
{
var reagent = Contents[i];
@@ -115,16 +273,65 @@ namespace Content.Shared.Chemistry.Components
continue;
Contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity);
TotalVolume += quantity;
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
ValidateSolution();
return;
}
Contents.Add(new ReagentQuantity(reagentId, quantity));
ValidateSolution();
}
TotalVolume += quantity;
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="proto">The prototype of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity)
{
AddReagent(proto.ID, quantity, false);
_heatCapacity += quantity.Float() * proto.SpecificHeat;
}
/// <summary>
/// Adds a given quantity of a reagent directly into the solution.
/// </summary>
/// <param name="proto">The prototype of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity, float temperature, IPrototypeManager? protoMan)
{
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
var totalThermalEnergy = Temperature * _heatCapacity + temperature * proto.SpecificHeat;
AddReagent(proto, quantity);
Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity;
}
/// <summary>
/// Scales the amount of solution by some integer quantity.
/// </summary>
/// <param name="scale">The scalar to modify the solution by.</param>
public void ScaleSolution(int scale)
{
if (scale == 1)
return;
if (scale == 0)
{
RemoveAllSolution();
return;
}
_heatCapacity *= scale;
Volume *= scale;
for (int i = 0; i <= Contents.Count; i++)
{
var old = Contents[i];
Contents[i] = new ReagentQuantity(old.ReagentId, old.Quantity * scale);
}
ValidateSolution();
}
/// <summary>
@@ -133,21 +340,31 @@ namespace Content.Shared.Chemistry.Components
/// <param name="scale">The scalar to modify the solution by.</param>
public void ScaleSolution(float scale)
{
if (scale.Equals(1f))
if (scale == 1)
return;
var tempContents = new List<ReagentQuantity>(Contents);
foreach(var current in tempContents)
if (scale == 0)
{
if(scale > 1)
{
AddReagent(current.ReagentId, current.Quantity * scale - current.Quantity);
}
RemoveAllSolution();
return;
}
Volume = FixedPoint2.Zero;
for (int i = Contents.Count - 1; i >= 0; i--)
{
var old = Contents[i];
var newQuantity = old.Quantity * scale;
if (newQuantity == FixedPoint2.Zero)
Contents.RemoveSwap(i);
else
{
RemoveReagent(current.ReagentId, current.Quantity - current.Quantity * scale);
Contents[i] = new ReagentQuantity(old.ReagentId, newQuantity);
Volume += newQuantity;
}
}
_heatCapacityDirty = true;
ValidateSolution();
}
/// <summary>
@@ -159,11 +376,12 @@ namespace Content.Shared.Chemistry.Components
{
for (var i = 0; i < Contents.Count; i++)
{
if (Contents[i].ReagentId == reagentId)
return Contents[i].Quantity;
var reagent = Contents[i];
if (reagent.ReagentId == reagentId)
return reagent.Quantity;
}
return FixedPoint2.New(0);
return FixedPoint2.Zero;
}
/// <summary>
@@ -174,7 +392,7 @@ namespace Content.Shared.Chemistry.Components
/// <returns>How much reagent was actually removed. Zero if the reagent is not present on the solution.</returns>
public FixedPoint2 RemoveReagent(string reagentId, FixedPoint2 quantity)
{
if(quantity <= 0)
if (quantity <= FixedPoint2.Zero)
return FixedPoint2.Zero;
for (var i = 0; i < Contents.Count; i++)
@@ -186,16 +404,19 @@ namespace Content.Shared.Chemistry.Components
var curQuantity = reagent.Quantity;
var newQuantity = curQuantity - quantity;
_heatCapacityDirty = true;
if (newQuantity <= 0)
{
Contents.RemoveSwap(i);
TotalVolume -= curQuantity;
Volume -= curQuantity;
ValidateSolution();
return curQuantity;
}
Contents[i] = new ReagentQuantity(reagentId, newQuantity);
TotalVolume -= quantity;
Volume -= quantity;
ValidateSolution();
return quantity;
}
@@ -203,104 +424,150 @@ namespace Content.Shared.Chemistry.Components
return FixedPoint2.Zero;
}
/// <summary>
/// Remove the specified quantity from this solution.
/// </summary>
/// <param name="quantity">The quantity of this solution to remove</param>
public void RemoveSolution(FixedPoint2 quantity)
{
if(quantity <= 0)
return;
var ratio = (TotalVolume - quantity).Double() / TotalVolume.Double();
if (ratio <= 0)
{
RemoveAllSolution();
return;
}
for (var i = 0; i < Contents.Count; i++)
{
var reagent = Contents[i];
var oldQuantity = reagent.Quantity;
// quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest
// whole unit. This should prevent little bits of chemical remaining because of float rounding errors.
var newQuantity = oldQuantity * ratio;
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
}
TotalVolume *= ratio;
}
public void RemoveAllSolution()
{
Contents.Clear();
TotalVolume = FixedPoint2.New(0);
Volume = FixedPoint2.Zero;
_heatCapacityDirty = false;
_heatCapacity = 0;
}
public Solution SplitSolution(FixedPoint2 quantity)
public Solution SplitSolution(FixedPoint2 toTake)
{
if (quantity <= 0)
if (toTake <= FixedPoint2.Zero)
return new Solution();
Solution newSolution;
if (quantity >= TotalVolume)
if (toTake >= Volume)
{
newSolution = Clone();
RemoveAllSolution();
return newSolution;
}
newSolution = new Solution();
var newTotalVolume = FixedPoint2.New(0);
var newHeatCapacity = 0.0d;
var remainingVolume = TotalVolume;
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var origVol = Volume;
var effVol = Volume.Value;
newSolution = new Solution(Contents.Count) { Temperature = Temperature };
var remaining = (long) toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--)
for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap.
{
if (remainingVolume == FixedPoint2.Zero)
// shouldn't happen, but it can if someone, somehow has a reagent with 0-quantity in a solution.
break;
var reagent = Contents[i];
var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double();
if(!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
proto = new ReagentPrototype();
remainingVolume -= reagent.Quantity;
// This is set up such that integer rounding will tend to take more reagents.
var split = remaining * reagent.Quantity.Value / effVol;
var newQuantity = reagent.Quantity * ratio;
var splitQuantity = reagent.Quantity - newQuantity;
if (split <= 0)
{
effVol -= reagent.Quantity.Value;
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
continue;
}
if (newQuantity > 0)
var splitQuantity = FixedPoint2.FromCents((int) split);
var newQuantity = reagent.Quantity - splitQuantity;
DebugTools.Assert(newQuantity >= 0);
if (newQuantity > FixedPoint2.Zero)
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
else
Contents.RemoveAt(i);
Contents.RemoveSwap(i);
if (splitQuantity > 0)
newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
newTotalVolume += splitQuantity;
newHeatCapacity += (float) splitQuantity * proto.SpecificHeat;
quantity -= splitQuantity;
newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
Volume -= splitQuantity;
remaining -= split;
effVol -= reagent.Quantity.Value;
}
newSolution.TotalVolume = newTotalVolume;
newSolution.Temperature = Temperature;
TotalVolume -= newTotalVolume;
newSolution.Volume = origVol - Volume;
DebugTools.Assert(remaining >= 0);
DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero);
_heatCapacityDirty = true;
newSolution._heatCapacityDirty = true;
ValidateSolution();
newSolution.ValidateSolution();
return newSolution;
}
public void AddSolution(Solution otherSolution)
/// <summary>
/// Variant of <see cref="SplitSolution(FixedPoint2)"/> that doesn't return a new solution containing the removed reagents.
/// </summary>
/// <param name="quantity">The quantity of this solution to remove</param>
public void RemoveSolution(FixedPoint2 toTake)
{
var oldThermalEnergy = Temperature * GetHeatCapacity();
var addedThermalEnergy = otherSolution.Temperature * otherSolution.GetHeatCapacity();
if (toTake <= FixedPoint2.Zero)
return;
if (toTake >= Volume)
{
RemoveAllSolution();
return;
}
var effVol = Volume.Value;
Volume -= toTake;
var remaining = (long) toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--)// iterate backwards because of remove swap.
{
var reagent = Contents[i];
// This is set up such that integer rounding will tend to take more reagents.
var split = remaining * reagent.Quantity.Value / effVol;
if (split <= 0)
{
effVol -= reagent.Quantity.Value;
DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?");
continue;
}
var splitQuantity = FixedPoint2.FromCents((int) split);
var newQuantity = reagent.Quantity - splitQuantity;
if (newQuantity > FixedPoint2.Zero)
Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
else
Contents.RemoveSwap(i);
remaining -= split;
effVol -= reagent.Quantity.Value;
}
DebugTools.Assert(remaining >= 0);
DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero);
_heatCapacityDirty = true;
ValidateSolution();
}
public void AddSolution(Solution otherSolution, IPrototypeManager? protoMan)
{
if (otherSolution.Volume <= FixedPoint2.Zero)
return;
Volume += otherSolution.Volume;
var closeTemps = MathHelper.CloseTo(otherSolution.Temperature, Temperature);
float totalThermalEnergy = 0;
if (!closeTemps)
{
IoCManager.Resolve(ref protoMan);
if (_heatCapacityDirty)
UpdateHeatCapacity(protoMan);
if (otherSolution._heatCapacityDirty)
otherSolution.UpdateHeatCapacity(protoMan);
totalThermalEnergy = _heatCapacity * Temperature + otherSolution._heatCapacity * otherSolution.Temperature;
}
for (var i = 0; i < otherSolution.Contents.Count; i++)
{
var otherReagent = otherSolution.Contents[i];
@@ -323,65 +590,50 @@ namespace Content.Shared.Chemistry.Components
}
}
TotalVolume += otherSolution.TotalVolume;
ThermalEnergy = oldThermalEnergy + addedThermalEnergy;
_heatCapacity += otherSolution._heatCapacity;
if (closeTemps)
_heatCapacityDirty |= otherSolution._heatCapacityDirty;
else
Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity;
ValidateSolution();
}
private Color GetColor()
public Color GetColor(IPrototypeManager? protoMan)
{
if (TotalVolume == 0)
if (Volume == FixedPoint2.Zero)
{
return Color.Transparent;
}
IoCManager.Resolve(ref protoMan);
Color mixColor = default;
var runningTotalQuantity = FixedPoint2.New(0);
var protoManager = IoCManager.Resolve<IPrototypeManager>();
bool first = true;
foreach (var reagent in Contents)
{
runningTotalQuantity += reagent.Quantity;
if (!protoManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
if (!protoMan.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
{
continue;
}
if (mixColor == default)
if (first)
{
first = false;
mixColor = proto.SubstanceColor;
continue;
}
var interpolateValue = (1 / runningTotalQuantity.Float()) * reagent.Quantity.Float();
var interpolateValue = reagent.Quantity.Float() / runningTotalQuantity.Float();
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue);
}
return mixColor;
}
public Solution Clone()
{
var volume = FixedPoint2.New(0);
var heatCapacity = 0.0d;
var newSolution = new Solution();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
for (var i = 0; i < Contents.Count; i++)
{
var reagent = Contents[i];
if (!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto))
proto = new ReagentPrototype();
newSolution.Contents.Add(reagent);
volume += reagent.Quantity;
heatCapacity += (float) reagent.Quantity * proto.SpecificHeat;
}
newSolution.TotalVolume = volume;
newSolution.Temperature = Temperature;
return newSolution;
}
[Obsolete("Use ReactiveSystem.DoEntityReaction")]
public void DoEntityReaction(EntityUid uid, ReactionMethod method)
{
@@ -429,7 +681,23 @@ namespace Content.Shared.Chemistry.Components
{
return GetEnumerator();
}
#endregion
public void SetContents(IEnumerable<ReagentQuantity> reagents, bool setMaxVol = false)
{
RemoveAllSolution();
_heatCapacityDirty = true;
Contents = new(reagents);
foreach (var reagent in Contents)
{
Volume += reagent.Quantity;
}
if (setMaxVol)
MaxVolume = Volume;
ValidateSolution();
}
}
}

View File

@@ -140,7 +140,7 @@ namespace Content.Shared.Chemistry.Reaction
var reactantName = reactantData.Key;
var reactantCoefficient = reactantData.Value.Amount;
if (!solution.ContainsReagent(reactantName, out var reactantQuantity))
if (!solution.TryGetReagent(reactantName, out var reactantQuantity))
return false;
if (reactantData.Value.Catalyst)
@@ -252,12 +252,12 @@ namespace Content.Shared.Chemistry.Reaction
// Remove any reactions that were not applicable. Avoids re-iterating over them in future.
reactions.Except(toRemove);
if (products.TotalVolume <= 0)
if (products.Volume <= 0)
return true;
// remove excess product
// TODO spill excess?
var excessVolume = solution.TotalVolume + products.TotalVolume - maxVolume;
var excessVolume = solution.Volume + products.Volume - maxVolume;
if (excessVolume > 0)
products.RemoveSolution(excessVolume);
@@ -269,7 +269,7 @@ namespace Content.Shared.Chemistry.Reaction
reactions.UnionWith(reactantReactions);
}
solution.AddSolution(products);
solution.AddSolution(products, _prototypeManager);
return true;
}

View File

@@ -11,7 +11,7 @@ namespace Content.Shared.FixedPoint
[Serializable, CopyByRef]
public struct FixedPoint2 : ISelfSerialize, IComparable<FixedPoint2>, IEquatable<FixedPoint2>, IFormattable
{
private int _value;
public int Value { get; private set; }
private const int Shift = 2;
public static FixedPoint2 MaxValue { get; } = new(int.MaxValue);
@@ -20,12 +20,12 @@ namespace Content.Shared.FixedPoint
private readonly double ShiftDown()
{
return _value / Math.Pow(10, Shift);
return Value / Math.Pow(10, Shift);
}
private FixedPoint2(int value)
{
_value = value;
Value = value;
}
public static FixedPoint2 New(int value)
@@ -33,6 +33,8 @@ namespace Content.Shared.FixedPoint
return new(value * (int) Math.Pow(10, Shift));
}
public static FixedPoint2 FromCents(int value) => new(value);
public static FixedPoint2 New(float value)
{
return new(FromFloat(value));
@@ -60,42 +62,42 @@ namespace Content.Shared.FixedPoint
public static FixedPoint2 operator +(FixedPoint2 a) => a;
public static FixedPoint2 operator -(FixedPoint2 a) => new(-a._value);
public static FixedPoint2 operator -(FixedPoint2 a) => new(-a.Value);
public static FixedPoint2 operator +(FixedPoint2 a, FixedPoint2 b)
=> new(a._value + b._value);
=> new(a.Value + b.Value);
public static FixedPoint2 operator -(FixedPoint2 a, FixedPoint2 b)
=> new(a._value - b._value);
=> new(a.Value - b.Value);
public static FixedPoint2 operator *(FixedPoint2 a, FixedPoint2 b)
{
return new((int) MathF.Round(b._value * a._value / MathF.Pow(10, Shift), MidpointRounding.AwayFromZero));
return new((int) MathF.Round(b.Value * a.Value / MathF.Pow(10, Shift), MidpointRounding.AwayFromZero));
}
public static FixedPoint2 operator *(FixedPoint2 a, float b)
{
return new((int) MathF.Round(a._value * b, MidpointRounding.AwayFromZero));
return new((int) MathF.Round(a.Value * b, MidpointRounding.AwayFromZero));
}
public static FixedPoint2 operator *(FixedPoint2 a, double b)
{
return new((int) Math.Round(a._value * b, MidpointRounding.AwayFromZero));
return new((int) Math.Round(a.Value * b, MidpointRounding.AwayFromZero));
}
public static FixedPoint2 operator *(FixedPoint2 a, int b)
{
return new(a._value * b);
return new(a.Value * b);
}
public static FixedPoint2 operator /(FixedPoint2 a, FixedPoint2 b)
{
return new((int) MathF.Round((MathF.Pow(10, Shift) * a._value) / b._value, MidpointRounding.AwayFromZero));
return new((int) MathF.Round((MathF.Pow(10, Shift) * a.Value) / b.Value, MidpointRounding.AwayFromZero));
}
public static FixedPoint2 operator /(FixedPoint2 a, float b)
{
return new((int) MathF.Round(a._value / b, MidpointRounding.AwayFromZero));
return new((int) MathF.Round(a.Value / b, MidpointRounding.AwayFromZero));
}
public static bool operator <=(FixedPoint2 a, int b)
@@ -140,22 +142,22 @@ namespace Content.Shared.FixedPoint
public static bool operator <=(FixedPoint2 a, FixedPoint2 b)
{
return a._value <= b._value;
return a.Value <= b.Value;
}
public static bool operator >=(FixedPoint2 a, FixedPoint2 b)
{
return a._value >= b._value;
return a.Value >= b.Value;
}
public static bool operator <(FixedPoint2 a, FixedPoint2 b)
{
return a._value < b._value;
return a.Value < b.Value;
}
public static bool operator >(FixedPoint2 a, FixedPoint2 b)
{
return a._value > b._value;
return a.Value > b.Value;
}
public readonly float Float()
@@ -199,7 +201,7 @@ namespace Content.Shared.FixedPoint
public static FixedPoint2 Abs(FixedPoint2 a)
{
return FixedPoint2.New(Math.Abs(a._value));
return FixedPoint2.New(Math.Abs(a.Value));
}
public static FixedPoint2 Dist(FixedPoint2 a, FixedPoint2 b)
@@ -220,18 +222,18 @@ namespace Content.Shared.FixedPoint
public override readonly bool Equals(object? obj)
{
return obj is FixedPoint2 unit &&
_value == unit._value;
Value == unit.Value;
}
public override readonly int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return HashCode.Combine(_value);
return HashCode.Combine(Value);
}
public void Deserialize(string value)
{
_value = FromFloat(FloatFromString(value));
Value = FromFloat(FloatFromString(value));
}
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
@@ -248,16 +250,16 @@ namespace Content.Shared.FixedPoint
public readonly bool Equals(FixedPoint2 other)
{
return _value == other._value;
return Value == other.Value;
}
public readonly int CompareTo(FixedPoint2 other)
{
if (other._value > _value)
if (other.Value > Value)
{
return -1;
}
if (other._value < _value)
if (other.Value < Value)
{
return 1;
}

View File

@@ -158,6 +158,20 @@ namespace Content.Shared.Movement.Systems
return;
}
var oldMapId = args.OldMapId;
var mapId = args.Transform.MapID;
// If we change maps then reset eye rotation entirely.
if (oldMapId != mapId)
{
component.RelativeEntity = relative;
component.TargetRelativeRotation = Angle.Zero;
component.RelativeRotation = Angle.Zero;
component.LerpAccumulator = 0f;
Dirty(component);
return;
}
// If we go on a grid and back off then just reset the accumulator.
if (relative == component.RelativeEntity)
{

View File

@@ -43,6 +43,7 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(quantity.Int(), Is.EqualTo(0));
}
#if !DEBUG
[Test]
public void AddLessThanZeroReagentReturnsZero()
{
@@ -51,6 +52,7 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(quantity.Int(), Is.EqualTo(0));
}
#endif
[Test]
public void AddingReagentsSumsProperly()
@@ -81,7 +83,7 @@ public sealed class Solution_Tests : ContentUnitTest
solution.AddReagent("water", FixedPoint2.New(1000));
solution.AddReagent("fire", FixedPoint2.New(2000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(3000));
Assert.That(solution.Volume.Int(), Is.EqualTo(3000));
}
[Test]
@@ -95,7 +97,7 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(newSolution.GetReagentQuantity("water").Int(), Is.EqualTo(1000));
Assert.That(newSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(newSolution.TotalVolume.Int(), Is.EqualTo(3000));
Assert.That(newSolution.Volume.Int(), Is.EqualTo(3000));
}
[Test]
@@ -109,7 +111,7 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2500));
Assert.That(solution.Volume.Int(), Is.EqualTo(2500));
}
[Test]
@@ -120,7 +122,7 @@ public sealed class Solution_Tests : ContentUnitTest
solution.RemoveReagent("water", FixedPoint2.New(-100));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100));
Assert.That(solution.Volume.Int(), Is.EqualTo(100));
}
[Test]
@@ -131,7 +133,7 @@ public sealed class Solution_Tests : ContentUnitTest
solution.RemoveReagent("water", FixedPoint2.New(1000));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
Assert.That(solution.Volume.Int(), Is.EqualTo(0));
}
[Test]
@@ -142,7 +144,7 @@ public sealed class Solution_Tests : ContentUnitTest
solution.RemoveReagent("fire", FixedPoint2.New(1000));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100));
Assert.That(solution.Volume.Int(), Is.EqualTo(100));
}
[Test]
@@ -154,7 +156,7 @@ public sealed class Solution_Tests : ContentUnitTest
//Check that edited solution is correct
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(200));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(200));
Assert.That(solution.Volume.Int(), Is.EqualTo(200));
}
[Test]
@@ -166,7 +168,7 @@ public sealed class Solution_Tests : ContentUnitTest
//Check that edited solution is correct
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
Assert.That(solution.Volume.Int(), Is.EqualTo(0));
}
[Test]
@@ -180,7 +182,7 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1000));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1500));
Assert.That(solution.Volume.Int(), Is.EqualTo(1500));
}
[Test]
@@ -191,7 +193,7 @@ public sealed class Solution_Tests : ContentUnitTest
solution.RemoveSolution(FixedPoint2.New(-200));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800));
Assert.That(solution.Volume.Int(), Is.EqualTo(800));
}
[Test]
@@ -205,11 +207,11 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(750));
Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1500));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2250));
Assert.That(solution.Volume.Int(), Is.EqualTo(2250));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(250));
Assert.That(splitSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(500));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(750));
Assert.That(splitSolution.Volume.Int(), Is.EqualTo(750));
}
[Test]
@@ -221,13 +223,13 @@ public sealed class Solution_Tests : ContentUnitTest
var splitSolution = solution.SplitSolution(FixedPoint2.New(1));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f));
Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.66f));
Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.34f));
Assert.That(solution.Volume.Int(), Is.EqualTo(2));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f));
Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(1));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.34f));
Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.66f));
Assert.That(splitSolution.Volume.Int(), Is.EqualTo(1));
}
[Test]
@@ -241,11 +243,11 @@ public sealed class Solution_Tests : ContentUnitTest
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f));
Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1));
Assert.That(solution.Volume.Int(), Is.EqualTo(1));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f));
Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(2));
Assert.That(splitSolution.Volume.Int(), Is.EqualTo(2));
}
[Test]
@@ -259,10 +261,10 @@ public sealed class Solution_Tests : ContentUnitTest
var splitSolution = solution.SplitSolution(FixedPoint2.New(reduce));
Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(remainder));
Assert.That(solution.TotalVolume.Float(), Is.EqualTo(remainder));
Assert.That(solution.Volume.Float(), Is.EqualTo(remainder));
Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(reduce));
Assert.That(splitSolution.TotalVolume.Float(), Is.EqualTo(reduce));
Assert.That(splitSolution.Volume.Float(), Is.EqualTo(reduce));
}
[Test]
@@ -280,7 +282,7 @@ public sealed class Solution_Tests : ContentUnitTest
var splitAmount = FixedPoint2.New(5);
var split = solutionOne.SplitSolution(splitAmount);
Assert.That(split.TotalVolume, Is.EqualTo(splitAmount));
Assert.That(split.Volume, Is.EqualTo(splitAmount));
}
[Test]
@@ -291,10 +293,10 @@ public sealed class Solution_Tests : ContentUnitTest
var splitSolution = solution.SplitSolution(FixedPoint2.New(1000));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0));
Assert.That(solution.Volume.Int(), Is.EqualTo(0));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(800));
Assert.That(splitSolution.Volume.Int(), Is.EqualTo(800));
}
[Test]
@@ -305,10 +307,10 @@ public sealed class Solution_Tests : ContentUnitTest
var splitSolution = solution.SplitSolution(FixedPoint2.New(-200));
Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800));
Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800));
Assert.That(solution.Volume.Int(), Is.EqualTo(800));
Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(0));
Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(0));
Assert.That(splitSolution.Volume.Int(), Is.EqualTo(0));
}
[Test]
@@ -343,12 +345,12 @@ public sealed class Solution_Tests : ContentUnitTest
solutionTwo.AddReagent("water", FixedPoint2.New(500));
solutionTwo.AddReagent("earth", FixedPoint2.New(1000));
solutionOne.AddSolution(solutionTwo);
solutionOne.AddSolution(solutionTwo, null);
Assert.That(solutionOne.GetReagentQuantity("water").Int(), Is.EqualTo(1500));
Assert.That(solutionOne.GetReagentQuantity("fire").Int(), Is.EqualTo(2000));
Assert.That(solutionOne.GetReagentQuantity("earth").Int(), Is.EqualTo(1000));
Assert.That(solutionOne.TotalVolume.Int(), Is.EqualTo(4500));
Assert.That(solutionOne.Volume.Int(), Is.EqualTo(4500));
}
// Tests concerning thermal energy and temperature.
@@ -359,24 +361,7 @@ public sealed class Solution_Tests : ContentUnitTest
public void EmptySolutionHasNoHeatCapacity()
{
var solution = new Solution();
Assert.That(solution.HeatCapacity, Is.EqualTo(0.0f));
}
[Test]
public void EmptySolutionHasNoThermalEnergy()
{
var solution = new Solution();
Assert.That(solution.ThermalEnergy, Is.EqualTo(0.0f));
}
[Test]
public void AddReagentToEmptySolutionSetsTemperature()
{
const float testTemp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), testTemp);
Assert.That(solution.Temperature, Is.EqualTo(testTemp));
Assert.That(solution.GetHeatCapacity(null), Is.EqualTo(0.0f));
}
[Test]
@@ -384,8 +369,7 @@ public sealed class Solution_Tests : ContentUnitTest
{
const float initialTemp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), initialTemp);
var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp };
solution.AddReagent("water", FixedPoint2.New(100));
Assert.That(solution.Temperature, Is.EqualTo(initialTemp));
@@ -408,7 +392,7 @@ public sealed class Solution_Tests : ContentUnitTest
solutionTwo.AddReagent("earth", FixedPoint2.New(100));
solutionTwo.Temperature = initialTemp;
solutionOne.AddSolution(solutionTwo);
solutionOne.AddSolution(solutionTwo, null);
Assert.That(solutionOne.Temperature, Is.EqualTo(initialTemp));
}
@@ -417,8 +401,7 @@ public sealed class Solution_Tests : ContentUnitTest
{
const float initialTemp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), initialTemp);
var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp };
solution.RemoveReagent("water", FixedPoint2.New(50));
Assert.That(solution.Temperature, Is.EqualTo(initialTemp));
}
@@ -428,8 +411,7 @@ public sealed class Solution_Tests : ContentUnitTest
{
const float initialTemp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), initialTemp);
var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp };
solution.RemoveSolution(FixedPoint2.New(50));
Assert.That(solution.Temperature, Is.EqualTo(initialTemp));
}
@@ -439,45 +421,10 @@ public sealed class Solution_Tests : ContentUnitTest
{
const float initialTemp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), initialTemp);
var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp };
solution.SplitSolution(FixedPoint2.New(50));
Assert.That(solution.Temperature, Is.EqualTo(initialTemp));
}
[Test]
public void AddReagentWithSetTemperatureAdjustsTemperature()
{
const float temp = 100.0f;
var solution = new Solution();
solution.AddReagent("water", FixedPoint2.New(100), temp * 1);
Assert.That(solution.Temperature, Is.EqualTo(temp * 1));
solution.AddReagent("water", FixedPoint2.New(100), temp * 3);
Assert.That(solution.Temperature, Is.EqualTo(temp * 2));
solution.AddReagent("earth", FixedPoint2.New(100), temp * 5);
Assert.That(solution.Temperature, Is.EqualTo(temp * 3));
}
[Test]
public void AddSolutionCombinesThermalEnergy()
{
const float initialTemp = 100.0f;
var solutionOne = new Solution();
solutionOne.AddReagent("water", FixedPoint2.New(100), initialTemp);
var solutionTwo = new Solution();
solutionTwo.AddReagent("water", FixedPoint2.New(100), initialTemp);
solutionTwo.AddReagent("earth", FixedPoint2.New(100));
var thermalEnergyOne = solutionOne.ThermalEnergy;
var thermalEnergyTwo = solutionTwo.ThermalEnergy;
solutionOne.AddSolution(solutionTwo);
Assert.That(solutionOne.ThermalEnergy, Is.EqualTo(thermalEnergyOne + thermalEnergyTwo));
}
#endregion Thermal Energy and Temperature
}

Binary file not shown.

View File

@@ -1,10 +1,11 @@
The following sounds were used from freesound:
herbertboland - heavyskirtmovement.wav - CC BY 3.0
vinrax - cloth-sounds.mp3 - CC BY-NC 3.0
lordvanye - skintouching.wav - CC0 1.0
lordvanye - skintouching.wav - CC0 1.0
FM Synthesis - healthscanner.ogg - CC BY 4.0
Additional sound effects from https://www.zapsplat.com
foley-cloth-denim-rip-003.mp3 -
zapsplat_food_bottle_golden_syrup_cap_open_then_close.mp3
foley-cloth-denim-rip-003.mp3 -
zapsplat_food_bottle_golden_syrup_cap_open_then_close.mp3

View File

@@ -1,79 +1,4 @@
Entries:
- author: Mervill
changes:
- {message: Forensic pad is now tagged as a document. They can be stored in filling
cabinets and folders., type: Fix}
id: 2350
time: '2022-09-22T15:46:11.0000000+00:00'
- author: vulppine
changes:
- {message: We have added a little sticker to some of your devices that should make
it easier for you to figure out what that device's local address is., type: Add}
id: 2351
time: '2022-09-22T19:00:54.0000000+00:00'
- author: Morb0
changes:
- {message: New backpacks for salvage specialists, type: Add}
- {message: Added missing botany duffle bag, type: Add}
id: 2352
time: '2022-09-22T19:02:30.0000000+00:00'
- author: Mervill
changes:
- {message: The agent id card interface now remembers the previous fake name and
title., type: Fix}
id: 2353
time: '2022-09-22T19:04:01.0000000+00:00'
- author: lapatison
changes:
- {message: 'psychologist, detective, and reporter ID cards now display correct
layers', type: Fix}
id: 2354
time: '2022-09-22T21:00:14.0000000+00:00'
- author: vulppine
changes:
- {message: Humanoids were recently changed. Expect to see more variations of humanoids
in places, type: Tweak}
id: 2355
time: '2022-09-22T22:19:00.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Gave descriptions to the shaker and shot glass., type: Add}
id: 2356
time: '2022-09-22T23:55:31.0000000+00:00'
- author: illiux
changes:
- {message: Latejoin spawns to Pillar, type: Add}
id: 2357
time: '2022-09-23T04:19:07.0000000+00:00'
- author: Peptide90
changes:
- {message: 'Minor update and fixes for Splitstation. Salvage is closer to Science,
atmos works and other minor changes.', type: Tweak}
id: 2358
time: '2022-09-24T21:48:39.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Mouse Migration events are now less common., type: Tweak}
id: 2359
time: '2022-09-25T04:44:29.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: Fixes an issue where you could insert incorrect materials into lathes
exactly once., type: Fix}
id: 2360
time: '2022-09-25T14:45:29.0000000+00:00'
- author: Scribbles0
changes:
- {message: Fixed Vector magazines showing the wrong name., type: Fix}
- {message: Gun descriptions now show the type of ammo it uses when examined., type: Add}
id: 2361
time: '2022-09-26T04:51:53.0000000+00:00'
- author: Peptide90
changes:
- {message: 'Pillar minor update. Evac is good now, additional jani closet in Dorms,
PTech for HoP. Salvage is back near Cargo. Cargo should space less.', type: Tweak}
id: 2362
time: '2022-09-26T19:35:42.0000000+00:00'
- author: Mervill
changes:
- {message: Proper feedback for when items thrown toward the disposal bin don't
@@ -2959,3 +2884,73 @@ Entries:
break naming rules.', type: Fix}
id: 2849
time: '2023-01-10T12:45:07.0000000+00:00'
- author: DrSmugleaf
changes:
- {message: Fixed AHelp relay to discord not working., type: Fix}
id: 2850
time: '2023-01-10T16:33:39.0000000+00:00'
- author: DrSmugleaf
changes:
- {message: Fixed togglehealthoverlay command not working, type: Fix}
id: 2851
time: '2023-01-10T21:36:48.0000000+00:00'
- author: DrSmugleaf
changes:
- {message: Added showing the player name to admins on the context menu., type: Add}
id: 2852
time: '2023-01-10T21:39:48.0000000+00:00'
- author: DrSmugleaf
changes:
- {message: Fixed admins blowing out your eardrums when playing a global sound.,
type: Fix}
id: 2853
time: '2023-01-10T21:45:09.0000000+00:00'
- author: Scribbles0
changes:
- {message: Narcolepsy can now be treated., type: Add}
id: 2854
time: '2023-01-11T08:14:16.0000000+00:00'
- author: mirrorcult
changes:
- {message: Phasing artifacts are now scannable again., type: Fix}
- {message: 'Artifacts can now be anchored onto the scanner, so wandering artifacts
are no longer essentially impossible to scan.', type: Fix}
id: 2855
time: '2023-01-11T08:16:16.0000000+00:00'
- author: Theomund
changes:
- {message: Screaming now emits a popup message that others can see., type: Add}
id: 2856
time: '2023-01-11T09:06:15.0000000+00:00'
- author: EmoGarbage404
changes:
- {message: You can now withdraw currency from the uplink again., type: Fix}
id: 2857
time: '2023-01-11T22:03:18.0000000+00:00'
- author: Chief-Engineer
changes:
- {message: Rounds will now have 2 minutes between the round ending and returning
to the lobby., type: Tweak}
id: 2858
time: '2023-01-11T23:36:28.0000000+00:00'
- author: nmajask
changes:
- {message: added goliath cloaks based on the one from TG that can rarely spawn
in maints, type: Add}
id: 2859
time: '2023-01-12T03:46:48.0000000+00:00'
- author: nmajask
changes:
- {message: a sound effect to health analyzers on a successful scan, type: Add}
id: 2860
time: '2023-01-12T06:38:39.0000000+00:00'
- author: metalgearsloth
changes:
- {message: Eye lerping will reset on map changes., type: Tweak}
id: 2861
time: '2023-01-12T12:10:52.0000000+00:00'
- author: Morb0
changes:
- {message: Worn lawyer badge now changes the style of the typing indicator, type: Add}
id: 2862
time: '2023-01-12T12:33:57.0000000+00:00'

View File

@@ -1 +1,2 @@
action-name-scream = Scream
action-name-scream = Scream
scream-action-popup = Screams!

View File

@@ -4,6 +4,9 @@ technologies-basic-research-technology-description = Nanotrasen basic research t
technologies-cleaning-technology = Cleaning technology
technologies-cleaning-technology-description = Start to a shiny clean station.
technologies-foodbev-technology = Food and beverage technology
technologies-food-and-beverage-technology-description = Robust service from better technology.
technologies-biological-technology = Biological technology
technologies-biological-technology-description = Investigations into the natural world.

View File

@@ -28,3 +28,6 @@ ent-ClothingHeadHatHoodNunHood = nun hood
ent-ClothingHeadHatHoodRad = radiation hood
.desc = A hood of the hazmat suit, designed for protection from high radioactivity.
.suffix = { "" }
ent-ClothingHeadHatHoodGoliathCloak = goliath cloak hood
.desc = A hood of a goliath cloak, it is made from the hide of resilient fauna from a distant planet.
.suffix = { "" }

View File

@@ -37,3 +37,6 @@ ent-ClothingNeckCloakMiner = miner's cloak
ent-ClothingNeckCloakTrans = vampire cloak
.desc = Worn by high ranking vampires of the transylvanian society of vampires.
.suffix = { "" }
ent-ClothingNeckCloakGoliathCloak = goliath cloak
.desc = A cloak made from the hide of resilient fauna from a distant planet, though its protective value has faded with its age.
.suffix = { "" }

View File

@@ -118,3 +118,9 @@ ent-SurveillanceWirelessCameraAnchoredCircuitboard = wireless camera board
ent-GasRecyclerMachineCircuitboard = gas recycler board
.desc = A printed circuit board for a gas recycler
.suffix = { "" }
ent-BoozeDispenserMachineCircuitboard = booze dispenser machine board
.desc = A machine printed circuit board for a booze dispenser
.suffix = { "" }
ent-SodaDispenserMachineCircuitboard = soda dispenser machine board
.desc = A machine printed circuit board for a soda dispenser
.suffix = { "" }

View File

@@ -16,7 +16,7 @@ artifact-effect-hint-storage = Internal chamber
artifact-effect-hint-drill = Serrated rotator
artifact-effect-hint-soap = Lubricated surface
artifact-effect-hint-communication = Long-distance communication
artifact-effect-hint-fixtures = Structural phasing
artifact-effect-hint-phasing = Structural phasing
artifact-effect-hint-sentience = Neurological activity
# the triggers should be more obvious than the effects

View File

@@ -1 +1,2 @@
action-name-scream = Крикнуть
scream-action-popup = Screams!

View File

@@ -1,4 +1,4 @@
ui-options-tts-volume = Громкость TTS:
ui-options-tts-volume = Громкость TTS:
credits-window-tts-title = Функция TTS (Text-To-Speech)
humanoid-profile-editor-voice-label = Голос:
humanoid-profile-editor-voice-play = ▶

View File

@@ -1,4 +1,4 @@
tts-voice-name-aidar = Aidar
tts-voice-name-aidar = Aidar
tts-voice-name-baya = Baya
tts-voice-name-kseniya = Kseniya
tts-voice-name-xenia = Xenia

View File

@@ -2,6 +2,8 @@ technologies-basic-research-technology = Базовые исследования
technologies-basic-research-technology-description = Технология базовых исследований Nanotrasen.
technologies-cleaning-technology = Технология уборки
technologies-cleaning-technology-description = Начало пути к сияющей чистотой станции.
technologies-foodbev-technology = Food and beverage technology
technologies-food-and-beverage-technology-description = Robust service from better technology.
technologies-biological-technology = Биология
technologies-biological-technology-description = Исследования окружающей нас природы.
technologies-advanced-botany = Продвинутая ботаника

View File

@@ -1,6 +1,3 @@
ent-ClothingBackpackSatchelHoS = сумка главы службы безопасности
.desc = Стильная кожаная чёрная сумка.
.suffix = { "" }
ent-ClothingBackpackSatchelSecurity = сумка главы службы безопасности
.desc = Стильная кожаная чёрная сумка.
.suffix = { "" }

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