diff --git a/BuildChecker/BuildChecker.csproj b/BuildChecker/BuildChecker.csproj index 4bd7fcf78c..c0a0c9e1f3 100644 --- a/BuildChecker/BuildChecker.csproj +++ b/BuildChecker/BuildChecker.csproj @@ -15,7 +15,6 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild python3 - py -3 {C899FCA4-7037-4E49-ABC2-44DE72487110} net4.7.2 false diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py index becd4506e8..83c41c3403 100644 --- a/BuildChecker/git_helper.py +++ b/BuildChecker/git_helper.py @@ -10,7 +10,7 @@ from typing import List SOLUTION_PATH = Path("..") / "SpaceStation14.sln" # If this doesn't match the saved version we overwrite them all. -CURRENT_HOOKS_VERSION = "2" +CURRENT_HOOKS_VERSION = "3" QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet" diff --git a/BuildChecker/hooks/post-checkout b/BuildChecker/hooks/post-checkout index c5662445c2..f34ffd293d 100755 --- a/BuildChecker/hooks/post-checkout +++ b/BuildChecker/hooks/post-checkout @@ -4,10 +4,4 @@ gitroot=`git rev-parse --show-toplevel` cd "$gitroot/BuildChecker" -if [[ `uname` == MINGW* || `uname` == CYGWIN* ]]; then - # Windows - py -3 git_helper.py --quiet -else - # Not Windows, so probably some other Unix thing. - python3 git_helper.py --quiet -fi +python3 git_helper.py --quiet diff --git a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs index 312c4e8019..99f9b34387 100644 --- a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs @@ -75,10 +75,9 @@ public sealed partial class CargoSystem switch (state) { case CargoTelepadState.Teleporting: - if (_player.HasRunningAnimation(uid, TelepadBeamKey)) - return; - _player.Stop(uid, player, TelepadIdleKey); - _player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey); + _player.Stop((uid, player), TelepadIdleKey); + if (!_player.HasRunningAnimation(uid, TelepadBeamKey)) + _player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey); break; case CargoTelepadState.Unpowered: sprite.LayerSetVisible(CargoTelepadLayers.Beam, false); diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs index 85d65020c6..5eaed77041 100644 --- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs +++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs @@ -213,52 +213,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow return; } - var entries = listing.ToList(); - entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal)); - // `entries` now contains the definitive list of items which should be in - // our list of records and is in the order we want to present those items. - - // Walk through the existing items in RecordListing and in the updated listing - // in parallel to synchronize the items in RecordListing with `entries`. - int i = RecordListing.Count - 1; - int j = entries.Count - 1; - while (i >= 0 && j >= 0) - { - var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal); - if (strcmp == 0) - { - // This item exists in both RecordListing and `entries`. Nothing to do. - i--; - j--; - } - else if (strcmp > 0) - { - // Item exists in RecordListing, but not in `entries`. Remove it. - RecordListing.RemoveAt(i); - i--; - } - else if (strcmp < 0) - { - // A new entry which doesn't exist in RecordListing. Create it. - RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key}); - j--; - } - } - - // Any remaining items in RecordListing don't exist in `entries`, so remove them - while (i >= 0) - { - RecordListing.RemoveAt(i); - i--; - } - - // And finally, any remaining items in `entries`, don't exist in RecordListing. Create them. - while (j >= 0) - { - RecordListing.Insert(0, new ItemList.Item(RecordListing){ Text = entries[j].Value, Metadata = entries[j].Key }); - j--; - } + var entries = listing.Select(i => new ItemList.Item(RecordListing) { + Text = i.Value, + Metadata = i.Key + }).ToList(); + entries.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.Ordinal)); + RecordListing.SetItems(entries, (a,b) => string.Compare(a.Text, b.Text)); } + private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord) { var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown"); diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs index 0237b86db7..3cf1792c14 100644 --- a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs +++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs @@ -3,6 +3,7 @@ using Content.Shared.Materials; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Client.Materials.UI; @@ -17,7 +18,7 @@ public sealed partial class MaterialStorageControl : ScrollContainer private EntityUid? _owner; - private Dictionary _currentMaterials = new(); + private Dictionary, int> _currentMaterials = new(); public MaterialStorageControl() { @@ -44,7 +45,7 @@ public sealed partial class MaterialStorageControl : ScrollContainer } var canEject = materialStorage.CanEjectStoredMaterials; - var mats = materialStorage.Storage.Select(pair => (pair.Key.Id, pair.Value)).ToDictionary(); + var mats = materialStorage.Storage; if (_currentMaterials.Equals(mats)) return; diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index c2ba35f3c5..d020a71359 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -632,8 +632,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged ent.EntityCount - ent.Count(); + IEnumerable Entities(IEntityManager entMan) => entMan.GetEntities().Where(entMan.HasComponent); - foreach (var protoId in protoIds) + await Assert.MultipleAsync(async () => { - // TODO fix ninja - // Currently ninja fails to equip their own loadout. - if (protoId == "MobHumanSpaceNinja") - continue; - - var count = Count(server.EntMan); - var clientCount = Count(client.EntMan); - EntityUid uid = default; - await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords)); - await pair.RunTicksSync(3); - - // If the entity deleted itself, check that it didn't spawn other entities - if (!server.EntMan.EntityExists(uid)) + foreach (var protoId in protoIds) { - if (Count(server.EntMan) != count) + // TODO fix ninja + // Currently ninja fails to equip their own loadout. + if (protoId == "MobHumanSpaceNinja") + continue; + + var count = Count(server.EntMan); + var clientCount = Count(client.EntMan); + var serverEntities = new HashSet(Entities(server.EntMan)); + var clientEntities = new HashSet(Entities(client.EntMan)); + EntityUid uid = default; + await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords)); + await pair.RunTicksSync(3); + + // If the entity deleted itself, check that it didn't spawn other entities + if (!server.EntMan.EntityExists(uid)) { - Assert.Fail($"Server prototype {protoId} failed on deleting itself"); + Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deleting itself\n" + + BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan)); + Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deleting itself\n" + + $"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" + + $"Server count was {count}.\n" + + BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan)); + continue; } - if (Count(client.EntMan) != clientCount) - { - Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" + - $"Expected {clientCount} and found {Count(client.EntMan)}.\n" + - $"Server was {count}."); - } - continue; - } + // Check that the number of entities has increased. + Assert.That(Count(server.EntMan), Is.GreaterThan(count), $"Server prototype {protoId} failed on spawning as entity count didn't increase\n" + + BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan)); + Assert.That(Count(client.EntMan), Is.GreaterThan(clientCount), $"Client prototype {protoId} failed on spawning as entity count didn't increase\n" + + $"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " + + $"Server count was {count}.\n" + + BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan)); - // Check that the number of entities has increased. - if (Count(server.EntMan) <= count) - { - Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase"); - } + await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); + await pair.RunTicksSync(3); - if (Count(client.EntMan) <= clientCount) - { - Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" + - $"Expected at least {clientCount} and found {Count(client.EntMan)}. " + - $"Server was {count}"); + // Check that the number of entities has gone back to the original value. + Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deletion: count didn't reset properly\n" + + BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan)); + Assert.That(client.EntMan.EntityCount, Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" + + $"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" + + $"Server count was {count}.\n" + + BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan)); } - - await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); - await pair.RunTicksSync(3); - - // Check that the number of entities has gone back to the original value. - if (Count(server.EntMan) != count) - { - Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly"); - } - - if (Count(client.EntMan) != clientCount) - { - Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" + - $"Expected {clientCount} and found {Count(client.EntMan)}.\n" + - $"Server was {count}."); - } - } + }); await pair.CleanReturnAsync(); } + private static string BuildDiffString(IEnumerable oldEnts, IEnumerable newEnts, IEntityManager entMan) + { + var sb = new StringBuilder(); + var addedEnts = newEnts.Except(oldEnts); + var removedEnts = oldEnts.Except(newEnts); + if (addedEnts.Any()) + sb.AppendLine("Listing new entities:"); + foreach (var addedEnt in addedEnts) + { + sb.AppendLine(entMan.ToPrettyString(addedEnt)); + } + if (removedEnts.Any()) + sb.AppendLine("Listing removed entities:"); + foreach (var removedEnt in removedEnts) + { + sb.AppendLine("\t" + entMan.ToPrettyString(removedEnt)); + } + return sb.ToString(); + } + + private static bool HasRequiredDataField(Component component) + { + foreach (var field in component.GetType().GetFields()) + { + foreach (var attribute in field.GetCustomAttributes(true)) + { + if (attribute is not DataFieldAttribute dataField) + continue; + + if (dataField.Required) + return true; + } + } + foreach (var property in component.GetType().GetProperties()) + { + foreach (var attribute in property.GetCustomAttributes(true)) + { + if (attribute is not DataFieldAttribute dataField) + continue; + + if (dataField.Required) + return true; + } + } + return false; + } + [Test] public async Task AllComponentsOneToOneDeleteTest() { @@ -362,9 +402,6 @@ namespace Content.IntegrationTests.Tests "ActivatableUI", // Requires enum key }; - // TODO TESTS - // auto ignore any components that have a "required" data field. - await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.ResolveDependency(); @@ -382,9 +419,12 @@ namespace Content.IntegrationTests.Tests foreach (var type in componentFactory.AllRegisteredTypes) { - var component = (Component) componentFactory.GetComponent(type); + var component = (Component)componentFactory.GetComponent(type); var name = componentFactory.GetComponentName(type); + if (HasRequiredDataField(component)) + continue; + // If this component is ignored if (skipComponents.Contains(name)) { diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index e6422f0ec4..4b020e9850 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -1,12 +1,13 @@ +#nullable enable using System.Collections.Generic; using Content.Server.Cargo.Systems; using Content.Server.Construction.Completions; using Content.Server.Construction.Components; using Content.Server.Destructible; using Content.Server.Destructible.Thresholds.Behaviors; +using Content.Server.Lathe; using Content.Server.Stack; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Construction.Components; using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Steps; using Content.Shared.FixedPoint; @@ -14,10 +15,9 @@ using Content.Shared.Lathe; using Content.Shared.Materials; using Content.Shared.Research.Prototypes; using Content.Shared.Stacks; +using Content.Shared.Tools.Components; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests; @@ -28,6 +28,21 @@ namespace Content.IntegrationTests.Tests; [TestFixture] public sealed class MaterialArbitrageTest { + // These recipes are currently broken and need fixing. You should not be adding to these sets. + private readonly HashSet _destructionArbitrageIgnore = + [ + "BaseChemistryEmptyVial", "DrinkShotGlass", "Beaker", "SodiumLightTube", "DrinkGlassCoupeShaped", + "LedLightBulb", "ExteriorLightTube", "LightTube", "DrinkGlass", "DimLightBulb", "LightBulb", "LedLightTube", + "SheetRGlass1", "ChemistryEmptyBottle01", "WarmLightBulb", + ]; + + private readonly HashSet _compositionArbitrageIgnore = + [ + "FoodPlateSmall", "AirTank", "FoodPlateTin", "FoodPlateMuffinTin", "WeaponCapacitorRechargerCircuitboard", + "WeaponCapacitorRechargerCircuitboard", "BorgChargerCircuitboard", "BorgChargerCircuitboard", "FoodPlate", + "CellRechargerCircuitboard", "CellRechargerCircuitboard", + ]; + [Test] public async Task NoMaterialArbitrage() { @@ -38,13 +53,12 @@ public sealed class MaterialArbitrageTest await server.WaitIdleAsync(); var entManager = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); var protoManager = server.ResolveDependency(); var pricing = entManager.System(); var stackSys = entManager.System(); var mapSystem = server.System(); - var latheSys = server.System(); + var latheSys = server.System(); var compFact = server.ResolveDependency(); Assert.That(mapSystem.IsInitialized(testMap.MapId)); @@ -53,13 +67,24 @@ public sealed class MaterialArbitrageTest var compositionName = compFact.GetComponentName(typeof(PhysicalCompositionComponent)); var materialName = compFact.GetComponentName(typeof(MaterialComponent)); var destructibleName = compFact.GetComponentName(typeof(DestructibleComponent)); + var refinableName = compFact.GetComponentName(typeof(ToolRefinableComponent)); // get the inverted lathe recipe dictionary var latheRecipes = latheSys.InverseRecipes; - // Lets assume the possible lathe for resource multipliers: - // TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in. - var multiplier = MathF.Pow(0.85f, 3); + // Find the lowest multiplier / optimal lathe that can be used to construct a recipie. + var minMultiplier = new Dictionary, float>(); + + foreach (var (_, lathe) in pair.GetPrototypesWithComponent()) + { + foreach (var recipe in latheSys.GetAllPossibleRecipes(lathe)) + { + if (!minMultiplier.TryGetValue(recipe, out var min)) + min = 1; + + minMultiplier[recipe] = Math.Min(min, lathe.MaterialUseMultiplier); + } + } // create construction dictionary Dictionary constructionRecipes = new(); @@ -122,6 +147,65 @@ public sealed class MaterialArbitrageTest Dictionary Ents, Dictionary Mats)> spawnedOnDestroy = new(); + // cache the compositions of entities + // If the entity is refineable (i.e. glass shared can be turned into glass, we take the greater of the two compositions. + Dictionary> compositions = new(); + foreach (var proto in protoManager.EnumeratePrototypes()) + { + Dictionary? baseComposition = null; + + if (proto.Components.ContainsKey(materialName) + && proto.Components.TryGetValue(compositionName, out var compositionReg)) + { + var compositionComp = (PhysicalCompositionComponent)compositionReg.Component; + baseComposition = compositionComp.MaterialComposition; + + } + + if (!proto.Components.TryGetValue(refinableName, out var refinableReg)) + { + if (baseComposition != null) + compositions[proto.ID] = new(baseComposition); + continue; + } + + var composition = new Dictionary(); + compositions.Add(proto.ID, composition); + + var refinable = (ToolRefinableComponent)refinableReg.Component; + foreach (var refineResult in refinable.RefineResult) + { + if (refineResult.PrototypeId == null) + continue; + + var refineProto = protoManager.Index(refineResult.PrototypeId.Value); + if (!refineProto.Components.ContainsKey(materialName)) + continue; + + if (!refineProto.Components.TryGetValue(compositionName, out var refinedCompositionReg)) + continue; + + var refinedCompositionComp = (PhysicalCompositionComponent)refinedCompositionReg.Component; + + // This assumes refine results do not have complex spawn behaviours like exclusive groups. + var quantity = refineResult.MaxAmount; + + foreach (var (matId, amount) in refinedCompositionComp.MaterialComposition) + { + composition[matId] = quantity * amount + composition.GetValueOrDefault(matId); + } + } + + if (baseComposition == null) + continue; + + // If the un-refined material quantity is greater than the refined quantity, we use that instead. + foreach (var (matId, amount) in baseComposition) + { + composition[matId] = Math.Max(amount, composition.GetValueOrDefault(matId)); + } + } + // Here we get the set of entities/materials spawned when destroying an entity. foreach (var proto in protoManager.EnumeratePrototypes()) { @@ -151,16 +235,10 @@ public sealed class MaterialArbitrageTest { spawnedEnts[key] = spawnedEnts.GetValueOrDefault(key) + value.Max; - var spawnProto = protoManager.Index(key); - - // get the amount of each material included in the entity - - if (!spawnProto.Components.ContainsKey(materialName) || - !spawnProto.Components.TryGetValue(compositionName, out var compositionReg)) + if (!compositions.TryGetValue(key, out var composition)) continue; - var mat = (PhysicalCompositionComponent) compositionReg.Component; - foreach (var (matId, amount) in mat.MaterialComposition) + foreach (var (matId, amount) in composition) { spawnedMats[matId] = value.Max * amount + spawnedMats.GetValueOrDefault(matId); } @@ -173,10 +251,13 @@ public sealed class MaterialArbitrageTest } // This is the main loop where we actually check for destruction arbitrage - Assert.Multiple(async () => + await Assert.MultipleAsync(async () => { foreach (var (id, (spawnedEnts, spawnedMats)) in spawnedOnDestroy) { + if (_destructionArbitrageIgnore.Contains(id)) + continue; + // Check cargo sell price // several constructible entities have no sell price // also this test only really matters if the entity is also purchaseable.... eh.. @@ -190,6 +271,11 @@ public sealed class MaterialArbitrageTest { foreach (var recipe in recipes) { + if (!minMultiplier.TryGetValue(recipe, out var multiplier)) + { + server.Log.Info($"Unused lathe recipe? {recipe.ID}?"); + continue; + } foreach (var (matId, amount) in recipe.Materials) { var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); @@ -231,6 +317,9 @@ public sealed class MaterialArbitrageTest var edge = cur.GetEdge(node.Name); cur = node; + if (edge == null) + continue; + foreach (var completion in edge.Completed) { if (completion is not SpawnPrototype spawnCompletion) @@ -253,9 +342,9 @@ public sealed class MaterialArbitrageTest } // This is functionally the same loop as before, but now testing deconstruction rather than destruction. - // This is pretty braindead. In principle construction graphs can have loops and whatnot. + // This is pretty brain-dead. In principle construction graphs can have loops and whatnot. - Assert.Multiple(async () => + await Assert.MultipleAsync(async () => { foreach (var (id, deconstructedMats) in deconstructionMaterials) { @@ -270,6 +359,11 @@ public sealed class MaterialArbitrageTest { foreach (var recipe in recipes) { + if (!minMultiplier.TryGetValue(recipe, out var multiplier)) + { + server.Log.Info($"Unused lathe recipe? {recipe.ID}?"); + continue; + } foreach (var (matId, amount) in recipe.Materials) { var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); @@ -291,7 +385,7 @@ public sealed class MaterialArbitrageTest } }); - // create phyiscal composition dictionary + // create physical composition dictionary // this doesn't account for the chemicals in the composition Dictionary physicalCompositions = new(); foreach (var proto in protoManager.EnumeratePrototypes()) @@ -308,10 +402,13 @@ public sealed class MaterialArbitrageTest // This is functionally the same loop as before, but now testing composition rather than destruction or deconstruction. // This doesn't take into account chemicals generated when deconstructing. Maybe it should. - Assert.Multiple(async () => + await Assert.MultipleAsync(async () => { foreach (var (id, compositionComponent) in physicalCompositions) { + if (_compositionArbitrageIgnore.Contains(id)) + continue; + // Check cargo sell price var materialPrice = await GetDeconstructedPrice(compositionComponent.MaterialComposition); var chemicalPrice = await GetChemicalCompositionPrice(compositionComponent.ChemicalComposition); @@ -325,6 +422,11 @@ public sealed class MaterialArbitrageTest { foreach (var recipe in recipes) { + if (!minMultiplier.TryGetValue(recipe, out var multiplier)) + { + server.Log.Info($"Unused lathe recipe? {recipe.ID}?"); + continue; + } foreach (var (matId, amount) in recipe.Materials) { var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); diff --git a/Content.MapRenderer/Program.cs b/Content.MapRenderer/Program.cs index 7314119108..5a4ccda0c8 100644 --- a/Content.MapRenderer/Program.cs +++ b/Content.MapRenderer/Program.cs @@ -38,6 +38,7 @@ namespace Content.MapRenderer var mapIds = pair.Server .ResolveDependency() .EnumeratePrototypes() + .Where(map => !pair.IsTestPrototype(map)) .Select(map => map.ID) .ToArray(); diff --git a/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs b/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs new file mode 100644 index 0000000000..aa6c5ba2bd --- /dev/null +++ b/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs @@ -0,0 +1,33 @@ +using Content.Server.AlertLevel.Systems; + +namespace Content.Server.AlertLevel; +/// +/// This component is for changing the alert level of the station when triggered. +/// +[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))] +public sealed partial class AlertLevelChangeOnTriggerComponent : Component +{ + /// + ///The alert level to change to when triggered. + /// + [DataField] + public string Level = "blue"; + + /// + ///Whether to play the sound when the alert level changes. + /// + [DataField] + public bool PlaySound = true; + + /// + ///Whether to say the announcement when the alert level changes. + /// + [DataField] + public bool Announce = true; + + /// + ///Force the alert change. This applies if the alert level is not selectable or not. + /// + [DataField] + public bool Force = false; +} diff --git a/Content.Server/AlertLevel/AlertLevelSystem.cs b/Content.Server/AlertLevel/AlertLevelSystem.cs index da78ec9860..dc3241f5a9 100644 --- a/Content.Server/AlertLevel/AlertLevelSystem.cs +++ b/Content.Server/AlertLevel/AlertLevelSystem.cs @@ -118,6 +118,20 @@ public sealed class AlertLevelSystem : EntitySystem return alert.CurrentDelay; } + /// + /// Get the default alert level for a station entity. + /// Returns an empty string if the station has no alert levels defined. + /// + /// The station entity. + public string GetDefaultLevel(Entity station) + { + if (!Resolve(station.Owner, ref station.Comp) || station.Comp.AlertLevels == null) + { + return string.Empty; + } + return station.Comp.AlertLevels.DefaultLevel; + } + /// /// Set the alert level based on the station's entity ID. /// diff --git a/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs b/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs new file mode 100644 index 0000000000..0c9734b943 --- /dev/null +++ b/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs @@ -0,0 +1,27 @@ +using Content.Server.AlertLevel; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Station.Systems; + +namespace Content.Server.AlertLevel.Systems; + +public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + var stationUid = _station.GetOwningStation(ent.Owner); + if (!stationUid.HasValue) + return; + + _alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force); + } +} diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 1f16b01f1e..8f4f1c3431 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -62,7 +62,6 @@ namespace Content.Server.Lathe /// Per-tick cache /// private readonly List _environments = new(); - private readonly HashSet> _availableRecipes = new(); public override void Initialize() { @@ -163,12 +162,8 @@ namespace Content.Server.Lathe public List> GetAvailableRecipes(EntityUid uid, LatheComponent component, bool getUnavailable = false) { - _availableRecipes.Clear(); - AddRecipesFromPacks(_availableRecipes, component.StaticPacks); - var ev = new LatheGetRecipesEvent(uid, getUnavailable) - { - Recipes = _availableRecipes - }; + var ev = new LatheGetRecipesEvent((uid, component), getUnavailable); + AddRecipesFromPacks(ev.Recipes, component.StaticPacks); RaiseLocalEvent(uid, ev); return ev.Recipes.ToList(); } @@ -292,7 +287,7 @@ namespace Content.Server.Lathe var pack = _proto.Index(id); foreach (var recipe in pack.Recipes) { - if (args.getUnavailable || database.UnlockedRecipes.Contains(recipe)) + if (args.GetUnavailable || database.UnlockedRecipes.Contains(recipe)) args.Recipes.Add(recipe); } } @@ -300,10 +295,8 @@ namespace Content.Server.Lathe private void OnGetRecipes(EntityUid uid, TechnologyDatabaseComponent component, LatheGetRecipesEvent args) { - if (uid != args.Lathe || !TryComp(uid, out var latheComponent)) - return; - - AddRecipesFromDynamicPacks(ref args, component, latheComponent.DynamicPacks); + if (uid == args.Lathe) + AddRecipesFromDynamicPacks(ref args, component, args.Comp.DynamicPacks); } private void GetEmagLatheRecipes(EntityUid uid, EmagLatheRecipesComponent component, LatheGetRecipesEvent args) @@ -311,7 +304,7 @@ namespace Content.Server.Lathe if (uid != args.Lathe) return; - if (!args.getUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction)) + if (!args.GetUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction)) return; AddRecipesFromPacks(args.Recipes, component.EmagStaticPacks); diff --git a/Content.Server/Mindshield/MindShieldSystem.cs b/Content.Server/Mindshield/MindShieldSystem.cs index cf0962f2d6..80af9b419c 100644 --- a/Content.Server/Mindshield/MindShieldSystem.cs +++ b/Content.Server/Mindshield/MindShieldSystem.cs @@ -4,46 +4,39 @@ using Content.Server.Popups; using Content.Server.Roles; using Content.Shared.Database; using Content.Shared.Implants; -using Content.Shared.Implants.Components; using Content.Shared.Mindshield.Components; using Content.Shared.Revolutionary.Components; -using Content.Shared.Tag; using Robust.Shared.Containers; namespace Content.Server.Mindshield; /// -/// System used for checking if the implanted is a Rev or Head Rev. +/// System used for adding or removing components with a mindshield implant +/// as well as checking if the implanted is a Rev or Head Rev. /// public sealed class MindShieldSystem : EntitySystem { [Dependency] private readonly IAdminLogManager _adminLogManager = default!; [Dependency] private readonly RoleSystem _roleSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [ValidatePrototypeId] - public const string MindShieldTag = "MindShield"; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(ImplantCheck); + + SubscribeLocalEvent(OnImplantImplanted); SubscribeLocalEvent(OnImplantDraw); SubscribeLocalEvent(ImplantCheck); } - /// - /// Checks if the implant was a mindshield or not - /// - public void ImplantCheck(EntityUid uid, SubdermalImplantComponent comp, ref ImplantImplantedEvent ev) + private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent ev) { - if (_tag.HasTag(ev.Implant, MindShieldTag) && ev.Implanted != null) - { - EnsureComp(ev.Implanted.Value); - MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant); - } + if (ev.Implanted == null) + return; + + EnsureComp(ev.Implanted.Value); + MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant); } // Sunrise-Start @@ -59,7 +52,7 @@ public sealed class MindShieldSystem : EntitySystem /// /// Checks if the implanted person was a Rev or Head Rev and remove role or destroy mindshield respectively. /// - public void MindShieldRemovalCheck(EntityUid implanted, EntityUid implant) + private void MindShieldRemovalCheck(EntityUid implanted, EntityUid implant) { if (HasComp(implanted)) { diff --git a/Content.Server/NPC/HTN/HTNPlanJob.cs b/Content.Server/NPC/HTN/HTNPlanJob.cs index 9c62f5840a..8a57d52a66 100644 --- a/Content.Server/NPC/HTN/HTNPlanJob.cs +++ b/Content.Server/NPC/HTN/HTNPlanJob.cs @@ -56,16 +56,16 @@ public sealed class HTNPlanJob : Job // hence we'll store it here. var appliedStates = new List?>(); - var tasksToProcess = new Queue(); + var tasksToProcess = new Stack(); var finalPlan = new List(); - tasksToProcess.Enqueue(_rootTask); + tasksToProcess.Push(_rootTask); // How many primitive tasks we've added since last record. var primitiveCount = 0; int tasksProcessed = 0; - while (tasksToProcess.TryDequeue(out var currentTask)) + while (tasksToProcess.TryPop(out var currentTask)) { if (tasksProcessed++ > _rootTask.MaximumTasks) throw new Exception("HTN Planner exceeded maximum tasks"); @@ -161,7 +161,7 @@ public sealed class HTNPlanJob : Job /// /// Goes through each compound task branch and tries to find an appropriate one. /// - private bool TryFindSatisfiedMethod(HTNCompoundTask compoundId, Queue tasksToProcess, NPCBlackboard blackboard, ref int mtrIndex) + private bool TryFindSatisfiedMethod(HTNCompoundTask compoundId, Stack tasksToProcess, NPCBlackboard blackboard, ref int mtrIndex) { var compound = _protoManager.Index(compoundId.Task); @@ -182,9 +182,9 @@ public sealed class HTNPlanJob : Job if (!isValid) continue; - foreach (var task in branch.Tasks) + foreach (var task in branch.Tasks.AsEnumerable().Reverse()) { - tasksToProcess.Enqueue(task); + tasksToProcess.Push(task); } return true; @@ -198,7 +198,7 @@ public sealed class HTNPlanJob : Job /// private void RestoreTolastDecomposedTask( Stack decompHistory, - Queue tasksToProcess, + Stack tasksToProcess, List?> appliedStates, List finalPlan, ref int primitiveCount, @@ -223,7 +223,7 @@ public sealed class HTNPlanJob : Job primitiveCount = lastDecomp.PrimitiveCount; blackboard = lastDecomp.Blackboard; - tasksToProcess.Enqueue(lastDecomp.CompoundTask); + tasksToProcess.Push(lastDecomp.CompoundTask); } /// diff --git a/Content.Server/Radiation/Systems/GeigerSystem.cs b/Content.Server/Radiation/Systems/GeigerSystem.cs index 9b6ed31781..6cf17c49c8 100644 --- a/Content.Server/Radiation/Systems/GeigerSystem.cs +++ b/Content.Server/Radiation/Systems/GeigerSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Radiation.Systems; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Server.Player; +using Robust.Shared.Player; namespace Content.Server.Radiation.Systems; @@ -155,16 +156,17 @@ public sealed class GeigerSystem : SharedGeigerSystem if (!component.Sounds.TryGetValue(component.DangerLevel, out var sounds)) return; - if (component.User == null) - return; - - if (!_player.TryGetSessionByEntity(component.User.Value, out var session)) - return; - var sound = _audio.ResolveSound(sounds); - var param = sounds.Params.WithLoop(true).WithVolume(-4f); + var param = sounds.Params.WithLoop(true).WithVolume(component.Volume); - component.Stream = _audio.PlayGlobal(sound, session, param)?.Entity; + if (component.BroadcastAudio) + { + // For some reason PlayPvs sounds quieter even at distance 0, so we need to boost the volume a bit for consistency + param = sounds.Params.WithLoop(true).WithVolume(component.Volume + 1.5f).WithMaxDistance(component.BroadcastRange); + component.Stream = _audio.PlayPvs(sound, uid, param)?.Entity; + } + else if(component.User is not null && _player.TryGetSessionByEntity(component.User.Value, out var session)) + component.Stream = _audio.PlayGlobal(sound, session, param)?.Entity; } public static GeigerDangerLevel RadsToLevel(float rads) diff --git a/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs b/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs deleted file mode 100644 index 04894461ed..0000000000 --- a/Content.Server/Storage/EntitySystems/StorageFillVisualizerSystem.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Shared.Rounding; -using Content.Shared.Storage; -using Content.Shared.Storage.Components; -using Robust.Shared.Containers; - -namespace Content.Server.Storage.EntitySystems; - -public sealed class StorageFillVisualizerSystem : EntitySystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnInserted); - SubscribeLocalEvent(OnRemoved); - } - - private void OnStartup(EntityUid uid, StorageFillVisualizerComponent component, ComponentStartup args) - { - UpdateAppearance(uid, component: component); - } - - private void OnInserted(EntityUid uid, StorageFillVisualizerComponent component, EntInsertedIntoContainerMessage args) - { - UpdateAppearance(uid, component: component); - } - - private void OnRemoved(EntityUid uid, StorageFillVisualizerComponent component, EntRemovedFromContainerMessage args) - { - UpdateAppearance(uid, component: component); - } - - private void UpdateAppearance(EntityUid uid, StorageComponent? storage = null, AppearanceComponent? appearance = null, - StorageFillVisualizerComponent? component = null) - { - if (!Resolve(uid, ref storage, ref appearance, ref component, false)) - return; - - if (component.MaxFillLevels < 1) - return; - - if (!_appearance.TryGetData(uid, StorageVisuals.StorageUsed, out var used, appearance)) - return; - - if (!_appearance.TryGetData(uid, StorageVisuals.Capacity, out var capacity, appearance)) - return; - - var level = ContentHelpers.RoundToLevels(used, capacity, component.MaxFillLevels); - _appearance.SetData(uid, StorageFillVisuals.FillLevel, level, appearance); - } -} diff --git a/Content.Shared/Lathe/LatheComponent.cs b/Content.Shared/Lathe/LatheComponent.cs index aaf273e0fe..80f4f62a31 100644 --- a/Content.Shared/Lathe/LatheComponent.cs +++ b/Content.Shared/Lathe/LatheComponent.cs @@ -21,6 +21,9 @@ namespace Content.Shared.Lathe /// [DataField] public List> DynamicPacks = new(); + // Note that this shouldn't be modified dynamically. + // I.e., this + the static recipies should represent all recipies that the lathe can ever make + // Otherwise the material arbitrage test and/or LatheSystem.GetAllBaseRecipes needs to be updated /// /// The lathe's construction queue @@ -81,15 +84,16 @@ namespace Content.Shared.Lathe public sealed class LatheGetRecipesEvent : EntityEventArgs { public readonly EntityUid Lathe; + public readonly LatheComponent Comp; - public bool getUnavailable; + public bool GetUnavailable; public HashSet> Recipes = new(); - public LatheGetRecipesEvent(EntityUid lathe, bool forced) + public LatheGetRecipesEvent(Entity lathe, bool forced) { - Lathe = lathe; - getUnavailable = forced; + (Lathe, Comp) = lathe; + GetUnavailable = forced; } } diff --git a/Content.Shared/Lathe/SharedLatheSystem.cs b/Content.Shared/Lathe/SharedLatheSystem.cs index ae5519d16c..524d83fd84 100644 --- a/Content.Shared/Lathe/SharedLatheSystem.cs +++ b/Content.Shared/Lathe/SharedLatheSystem.cs @@ -34,6 +34,25 @@ public abstract class SharedLatheSystem : EntitySystem BuildInverseRecipeDictionary(); } + /// + /// Get the set of all recipes that a lathe could possibly ever create (e.g., if all techs were unlocked). + /// + public HashSet> GetAllPossibleRecipes(LatheComponent component) + { + var recipes = new HashSet>(); + foreach (var pack in component.StaticPacks) + { + recipes.UnionWith(_proto.Index(pack).Recipes); + } + + foreach (var pack in component.DynamicPacks) + { + recipes.UnionWith(_proto.Index(pack).Recipes); + } + + return recipes; + } + /// /// Add every recipe in the list of recipe packs to a single hashset. /// diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index aba189cc13..c40ae1df78 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -128,6 +128,7 @@ public sealed class HungerSystem : EntitySystem entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime; entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value); DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerChangeTime)); + DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerValue)); } private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null) @@ -140,6 +141,7 @@ public sealed class HungerSystem : EntitySystem return; component.CurrentThreshold = calculatedHungerThreshold; + DirtyField(uid, component, nameof(HungerComponent.CurrentThreshold)); DoHungerThresholdEffects(uid, component); } @@ -176,10 +178,12 @@ public sealed class HungerSystem : EntitySystem if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier)) { component.ActualDecayRate = component.BaseDecayRate * modifier; + DirtyField(uid, component, nameof(HungerComponent.ActualDecayRate)); SetAuthoritativeHungerValue((uid, component), GetHunger(component)); } component.LastThreshold = component.CurrentThreshold; + DirtyField(uid, component, nameof(HungerComponent.LastThreshold)); } private void DoContinuousHungerEffects(EntityUid uid, HungerComponent? component = null) diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index f2f28cf822..2a906b420a 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -58,6 +58,8 @@ public sealed class ThirstSystem : EntitySystem component.CurrentThirst = _random.Next( (int) component.ThirstThresholds[ThirstThreshold.Thirsty] + 10, (int) component.ThirstThresholds[ThirstThreshold.Okay] - 1); + + DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst)); } component.NextUpdateTime = _timing.CurTime; component.CurrentThirstThreshold = GetThirstThreshold(component, component.CurrentThirst); @@ -65,6 +67,8 @@ public sealed class ThirstSystem : EntitySystem // TODO: Check all thresholds make sense and throw if they don't. UpdateEffects(uid, component); + DirtyFields(uid, component, null, nameof(ThirstComponent.NextUpdateTime), nameof(ThirstComponent.CurrentThirstThreshold), nameof(ThirstComponent.LastThirstThreshold)); + TryComp(uid, out MovementSpeedModifierComponent? moveMod); _movement.RefreshMovementSpeedModifiers(uid, moveMod); } @@ -113,7 +117,7 @@ public sealed class ThirstSystem : EntitySystem component.ThirstThresholds[ThirstThreshold.OverHydrated] ); - EntityManager.DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst)); + DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst)); } private bool IsMovementThreshold(ThirstThreshold threshold) @@ -188,6 +192,9 @@ public sealed class ThirstSystem : EntitySystem _alerts.ClearAlertCategory(uid, component.ThirstyCategory); } + DirtyField(uid, component, nameof(ThirstComponent.LastThirstThreshold)); + DirtyField(uid, component, nameof(ThirstComponent.ActualDecayRate)); + var ev = new MoodEffectEvent("Thirst" + component.CurrentThirstThreshold); RaiseLocalEvent(uid, ev); diff --git a/Content.Shared/Radiation/Components/GeigerComponent.cs b/Content.Shared/Radiation/Components/GeigerComponent.cs index 71edb70b37..34a262e131 100644 --- a/Content.Shared/Radiation/Components/GeigerComponent.cs +++ b/Content.Shared/Radiation/Components/GeigerComponent.cs @@ -83,6 +83,24 @@ public sealed partial class GeigerComponent : Component /// Played only for current user. /// public EntityUid? Stream; + + /// + /// Mark true if the audio should be heard by everyone around the device + /// + [DataField] + public bool BroadcastAudio = false; + + /// + /// The distance within which the broadcast tone can be heard. + /// + [DataField] + public float BroadcastRange = 4f; + + /// + /// The volume of the warning tone. + /// + [DataField] + public float Volume = -4f; } [Serializable, NetSerializable] diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index f2a2031743..3bf51f7957 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -40,6 +40,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Shared.Rounding; namespace Content.Shared.Storage.EntitySystems; @@ -880,6 +881,12 @@ public abstract class SharedStorageSystem : EntitySystem _appearance.SetData(uid, StorageVisuals.Open, isOpen, appearance); _appearance.SetData(uid, SharedBagOpenVisuals.BagState, isOpen ? SharedBagState.Open : SharedBagState.Closed, appearance); + if (TryComp(uid, out var storageFillVisualizerComp)) + { + var level = ContentHelpers.RoundToLevels(used, capacity, storageFillVisualizerComp.MaxFillLevels); + _appearance.SetData(uid, StorageFillVisuals.FillLevel, level, appearance); + } + // HideClosedStackVisuals true sets the StackVisuals.Hide to the open state of the storage. // This is for containers that only show their contents when open. (e.g. donut boxes) if (storage.HideStackVisualsWhenClosed) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7f2298bf34..db2db28a6c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,67 +1,4 @@ Entries: -- author: Beck Thompson - changes: - - message: Added some new scrap that contains uranium and plasma that can be found - when salvaging! - type: Add - id: 7709 - time: '2024-12-16T12:33:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32198 -- author: Sirionaut, SpaceBaa - changes: - - message: Animals no longer consume nutrients when their udder/woolcoat is full. - type: Fix - - message: Entities with udders display a rough hunger level when examined. - type: Add - id: 7710 - time: '2024-12-16T12:53:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32905 -- author: thetolbean - changes: - - message: Teleporting or dashing now causes you to stop pulling objects. - type: Tweak - id: 7711 - time: '2024-12-16T14:09:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33252 -- author: KieueCaprie - changes: - - message: The lizard plushie will now be visible when held in your hands! - type: Add - id: 7712 - time: '2024-12-16T14:14:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32583 -- author: Bhijn and Myr - changes: - - message: Admins no longer count towards the playercount cap, meaning you no longer - need to wait for multiple people to leave in order to join after an admin has - joined. - type: Tweak - id: 7713 - time: '2024-12-16T14:19:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33424 -- author: dragonryan06 - changes: - - message: 'New cocktail: the Zombie.' - type: Add - id: 7714 - time: '2024-12-16T15:25:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32802 -- author: SlamBamActionman - changes: - - message: Added Holy damage to the Chaplain's bible, holy water and the gohei, - which can be exclusively dealt to spirits. - type: Add - id: 7715 - time: '2024-12-16T15:33:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32755 -- author: Vexerot - changes: - - message: Security Belts and Security Carriers now provide 10% reduced explosion - damage to contents. - type: Tweak - id: 7716 - time: '2024-12-16T16:27:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33253 - author: goet changes: - message: Spaceshrooms can now be cooked on the electric grill. @@ -3909,3 +3846,63 @@ id: 8208 time: '2025-04-17T03:14:17.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/36642 +- author: VerinSenpai + changes: + - message: You can now use actions within the action window. + type: Add + id: 8209 + time: '2025-04-17T10:08:09.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35642 +- author: ElectroJr + changes: + - message: Fixed some intense explosion types using incorrect overlay sprites. + type: Fix + id: 8210 + time: '2025-04-17T10:12:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36644 +- author: Beck Thompson, SlamBamActionman + changes: + - message: Fixed trash bag visuals. The appearance will now update correctly depending + on how many items it contains! + type: Fix + id: 8211 + time: '2025-04-17T10:36:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32386 +- author: Callmore + changes: + - message: Cyborgs now require being pryed to open/close the panel instead of screwed. + type: Tweak + id: 8212 + time: '2025-04-17T10:38:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32796 +- author: Errant + changes: + - message: Handheld geiger counters can now by heard by everyone nearby. + type: Tweak + id: 8213 + time: '2025-04-17T11:24:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/30463 +- author: Centronias + changes: + - message: Tank harnesses can be made at lathes, similar to utility belts. + type: Add + - message: Tank harnesses take up a 1x2 space in inventories, half of their previous + 2x2. + type: Tweak + id: 8214 + time: '2025-04-17T12:33:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35590 +- author: Jacktastic09 + changes: + - message: Added the Solid Headband, available via hacked ClothesMate vending machines + type: Add + id: 8215 + time: '2025-04-17T13:43:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36650 +- author: Hoodie42 + changes: + - message: The banjo can now be worn on the back or suit storage slots. + type: Tweak + id: 8216 + time: '2025-04-17T14:05:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34057 diff --git a/Resources/Maps/loop.yml b/Resources/Maps/loop.yml index af441f6106..a1f65f2521 100644 --- a/Resources/Maps/loop.yml +++ b/Resources/Maps/loop.yml @@ -4,8 +4,8 @@ meta: engineVersion: 253.0.0 forkId: "" forkVersion: "" - time: 04/14/2025 22:52:39 - entityCount: 17812 + time: 04/17/2025 05:34:31 + entityCount: 17813 maps: - 1 grids: @@ -79,7 +79,7 @@ entities: version: 6 -1,0: ind: -1,0 - tiles: HQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAAAHgAAAAABHgAAAAAAHgAAAAABHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAACHgAAAAACHgAAAAAAHgAAAAAAHgAAAAACHgAAAAADHgAAAAABHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAACHgAAAAACHgAAAAACHgAAAAABHgAAAAAAHgAAAAACHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAAAHgAAAAACHgAAAAACHgAAAAABHgAAAAADHgAAAAAAHgAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAABHgAAAAABHgAAAAACHgAAAAABHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAABHgAAAAADHgAAAAADHgAAAAAAHgAAAAAAHgAAAAACHgAAAAAAHgAAAAADHgAAAAABHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAACHgAAAAACHgAAAAABHgAAAAABHgAAAAAAHgAAAAABHgAAAAACHgAAAAACHgAAAAAAHQAAAAAAHgAAAAADHgAAAAABHgAAAAADHgAAAAADHgAAAAABHgAAAAADHgAAAAABHgAAAAACHgAAAAADHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAAAHQAAAAAAHgAAAAACHgAAAAABHgAAAAADHgAAAAABHgAAAAAAHgAAAAADHgAAAAAAHgAAAAACHgAAAAACHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAADHQAAAAAAHgAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAADHgAAAAABHgAAAAACHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAABHgAAAAAAHQAAAAAAHgAAAAADHgAAAAAAHgAAAAABHgAAAAACHgAAAAAAHgAAAAADHgAAAAABHgAAAAAAHgAAAAACHgAAAAADHgAAAAACHgAAAAADHgAAAAAAHgAAAAACHgAAAAACHQAAAAAAHgAAAAADHgAAAAACHgAAAAADHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAACHgAAAAADHgAAAAADHgAAAAACHgAAAAABHgAAAAADHgAAAAADHgAAAAACHQAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAACHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAACHgAAAAABHQAAAAAAHgAAAAADHgAAAAABHgAAAAAAHQAAAAAAHgAAAAADHgAAAAABHgAAAAADHgAAAAABHgAAAAAB + tiles: HQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAIQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAAAHgAAAAABHgAAAAAAHgAAAAABHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAACHgAAAAACHgAAAAAAHgAAAAAAHgAAAAACHgAAAAADHgAAAAABHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAACHgAAAAACHgAAAAACHgAAAAABHgAAAAAAHgAAAAACHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAHQAAAAAAHgAAAAAAHgAAAAACHgAAAAACHgAAAAABHgAAAAADHgAAAAAAHgAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAABHgAAAAABHgAAAAACHgAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAABHgAAAAADHgAAAAADHgAAAAAAHgAAAAAAHgAAAAACHgAAAAAAHgAAAAADHgAAAAABHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAACHgAAAAACHgAAAAABHgAAAAABHgAAAAAAHgAAAAABHgAAAAACHgAAAAACHgAAAAAAHQAAAAAAHgAAAAADHgAAAAABHgAAAAADHgAAAAADHgAAAAABHgAAAAADHgAAAAABHgAAAAACHgAAAAADHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAAAHQAAAAAAHgAAAAACHgAAAAABHgAAAAADHgAAAAABHgAAAAAAHgAAAAADHgAAAAAAHgAAAAACHgAAAAACHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAADHQAAAAAAHgAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAADHgAAAAABHgAAAAACHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAABHgAAAAABHgAAAAAAHQAAAAAAHgAAAAADHgAAAAAAHgAAAAABHgAAAAACHgAAAAAAHgAAAAADHgAAAAABHgAAAAAAHgAAAAACHgAAAAADHgAAAAACHgAAAAADHgAAAAAAHgAAAAACHgAAAAACHQAAAAAAHgAAAAADHgAAAAACHgAAAAADHgAAAAABHgAAAAAAHgAAAAAAHgAAAAADHgAAAAACHgAAAAADHgAAAAADHgAAAAACHgAAAAABHgAAAAADHgAAAAADHgAAAAACHQAAAAAAHgAAAAABHgAAAAABHgAAAAADHgAAAAACHgAAAAADHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHQAAAAAAHgAAAAAAHgAAAAABHgAAAAAAHgAAAAAAHgAAAAAAHgAAAAACHgAAAAABHQAAAAAAHgAAAAADHgAAAAABHgAAAAAAHQAAAAAAHgAAAAADHgAAAAABHgAAAAADHgAAAAABHgAAAAAB version: 6 0,-1: ind: 0,-1 @@ -7277,7 +7277,7 @@ entities: pos: -30.5,13.5 parent: 2 - type: Door - secondsUntilStateChange: -52499.977 + secondsUntilStateChange: -52533.863 state: Opening - type: DeviceLinkSource lastSignals: @@ -43892,7 +43892,7 @@ entities: pos: -9.5,51.5 parent: 2 - type: Door - secondsUntilStateChange: -300255.53 + secondsUntilStateChange: -300289.4 state: Opening - uid: 6747 components: @@ -43900,7 +43900,7 @@ entities: pos: -8.5,51.5 parent: 2 - type: Door - secondsUntilStateChange: -300256.25 + secondsUntilStateChange: -300290.12 state: Opening - uid: 6749 components: @@ -43908,7 +43908,7 @@ entities: pos: -6.5,51.5 parent: 2 - type: Door - secondsUntilStateChange: -300254.8 + secondsUntilStateChange: -300288.7 state: Opening - uid: 6750 components: @@ -43916,7 +43916,7 @@ entities: pos: -5.5,51.5 parent: 2 - type: Door - secondsUntilStateChange: -300254.2 + secondsUntilStateChange: -300288.06 state: Opening - uid: 9721 components: @@ -54147,14 +54147,6 @@ entities: parent: 2 - type: AtmosPipeColor color: '#0055CCFF' - - uid: 11648 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -4.5,6.5 - parent: 2 - - type: AtmosPipeColor - color: '#990000FF' - uid: 11654 components: - type: Transform @@ -54513,6 +54505,11 @@ entities: parent: 2 - type: AtmosPipeColor color: '#990000FF' + - uid: 14534 + components: + - type: Transform + pos: -3.5,6.5 + parent: 2 - uid: 15192 components: - type: Transform @@ -66771,6 +66768,12 @@ entities: parent: 2 - type: AtmosPipeColor color: '#0055CCFF' + - uid: 11648 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,6.5 + parent: 2 - uid: 11655 components: - type: Transform @@ -104939,12 +104942,6 @@ entities: - type: Transform pos: 3.5,-6.5 parent: 2 - - uid: 14534 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -3.5,6.5 - parent: 2 - uid: 14555 components: - type: Transform @@ -105657,6 +105654,11 @@ entities: rot: 3.141592653589793 rad pos: 12.5,-7.5 parent: 2 + - uid: 17813 + components: + - type: Transform + pos: -3.5,6.5 + parent: 2 - proto: WallSolid entities: - uid: 19 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml index 14c241ecd5..878f20c670 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml @@ -149,4 +149,5 @@ ToyFigurinePassenger: 1 ToyFigurineGreytider: 1 ClothingBackpackSatchelSmugglerUnanchored: 1 + ClothingHeadHatSolidHeadband: 2 # DO NOT ADD MORE, USE UNIFORM DYING diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 6264748293..64d55a2dd9 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -1148,3 +1148,14 @@ sprite: Clothing/Head/Hats/beret_medic.rsi - type: Clothing sprite: Clothing/Head/Hats/beret_medic.rsi + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatSolidHeadband + name: solid headband + description: "You'll feel like you're Invisible while wearing this! (DISCLAIMER: DOES NOT ACTUALLY MAKE THE WEARER INVISIBLE)" + components: + - type: Sprite + sprite: Clothing/Head/Hats/solidheadband.rsi + - type: Clothing + sprite: Clothing/Head/Hats/solidheadband.rsi diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 3807264841..fb779fac23 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -129,3 +129,7 @@ sprite: Clothing/OuterClothing/Vests/tankharness.rsi - type: Clothing sprite: Clothing/OuterClothing/Vests/tankharness.rsi + - type: Item + size: Normal # Make smaller than typical outer clothing + shape: + - 0, 0, 0, 1 diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 67df529dea..960d248095 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -16,6 +16,7 @@ soundHit: collection: MetalThud - type: CombatMode + - type: NoSlip - type: StaticPrice price: 1250 - type: Fixtures @@ -35,7 +36,7 @@ - type: Sprite sprite: Mobs/Silicon/chassis.rsi - type: RotationVisuals - defaultRotation: 90 + defaultRotation: 90 # Sunrise-Edit horizontalRotation: 90 - type: MobState allowedStates: @@ -122,6 +123,7 @@ - BorgChassis - RoboticsConsole - type: WiresPanel + openingTool: Prying openDelay: 3 - type: ActivatableUIRequiresPanel - type: NameIdentifier diff --git a/Resources/Prototypes/Entities/Objects/Devices/geiger.yml b/Resources/Prototypes/Entities/Objects/Devices/geiger.yml index 015ec14ba7..ccbcc4dcd9 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/geiger.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/geiger.yml @@ -4,34 +4,34 @@ name: Geiger counter description: A handheld device used for detecting and measuring radiation pulses. components: - - type: Sprite - sprite: Objects/Tools/geiger.rsi - layers: - - state: geiger_base - - state: geiger_on_idle - map: ["enum.GeigerLayers.Screen"] - shader: unshaded - visible: false - - type: Item - sprite: Objects/Tools/geiger.rsi - - type: Geiger - showControl: true - showExamine: true - - type: Appearance - - type: GenericVisualizer - visuals: - enum.GeigerVisuals.IsEnabled: - GeigerLayers.Screen: - True: { visible: True } - False: { visible: False } - enum.GeigerVisuals.DangerLevel: - GeigerLayers.Screen: - None: {state: geiger_on_idle} - Low: {state: geiger_on_low} - Med: {state: geiger_on_med} - High: {state: geiger_on_high} - Extreme: {state: geiger_on_ext} - - type: PhysicalComposition - materialComposition: - Plastic: 100 - + - type: Sprite + sprite: Objects/Tools/geiger.rsi + layers: + - state: geiger_base + - state: geiger_on_idle + map: ["enum.GeigerLayers.Screen"] + shader: unshaded + visible: false + - type: Item + sprite: Objects/Tools/geiger.rsi + - type: Geiger + showControl: true + showExamine: true + broadcastAudio: true + - type: Appearance + - type: GenericVisualizer + visuals: + enum.GeigerVisuals.IsEnabled: + GeigerLayers.Screen: + True: { visible: True } + False: { visible: False } + enum.GeigerVisuals.DangerLevel: + GeigerLayers.Screen: + None: {state: geiger_on_idle} + Low: {state: geiger_on_low} + Med: {state: geiger_on_med} + High: {state: geiger_on_high} + Extreme: {state: geiger_on_ext} + - type: PhysicalComposition + materialComposition: + Plastic: 100 diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml index ed1520ff99..f1ba8faf96 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml @@ -184,6 +184,12 @@ - type: Item size: Normal sprite: Objects/Fun/Instruments/banjo.rsi + - type: Clothing + quickEquip: false + slots: + - back + - suitStorage + sprite: Objects/Fun/Instruments/banjo.rsi - type: Tag tags: - StringInstrument diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 6556b46d5c..4d8c7916f2 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -420,7 +420,6 @@ - type: MindShieldImplant - type: Tag tags: - - MindShield - SubdermalImplant # Centcomm implants diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml index f64f6612b0..f4bbf9e686 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml @@ -58,7 +58,7 @@ id: SolarPanelPlasma parent: SolarPanelBasePhysSprite name: solar panel plasma - description: A plasma solar panel that generates power. + description: A few sheets of plasma glass that generate electricity when hit by photons. components: - type: PowerSupplier supplyRampTolerance: 500 @@ -92,7 +92,7 @@ id: SolarPanelUranium parent: SolarPanelBasePhysSprite name: solar panel uranium - description: A uranium solar panel that generates power. + description: A few sheets of uranium glass that generate electricity when hit by photons. components: - type: PowerSupplier supplyRampTolerance: 500 @@ -126,7 +126,7 @@ id: SolarPanel parent: SolarPanelBasePhysSprite name: solar panel - description: A solar panel that generates power. + description: A few sheets of glass that generate electricity when hit by photons. components: - type: PowerSupplier supplyRampTolerance: 500 diff --git a/Resources/Prototypes/Recipes/Lathes/Packs/engineering.yml b/Resources/Prototypes/Recipes/Lathes/Packs/engineering.yml index cb868aa9c2..3ecd39cc98 100644 --- a/Resources/Prototypes/Recipes/Lathes/Packs/engineering.yml +++ b/Resources/Prototypes/Recipes/Lathes/Packs/engineering.yml @@ -16,6 +16,7 @@ - HandheldGPSBasic - TRayScanner - UtilityBelt + - ClothingOuterVestTank - HandheldStationMap - ClothingHeadHatWelding - ClothingHeadHatCone diff --git a/Resources/Prototypes/Recipes/Lathes/misc.yml b/Resources/Prototypes/Recipes/Lathes/misc.yml index d0a9b586c0..27316ed08a 100644 --- a/Resources/Prototypes/Recipes/Lathes/misc.yml +++ b/Resources/Prototypes/Recipes/Lathes/misc.yml @@ -237,3 +237,11 @@ completetime: 3 materials: Plastic: 200 + +- type: latheRecipe + id: ClothingOuterVestTank + result: ClothingOuterVestTank + completetime: 2 + materials: + Cloth: 100 + Steel: 50 diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index 556b740b95..042a40b893 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -48,6 +48,8 @@ - PetsNT - Zombie - Revolutionary + - Dragon + - Xeno - AllHostile - Wizard # Sunrise Edit diff --git a/Resources/Prototypes/explosion.yml b/Resources/Prototypes/explosion.yml index 6274ef5f46..4f8360a686 100644 --- a/Resources/Prototypes/explosion.yml +++ b/Resources/Prototypes/explosion.yml @@ -1,5 +1,11 @@ # Does not currently support prototype hot-reloading. See comments in c# file. +# Note that for every explosion type you define, explosions & nukes will start performing worse +# You should only define a new explopsion type if you really need to +# +# If you just want to modify properties other than `damagePerIntensity`, it'd be better to +# split off explosion damage & explosion visuals/effects into their own separate prototypes. + - type: explosion id: Default damagePerIntensity: @@ -43,7 +49,7 @@ intensityPerState: 20 lightColor: Orange texturePath: /Textures/Effects/fire.rsi - fireStates: 6 + fireStates: 3 - type: explosion id: Radioactive @@ -100,7 +106,7 @@ intensityPerState: 20 lightColor: Orange texturePath: /Textures/Effects/fire.rsi - fireStates: 6 + fireStates: 3 - type: explosion id: HardBomb @@ -116,7 +122,7 @@ intensityPerState: 20 lightColor: Orange texturePath: /Textures/Effects/fire.rsi - fireStates: 6 + fireStates: 3 - type: explosion id: FireBomb @@ -129,7 +135,7 @@ #Sunrise-end lightColor: Orange texturePath: /Textures/Effects/fire.rsi - fireStates: 6 + fireStates: 3 fireStacks: 2 # STOP diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 7546f793ec..475f505699 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -870,9 +870,6 @@ - type: Tag id: MimeHappyHonk -- type: Tag - id: MindShield - - type: Tag id: MindTransferTarget diff --git a/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..8d0b70fb48 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/icon.png new file mode 100644 index 0000000000..439e6ce1a0 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-left.png new file mode 100644 index 0000000000..a667e3c1f4 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-right.png new file mode 100644 index 0000000000..5060aea6da Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/meta.json new file mode 100644 index 0000000000..36f70f342b --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/solidheadband.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprited by Jacktastic09 (Discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-BACKPACK.png b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..204f53416a Binary files /dev/null and b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..204f53416a Binary files /dev/null and b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/meta.json b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/meta.json index 6c2e95c7e5..cfe2d45291 100644 --- a/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Instruments/banjo.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/vgstation-coders/vgstation13 at 8d9c91e19cb52713c7f7f1804c2b6f7203f8d331", + "copyright": "https://github.com/vgstation-coders/vgstation13 at 8d9c91e19cb52713c7f7f1804c2b6f7203f8d331, equipped sprites made by Hoodie42 for Space Station 14", "states": [ { "name": "icon" @@ -17,6 +17,14 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] }