Merge branch 'master' into expl_int_analyzer

This commit is contained in:
Pieter-Jan Briers
2021-02-08 22:46:28 +01:00
140 changed files with 3622 additions and 1208 deletions

1
.gitattributes vendored
View File

@@ -34,6 +34,7 @@
#*.modelproj merge=binary #*.modelproj merge=binary
#*.sqlproj merge=binary #*.sqlproj merge=binary
#*.wwaproj merge=binary #*.wwaproj merge=binary
Resources/Maps/**.yml merge=mapping-merge-driver
############################################################################### ###############################################################################
# behavior for image files # behavior for image files

4
.gitignore vendored
View File

@@ -284,3 +284,7 @@ BuildFiles/Windows/Godot/*
# Windows image file caches # Windows image file caches
Thumbs.db Thumbs.db
ehthumbs.db ehthumbs.db
# Merge driver stuff
Content.Tools/test/out.yml

View File

@@ -1,3 +1,5 @@
// ReSharper disable once RedundantUsingDirective
// Used to warn the player in big red letters in debug mode
using System; using System;
using Content.Client.GameObjects.Components; using Content.Client.GameObjects.Components;
using Content.Client.GameObjects.EntitySystems; using Content.Client.GameObjects.EntitySystems;
@@ -8,6 +10,7 @@ using Robust.Shared.Console;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Commands namespace Content.Client.Commands
{ {
@@ -93,6 +96,10 @@ namespace Content.Client.Commands
return; return;
} }
#if DEBUG
shell.WriteError("WARNING: The client is using a debug build. You are risking losing your changes.");
#endif
shell.ConsoleHost.RegisteredCommands["togglelight"].Execute(shell, string.Empty, Array.Empty<string>()); shell.ConsoleHost.RegisteredCommands["togglelight"].Execute(shell, string.Empty, Array.Empty<string>());
shell.ConsoleHost.RegisteredCommands["showsubfloorforever"].Execute(shell, string.Empty, Array.Empty<string>()); shell.ConsoleHost.RegisteredCommands["showsubfloorforever"].Execute(shell, string.Empty, Array.Empty<string>());

View File

@@ -1,4 +1,5 @@
using Content.Shared.GameObjects.Components.ActionBlocking; #nullable enable
using Content.Shared.GameObjects.Components.ActionBlocking;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -6,9 +7,10 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.ActionBlocking namespace Content.Client.GameObjects.Components.ActionBlocking
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedHandcuffComponent))]
public class HandcuffComponent : SharedHandcuffComponent public class HandcuffComponent : SharedHandcuffComponent
{ {
public override void HandleComponentState(ComponentState curState, ComponentState nextState) public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{ {
if (curState is not HandcuffedComponentState state) if (curState is not HandcuffedComponentState state)
{ {

View File

@@ -1,3 +1,4 @@
// ReSharper disable ArrangeTrailingCommaInMultilineLists
namespace Content.Client namespace Content.Client
{ {
public static class IgnoredComponents public static class IgnoredComponents
@@ -242,7 +243,10 @@ namespace Content.Client
"SecretStash", "SecretStash",
"Toilet", "Toilet",
"ClusterFlash", "ClusterFlash",
"GasGenerator" "GasGenerator",
"SolutionTransfer",
"Shovel",
"ReagentTank",
}; };
} }
} }

View File

@@ -0,0 +1,68 @@
#nullable enable
using Robust.Client.Placement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Content.Client.Placement.Modes
{
public class WallmountLight : PlacementMode
{
public WallmountLight(PlacementManager pMan) : base(pMan)
{
}
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
CurrentTile = GetTileRef(MouseCoords);
if (pManager.CurrentPermission!.IsTile)
{
return;
}
var tileCoordinates = new EntityCoordinates(MouseCoords.EntityId, CurrentTile.GridIndices);
Vector2 offset;
switch (pManager.Direction)
{
case Direction.North:
offset = new Vector2(0.5f, 1f);
break;
case Direction.South:
offset = new Vector2(0.5f, 0f);
break;
case Direction.East:
offset = new Vector2(1f, 0.5f);
break;
case Direction.West:
offset = new Vector2(0f, 0.5f);
break;
default:
return;
}
tileCoordinates = tileCoordinates.Offset(offset);
MouseCoords = tileCoordinates;
}
public override bool IsValidPosition(EntityCoordinates position)
{
if (pManager.CurrentPermission!.IsTile)
{
return false;
}
else if (!RangeCheck(position))
{
return false;
}
return true;
}
}
}

View File

@@ -188,11 +188,11 @@ namespace Content.Client.ParticleAccelerator
new MarginContainer new MarginContainer
{ {
MarginLeftOverride = 4, MarginLeftOverride = 4,
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children = Children =
{ {
new VBoxContainer new VBoxContainer
{ {
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children = Children =
{ {
new HBoxContainer new HBoxContainer
@@ -246,55 +246,61 @@ namespace Content.Client.ParticleAccelerator
} }
} }
}, },
new VBoxContainer new MarginContainer
{ {
SizeFlagsHorizontal = SizeFlags.FillExpand, CustomMinimumSize = (186, 0),
Children = Children =
{ {
(_statusLabel = new Label new VBoxContainer
{ {
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
}),
new Control
{
CustomMinimumSize = (0, 20)
},
new PanelContainer
{
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
PanelOverride = back2,
Children = Children =
{ {
new GridContainer (_statusLabel = new Label
{ {
Columns = 3, SizeFlagsHorizontal = SizeFlags.ShrinkCenter
VSeparationOverride = 0, }),
HSeparationOverride = 0, new Control
{
CustomMinimumSize = (0, 20)
},
new PanelContainer
{
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
PanelOverride = back2,
Children = Children =
{ {
new Control {CustomMinimumSize = imgSize}, new GridContainer
(_endCapTexture = Segment("end_cap")), {
new Control {CustomMinimumSize = imgSize}, Columns = 3,
(_controlBoxTexture = Segment("control_box")), VSeparationOverride = 0,
(_fuelChamberTexture = Segment("fuel_chamber")), HSeparationOverride = 0,
new Control {CustomMinimumSize = imgSize}, Children =
new Control {CustomMinimumSize = imgSize}, {
(_powerBoxTexture = Segment("power_box")), new Control {CustomMinimumSize = imgSize},
new Control {CustomMinimumSize = imgSize}, (_endCapTexture = Segment("end_cap")),
(_emitterLeftTexture = Segment("emitter_left")), new Control {CustomMinimumSize = imgSize},
(_emitterCenterTexture = Segment("emitter_center")), (_controlBoxTexture = Segment("control_box")),
(_emitterRightTexture = Segment("emitter_right")), (_fuelChamberTexture = Segment("fuel_chamber")),
new Control {CustomMinimumSize = imgSize},
new Control {CustomMinimumSize = imgSize},
(_powerBoxTexture = Segment("power_box")),
new Control {CustomMinimumSize = imgSize},
(_emitterLeftTexture = Segment("emitter_left")),
(_emitterCenterTexture = Segment("emitter_center")),
(_emitterRightTexture = Segment("emitter_right")),
}
}
} }
} },
(_scanButton = new Button
{
Text = Loc.GetString("Scan Parts"),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
})
} }
}, }
(_scanButton = new Button
{
Text = Loc.GetString("Scan Parts"),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
})
} }
} },
} }
}, },
new StripeBack new StripeBack
@@ -507,7 +513,7 @@ namespace Content.Client.ParticleAccelerator
public void SetPowerState(ParticleAcceleratorUIState state, bool exists) public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
{ {
_base.ShaderOverride = exists ? null : _menu._greyScaleShader; _base.ShaderOverride = exists ? null : _menu._greyScaleShader;
_base.ModulateSelfOverride = exists ? (Color?)null : new Color(127, 127, 127); _base.ModulateSelfOverride = exists ? (Color?) null : new Color(127, 127, 127);
if (!state.Enabled || !exists) if (!state.Enabled || !exists)
{ {

View File

@@ -80,6 +80,9 @@ namespace Content.IntegrationTests
// Avoid funny race conditions with the database. // Avoid funny race conditions with the database.
options.CVarOverrides[CCVars.DatabaseSynchronous.Name] = "true"; options.CVarOverrides[CCVars.DatabaseSynchronous.Name] = "true";
// Avoid loading a large map by default for integration tests.
options.CVarOverrides[CCVars.GameMap.Name] = "Maps/Test/empty.yml";
return base.StartServer(options); return base.StartServer(options);
} }

View File

@@ -6,7 +6,6 @@ using Content.Server.GameObjects.Components.Strap;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.GameObjects.Components.Buckle; using Content.Shared.GameObjects.Components.Buckle;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.GameObjects.EntitySystems.EffectBlocker; using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
using Content.Shared.Utility; using Content.Shared.Utility;
@@ -45,7 +44,7 @@ namespace Content.IntegrationTests.Tests.Buckle
public async Task BuckleUnbuckleCooldownRangeTest() public async Task BuckleUnbuckleCooldownRangeTest()
{ {
var options = new ServerIntegrationOptions {ExtraPrototypes = PROTOTYPES}; var options = new ServerIntegrationOptions {ExtraPrototypes = PROTOTYPES};
var server = StartServerDummyTicker(options); var server = StartServer(options);
IEntity human = null; IEntity human = null;
IEntity chair = null; IEntity chair = null;
@@ -55,13 +54,14 @@ namespace Content.IntegrationTests.Tests.Buckle
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var mapManager = IoCManager.Resolve<IMapManager>(); var mapManager = IoCManager.Resolve<IMapManager>();
mapManager.CreateNewMapEntity(MapId.Nullspace);
var entityManager = IoCManager.Resolve<IEntityManager>(); var entityManager = IoCManager.Resolve<IEntityManager>();
human = entityManager.SpawnEntity("BuckleDummy", MapCoordinates.Nullspace); var gridId = new GridId(1);
chair = entityManager.SpawnEntity("StrapDummy", MapCoordinates.Nullspace); var grid = mapManager.GetGrid(gridId);
var coordinates = grid.GridEntityId.ToCoordinates();
human = entityManager.SpawnEntity("BuckleDummy", coordinates);
chair = entityManager.SpawnEntity("StrapDummy", coordinates);
// Default state, unbuckled // Default state, unbuckled
Assert.True(human.TryGetComponent(out buckle)); Assert.True(human.TryGetComponent(out buckle));
@@ -197,31 +197,24 @@ namespace Content.IntegrationTests.Tests.Buckle
var options = new ServerIntegrationOptions {ExtraPrototypes = PROTOTYPES}; var options = new ServerIntegrationOptions {ExtraPrototypes = PROTOTYPES};
var server = StartServer(options); var server = StartServer(options);
IEntity human; IEntity human = null;
IEntity chair;
BuckleComponent buckle = null; BuckleComponent buckle = null;
HandsComponent hands = null; HandsComponent hands = null;
IBody body = null; IBody body = null;
await server.WaitIdleAsync();
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var mapManager = IoCManager.Resolve<IMapManager>(); var mapManager = IoCManager.Resolve<IMapManager>();
var mapId = new MapId(1);
mapManager.CreateNewMapEntity(mapId);
var entityManager = IoCManager.Resolve<IEntityManager>(); var entityManager = IoCManager.Resolve<IEntityManager>();
var gridId = new GridId(1);
var grid = mapManager.CreateGrid(mapId, gridId);
var coordinates = grid.GridEntityId.ToCoordinates();
var tileManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileId = tileManager["underplating"].TileId;
var tile = new Tile(tileId);
grid.SetTile(coordinates, tile); var gridId = new GridId(1);
var grid = mapManager.GetGrid(gridId);
var coordinates = grid.GridEntityId.ToCoordinates();
human = entityManager.SpawnEntity("BuckleDummy", coordinates); human = entityManager.SpawnEntity("BuckleDummy", coordinates);
chair = entityManager.SpawnEntity("StrapDummy", coordinates); IEntity chair = entityManager.SpawnEntity("StrapDummy", coordinates);
// Component sanity check // Component sanity check
Assert.True(human.TryGetComponent(out buckle)); Assert.True(human.TryGetComponent(out buckle));
@@ -279,6 +272,8 @@ namespace Content.IntegrationTests.Tests.Buckle
{ {
Assert.Null(hands.GetItem(slot)); Assert.Null(hands.GetItem(slot));
} }
buckle.TryUnbuckle(human, true);
}); });
} }
@@ -295,19 +290,11 @@ namespace Content.IntegrationTests.Tests.Buckle
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var mapManager = IoCManager.Resolve<IMapManager>(); var mapManager = IoCManager.Resolve<IMapManager>();
var mapId = new MapId(1);
mapManager.CreateNewMapEntity(mapId);
var entityManager = IoCManager.Resolve<IEntityManager>(); var entityManager = IoCManager.Resolve<IEntityManager>();
var gridId = new GridId(1);
var grid = mapManager.CreateGrid(mapId, gridId);
var coordinates = grid.GridEntityId.ToCoordinates();
var tileManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileId = tileManager["underplating"].TileId;
var tile = new Tile(tileId);
grid.SetTile(coordinates, tile); var gridId = new GridId(1);
var grid = mapManager.GetGrid(gridId);
var coordinates = grid.GridEntityId.ToCoordinates();
human = entityManager.SpawnEntity("BuckleDummy", coordinates); human = entityManager.SpawnEntity("BuckleDummy", coordinates);
chair = entityManager.SpawnEntity("StrapDummy", coordinates); chair = entityManager.SpawnEntity("StrapDummy", coordinates);

View File

@@ -0,0 +1,170 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using NUnit.Framework;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
{
[TestFixture]
[TestOf(typeof(TotalDamageClassesTrigger))]
public class DestructibleDamageClassTest : ContentIntegrationTest
{
[Test]
public async Task Test()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
}
});
await server.WaitIdleAsync();
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDestructibleEntity;
IDamageableComponent sDamageableComponent = null;
TestThresholdListenerComponent sThresholdListenerComponent = null;
await server.WaitPost(() =>
{
var mapId = new MapId(1);
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageClassEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
await server.WaitRunTicks(5);
await server.WaitAssertion(() =>
{
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
});
await server.WaitAssertion(() =>
{
// Raise brute damage to 5
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
// No thresholds reached yet, the earliest one is at 10 damage
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
// No threshold reached, burn needs to be 10 as well
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
// One threshold reached, brute 10 + burn 10
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold brute 10 + burn 10
var msg = sThresholdListenerComponent.ThresholdsReached[0];
var threshold = msg.Threshold;
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Is.Empty);
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True);
Assert.IsInstanceOf<TotalDamageClassesTrigger>(threshold.Trigger);
sThresholdListenerComponent.ThresholdsReached.Clear();
// Raise brute damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Lower brute damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage back up to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
// 10 brute + 10 burn threshold reached, brute was healed and brought back to its threshold amount and slash stayed the same
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal both classes of damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
// Both classes of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold brute 10 + burn 10
msg = sThresholdListenerComponent.ThresholdsReached[0];
threshold = msg.Threshold;
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Is.Empty);
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True);
Assert.IsInstanceOf<TotalDamageClassesTrigger>(threshold.Trigger);
sThresholdListenerComponent.ThresholdsReached.Clear();
// Change triggers once to true
threshold.TriggersOnce = true;
// Heal brute and burn back to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -10, true));
// No new thresholds reached from healing
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
// No new thresholds reached as triggers once is set to true and it already triggered before
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
});
}
}
}

View File

@@ -0,0 +1,170 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using NUnit.Framework;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
{
[TestFixture]
[TestOf(typeof(TotalDamageTypesTrigger))]
public class DestructibleDamageTypeTest : ContentIntegrationTest
{
[Test]
public async Task Test()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
}
});
await server.WaitIdleAsync();
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDestructibleEntity;
IDamageableComponent sDamageableComponent = null;
TestThresholdListenerComponent sThresholdListenerComponent = null;
await server.WaitPost(() =>
{
var mapId = new MapId(1);
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageTypeEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
await server.WaitRunTicks(5);
await server.WaitAssertion(() =>
{
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
});
await server.WaitAssertion(() =>
{
// Raise blunt damage to 5
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
// No thresholds reached yet, the earliest one is at 10 damage
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
// No threshold reached, slash needs to be 10 as well
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
// One threshold reached, blunt 10 + slash 10
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold blunt 10 + slash 10
var msg = sThresholdListenerComponent.ThresholdsReached[0];
var threshold = msg.Threshold;
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Is.Empty);
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True);
Assert.IsInstanceOf<TotalDamageTypesTrigger>(threshold.Trigger);
sThresholdListenerComponent.ThresholdsReached.Clear();
// Raise blunt damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Lower blunt damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage back up to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// 10 blunt + 10 slash threshold reached, blunt was healed and brought back to its threshold amount and slash stayed the same
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal both types of damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
// Both types of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold blunt 10 + slash 10
msg = sThresholdListenerComponent.ThresholdsReached[0];
threshold = msg.Threshold;
// Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Is.Empty);
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True);
Assert.IsInstanceOf<TotalDamageTypesTrigger>(threshold.Trigger);
sThresholdListenerComponent.ThresholdsReached.Clear();
// Change triggers once to true
threshold.TriggersOnce = true;
// Heal blunt and slash back to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -10, true));
// No new thresholds reached from healing
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
// No new thresholds reached as triggers once is set to true and it already triggered before
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
});
}
}
}

View File

@@ -0,0 +1,95 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Destructible.Thresholds;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using NUnit.Framework;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
{
public class DestructibleDestructionTest : ContentIntegrationTest
{
[Test]
public async Task Test()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
}
});
await server.WaitIdleAsync();
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDestructibleEntity = null;
IDamageableComponent sDamageableComponent = null;
TestThresholdListenerComponent sThresholdListenerComponent = null;
await server.WaitPost(() =>
{
var mapId = new MapId(1);
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDestructionEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
await server.WaitAssertion(() =>
{
var coordinates = sDestructibleEntity.Transform.Coordinates;
Assert.DoesNotThrow(() =>
{
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
});
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
Assert.That(threshold.Triggered, Is.True);
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior);
Assert.That(spawnEntitiesBehavior.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnEntitiesBehavior.Spawn.Keys.Single(), Is.EqualTo(SpawnedEntityId));
Assert.That(spawnEntitiesBehavior.Spawn.Values.Single(), Is.EqualTo(new MinMax {Min = 1, Max = 1}));
var entitiesInRange = sEntityManager.GetEntitiesInRange(coordinates, 2);
var found = false;
foreach (var entity in entitiesInRange)
{
if (entity.Prototype == null)
{
continue;
}
if (entity.Prototype.Name != SpawnedEntityId)
{
continue;
}
found = true;
break;
}
Assert.That(found, Is.True);
});
}
}
}

View File

@@ -0,0 +1,94 @@
namespace Content.IntegrationTests.Tests.Destructible
{
public static class DestructibleTestPrototypes
{
public const string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
public const string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
public const string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
public const string DestructibleDamageTypeEntityId = "DestructibleTestsDestructibleDamageTypeEntity";
public const string DestructibleDamageClassEntityId = "DestructibleTestsDestructibleDamageClassEntity";
public static readonly string Prototypes = $@"
- type: entity
id: {SpawnedEntityId}
name: {SpawnedEntityId}
- type: entity
id: {DestructibleEntityId}
name: {DestructibleEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:TotalDamageTrigger
damage: 20
triggersOnce: false
- trigger:
!type:TotalDamageTrigger
damage: 50
triggersOnce: false
behaviors:
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
{SpawnedEntityId}:
min: 1
max: 1
- !type:DoActsBehavior
acts: [""Breakage""]
- type: TestThresholdListener
- type: entity
id: {DestructibleDestructionEntityId}
name: {DestructibleDestructionEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:TotalDamageTrigger
damage: 50
behaviors:
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
{SpawnedEntityId}:
min: 1
max: 1
- !type:DoActsBehavior # This must come last as it destroys the entity.
acts: [""Destruction""]
- type: TestThresholdListener
- type: entity
id: {DestructibleDamageTypeEntityId}
name: {DestructibleDamageTypeEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:TotalDamageTypesTrigger
damage:
Blunt: 10
Slash: 10
- type: TestThresholdListener
- type: entity
id: {DestructibleDamageClassEntityId}
name: {DestructibleDamageClassEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
- trigger:
!type:TotalDamageClassesTrigger
damage:
Brute: 10
Burn: 10
- type: TestThresholdListener";
}
}

View File

@@ -1,99 +1,27 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Destructible; using Content.Server.GameObjects.Components.Destructible;
using Content.Server.GameObjects.Components.Destructible.Thresholds; using Content.Server.GameObjects.Components.Destructible.Thresholds;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior; using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Damage;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible namespace Content.IntegrationTests.Tests.Destructible
{ {
[TestFixture] [TestFixture]
[TestOf(typeof(DestructibleComponent))] [TestOf(typeof(DestructibleComponent))]
[TestOf(typeof(Threshold))] [TestOf(typeof(Threshold))]
public class DestructibleTests : ContentIntegrationTest public class DestructibleThresholdActivationTest : ContentIntegrationTest
{ {
private static readonly string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
private static readonly string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
private static readonly string Prototypes = $@"
- type: entity
id: {SpawnedEntityId}
name: {SpawnedEntityId}
- type: entity
id: {DestructibleEntityId}
name: {DestructibleEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
20:
triggersOnce: false
50:
triggersOnce: false
behaviors:
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
{SpawnedEntityId}:
min: 1
max: 1
- !type:DoActsBehavior
acts: [""Breakage""]
- type: TestThresholdListener
- type: entity
id: {DestructibleDestructionEntityId}
name: {DestructibleDestructionEntityId}
components:
- type: Damageable
- type: Destructible
thresholds:
50:
behaviors:
- !type:PlaySoundBehavior
sound: /Audio/Effects/woodhit.ogg
- !type:SpawnEntitiesBehavior
spawn:
{SpawnedEntityId}:
min: 1
max: 1
- !type:DoActsBehavior # This must come last as it destroys the entity.
acts: [""Destruction""]
- type: TestThresholdListener
";
private class TestThresholdListenerComponent : Component
{
public override string Name => "TestThresholdListener";
public List<DestructibleThresholdReachedMessage> ThresholdsReached { get; } = new();
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case DestructibleThresholdReachedMessage msg:
ThresholdsReached.Add(msg);
break;
}
}
}
[Test] [Test]
public async Task TestThresholdActivation() public async Task Test()
{ {
var server = StartServerDummyTicker(new ServerContentIntegrationOption var server = StartServerDummyTicker(new ServerContentIntegrationOption
{ {
@@ -130,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Destructible
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero); Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
}); });
await server.WaitAssertion(() => await server.WaitAssertion(() =>
@@ -138,36 +66,31 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true)); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// No thresholds reached yet, the earliest one is at 20 damage // No thresholds reached yet, the earliest one is at 20 damage
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero); Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true)); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
// Only one threshold reached, 20 // Only one threshold reached, 20
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold 20
var msg = sThresholdListenerComponent.ThresholdsReached[0]; var msg = sThresholdListenerComponent.ThresholdsReached[0];
// Check that it matches the total damage dealt
Assert.That(msg.TotalDamage, Is.EqualTo(20));
var threshold = msg.Threshold; var threshold = msg.Threshold;
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
Assert.That(threshold.Behaviors, Is.Empty); Assert.That(threshold.Behaviors, Is.Empty);
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
sThresholdListenerComponent.ThresholdsReached.Clear(); sThresholdListenerComponent.ThresholdsReached.Clear();
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 30, true)); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 30, true));
// Only one threshold reached, 50, since 20 was already reached before // One threshold reached, 50, since 20 already triggered before and it has not been healed below that amount
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
// Threshold 50
msg = sThresholdListenerComponent.ThresholdsReached[0]; msg = sThresholdListenerComponent.ThresholdsReached[0];
// Check that it matches the total damage dealt
Assert.That(msg.TotalDamage, Is.EqualTo(50));
threshold = msg.Threshold; threshold = msg.Threshold;
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
@@ -184,6 +107,7 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
sThresholdListenerComponent.ThresholdsReached.Clear(); sThresholdListenerComponent.ThresholdsReached.Clear();
@@ -191,8 +115,19 @@ namespace Content.IntegrationTests.Tests.Destructible
// Damage for 50 again, up to 100 now // Damage for 50 again, up to 100 now
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true)); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true));
// No new thresholds reached as even though they don't only trigger once, the entity was not healed below the threshold // No thresholds reached as they weren't healed below the trigger amount
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Heal down to 0
sDamageableComponent.Heal();
// Damage for 100, up to 100
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 100, true));
// Two thresholds reached as damage increased past the previous, 20 and 50
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(2));
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal the entity for 40 damage, down to 60 // Heal the entity for 40 damage, down to 60
sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true); sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true);
@@ -219,10 +154,6 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
msg = sThresholdListenerComponent.ThresholdsReached[0]; msg = sThresholdListenerComponent.ThresholdsReached[0];
// Check that it matches the total damage dealt
Assert.That(msg.TotalDamage, Is.EqualTo(50));
threshold = msg.Threshold; threshold = msg.Threshold;
// Check that it matches the YAML prototype // Check that it matches the YAML prototype
@@ -240,6 +171,7 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
// Reset thresholds reached // Reset thresholds reached
@@ -259,10 +191,9 @@ namespace Content.IntegrationTests.Tests.Destructible
// Verify the first one, should be the lowest one (20) // Verify the first one, should be the lowest one (20)
msg = sThresholdListenerComponent.ThresholdsReached[0]; msg = sThresholdListenerComponent.ThresholdsReached[0];
Assert.That(msg.ThresholdAmount, Is.EqualTo(20)); var trigger = (TotalDamageTrigger) msg.Threshold.Trigger;
Assert.NotNull(trigger);
// The total damage should be 50 Assert.That(trigger.Damage, Is.EqualTo(20));
Assert.That(msg.TotalDamage, Is.EqualTo(50));
threshold = msg.Threshold; threshold = msg.Threshold;
@@ -271,10 +202,9 @@ namespace Content.IntegrationTests.Tests.Destructible
// Verify the second one, should be the highest one (50) // Verify the second one, should be the highest one (50)
msg = sThresholdListenerComponent.ThresholdsReached[1]; msg = sThresholdListenerComponent.ThresholdsReached[1];
Assert.That(msg.ThresholdAmount, Is.EqualTo(50)); trigger = (TotalDamageTrigger) msg.Threshold.Trigger;
Assert.NotNull(trigger);
// Check that it matches the total damage dealt Assert.That(trigger.Damage, Is.EqualTo(50));
Assert.That(msg.TotalDamage, Is.EqualTo(50));
threshold = msg.Threshold; threshold = msg.Threshold;
@@ -292,6 +222,7 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
Assert.NotNull(threshold.Trigger);
Assert.That(threshold.Triggered, Is.True); Assert.That(threshold.Triggered, Is.True);
// Reset thresholds reached // Reset thresholds reached
@@ -304,8 +235,9 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0)); Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
// Set both thresholds to only trigger once // Set both thresholds to only trigger once
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values) foreach (var destructibleThreshold in sDestructibleComponent.Thresholds)
{ {
Assert.NotNull(destructibleThreshold.Trigger);
destructibleThreshold.TriggersOnce = true; destructibleThreshold.TriggersOnce = true;
} }
@@ -319,8 +251,9 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
// Set both thresholds to trigger multiple times // Set both thresholds to trigger multiple times
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values) foreach (var destructibleThreshold in sDestructibleComponent.Thresholds)
{ {
Assert.NotNull(destructibleThreshold.Trigger);
destructibleThreshold.TriggersOnce = false; destructibleThreshold.TriggersOnce = false;
} }
@@ -331,84 +264,5 @@ namespace Content.IntegrationTests.Tests.Destructible
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
}); });
} }
[Test]
public async Task DestructibleDestructionTest()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () =>
{
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
}
});
await server.WaitIdleAsync();
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDestructibleEntity = null;
IDamageableComponent sDamageableComponent = null;
DestructibleComponent sDestructibleComponent = null;
TestThresholdListenerComponent sThresholdListenerComponent = null;
await server.WaitPost(() =>
{
var mapId = new MapId(1);
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDestructionEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sDestructibleComponent = sDestructibleEntity.GetComponent<DestructibleComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
await server.WaitAssertion(() =>
{
var coordinates = sDestructibleEntity.Transform.Coordinates;
Assert.DoesNotThrow(() =>
{
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
});
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
Assert.That(threshold.Triggered, Is.True);
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior);
Assert.That(spawnEntitiesBehavior.Spawn.Count, Is.EqualTo(1));
Assert.That(spawnEntitiesBehavior.Spawn.Keys.Single(), Is.EqualTo(SpawnedEntityId));
Assert.That(spawnEntitiesBehavior.Spawn.Values.Single(), Is.EqualTo(new MinMax {Min = 1, Max = 1}));
var entitiesInRange = sEntityManager.GetEntitiesInRange(coordinates, 2);
var found = false;
foreach (var entity in entitiesInRange)
{
if (entity.Prototype == null)
{
continue;
}
if (entity.Prototype.Name != SpawnedEntityId)
{
continue;
}
found = true;
break;
}
Assert.That(found, Is.True);
});
}
} }
} }

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Destructible;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.IntegrationTests.Tests.Destructible
{
public class TestThresholdListenerComponent : Component
{
public override string Name => "TestThresholdListener";
public List<DestructibleThresholdReachedMessage> ThresholdsReached { get; } = new();
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case DestructibleThresholdReachedMessage msg:
ThresholdsReached.Add(msg);
break;
}
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
[TestFixture] [TestFixture]
[TestOf(typeof(CuffableComponent))] [TestOf(typeof(CuffableComponent))]
[TestOf(typeof(HandcuffComponent))] [TestOf(typeof(HandcuffComponent))]
public class CuffUnitTest : ContentIntegrationTest public class HandCuffTest : ContentIntegrationTest
{ {
private const string PROTOTYPES = @" private const string PROTOTYPES = @"
- type: entity - type: entity
@@ -77,7 +77,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
Assert.True(secondCuffs.TryGetComponent(out secondHandcuff!), $"Second handcuffs has no {nameof(HandcuffComponent)}"); Assert.True(secondCuffs.TryGetComponent(out secondHandcuff!), $"Second handcuffs has no {nameof(HandcuffComponent)}");
// Test to ensure cuffed players register the handcuffs // Test to ensure cuffed players register the handcuffs
cuffed.AddNewCuffs(cuffs); cuffed.TryAddNewCuffs(human, cuffs);
Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed"); Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed");
// Test to ensure a player with 4 hands will still only have 2 hands cuffed // Test to ensure a player with 4 hands will still only have 2 hands cuffed
@@ -86,7 +86,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed"); Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed");
// Test to give a player with 4 hands 2 sets of cuffs // Test to give a player with 4 hands 2 sets of cuffs
cuffed.AddNewCuffs(secondCuffs); cuffed.TryAddNewCuffs(human, secondCuffs);
Assert.True(cuffed.CuffedHandCount == 4, "Player doesn't have correct amount of hands cuffed"); Assert.True(cuffed.CuffedHandCount == 4, "Player doesn't have correct amount of hands cuffed");
}); });

View File

@@ -16,7 +16,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
[TestOf(typeof(ServerAlertsComponent))] [TestOf(typeof(ServerAlertsComponent))]
public class AlertsComponentTests : ContentIntegrationTest public class AlertsComponentTests : ContentIntegrationTest
{ {
[Test] [Test]
public async Task AlertsTest() public async Task AlertsTest()
{ {
@@ -61,11 +60,11 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
Assert.NotNull(alertsUI); Assert.NotNull(alertsUI);
// we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order. // we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order.
Assert.That(alertsUI.Grid.ChildCount, Is.EqualTo(3)); Assert.That(alertsUI.Grid.ChildCount, Is.GreaterThanOrEqualTo(3));
var alertControls = alertsUI.Grid.Children.Select(c => c as AlertControl); var alertControls = alertsUI.Grid.Children.Select(c => (AlertControl) c);
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2}; var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2};
Assert.That(alertIDs, Is.EqualTo(expectedIDs)); Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
}); });
await server.WaitAssertion(() => await server.WaitAssertion(() =>
@@ -96,12 +95,12 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is AlertsUI) as AlertsUI; clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is AlertsUI) as AlertsUI;
Assert.NotNull(alertsUI); Assert.NotNull(alertsUI);
// we should be seeing only 2 alerts now because one was cleared // we should be seeing 2 alerts now because one was cleared
Assert.That(alertsUI.Grid.ChildCount, Is.EqualTo(2)); Assert.That(alertsUI.Grid.ChildCount, Is.GreaterThanOrEqualTo(2));
var alertControls = alertsUI.Grid.Children.Select(c => c as AlertControl); var alertControls = alertsUI.Grid.Children.Select(c => (AlertControl) c);
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug2}; var expectedIDs = new [] {AlertType.HumanHealth, AlertType.Debug2};
Assert.That(alertIDs, Is.EqualTo(expectedIDs)); Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
}); });
} }
} }

View File

@@ -2,7 +2,6 @@
using Content.Server.GameObjects.Components.Gravity; using Content.Server.GameObjects.Components.Gravity;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Gravity;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Utility; using Content.Shared.Utility;
@@ -45,10 +44,7 @@ namespace Content.IntegrationTests.Tests.Gravity
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var mapId = mapManager.CreateMap(); var mapId = new MapId(1);
pauseManager.AddUninitializedMap(mapId);
var gridId = new GridId(1); var gridId = new GridId(1);
if (!mapManager.TryGetGrid(gridId, out var grid)) if (!mapManager.TryGetGrid(gridId, out var grid))
@@ -56,14 +52,7 @@ namespace Content.IntegrationTests.Tests.Gravity
grid = mapManager.CreateGrid(mapId, gridId); grid = mapManager.CreateGrid(mapId, gridId);
} }
var tileDefinition = tileDefinitionManager["underplating"];
var tile = new Tile(tileDefinition.TileId);
var coordinates = grid.ToCoordinates(); var coordinates = grid.ToCoordinates();
grid.SetTile(coordinates, tile);
pauseManager.DoMapInitialize(mapId);
human = entityManager.SpawnEntity("HumanDummy", coordinates); human = entityManager.SpawnEntity("HumanDummy", coordinates);
Assert.True(human.TryGetComponent(out alerts)); Assert.True(human.TryGetComponent(out alerts));
@@ -98,7 +87,7 @@ namespace Content.IntegrationTests.Tests.Gravity
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
Assert.False(alerts.IsShowingAlert(AlertType.Weightless)); Assert.True(alerts.IsShowingAlert(AlertType.Weightless));
}); });
} }
} }

View File

@@ -0,0 +1,26 @@
#nullable enable
using Content.Server.GameObjects.Components.ActionBlocking;
using Content.Shared.Alert;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
namespace Content.Server.Alert.Click
{
/// <summary>
/// Try to remove handcuffs from yourself
/// </summary>
[UsedImplicitly]
public class RemoveCuffs : IAlertClick
{
void IExposeData.ExposeData(ObjectSerializer serializer) {}
public void AlertClicked(ClickAlertEventArgs args)
{
if (args.Player.TryGetComponent(out CuffableComponent? cuffableComponent))
{
cuffableComponent.TryUncuff(args.Player);
}
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.Administration;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Headset; using Content.Server.GameObjects.Components.Headset;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Mobs.Speech;
using Content.Server.GameObjects.Components.Observer; using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces; using Content.Server.Interfaces;

View File

@@ -9,6 +9,7 @@ using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -83,7 +84,7 @@ namespace Content.Server.Chemistry.ReactionEffects
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
} }
public void ExposeData(ObjectSerializer serializer) void IExposeData.ExposeData(ObjectSerializer serializer)
{ {
serializer.DataField(ref _rangeConstant, "rangeConstant",0f); serializer.DataField(ref _rangeConstant, "rangeConstant",0f);
serializer.DataField(ref _rangeMultiplier, "rangeMultiplier",1.1f); serializer.DataField(ref _rangeMultiplier, "rangeMultiplier",1.1f);

View File

@@ -1,3 +1,5 @@
// ReSharper disable once RedundantUsingDirective
// Used to warn the player in big red letters in debug mode
using System.Linq; using System.Linq;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
@@ -6,6 +8,7 @@ using Robust.Server.Interfaces.Timing;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Commands.GameTicking namespace Content.Server.Commands.GameTicking
@@ -26,6 +29,10 @@ namespace Content.Server.Commands.GameTicking
return; return;
} }
#if DEBUG
shell.WriteError("WARNING: The server is using a debug build. You are risking losing your changes.");
#endif
var mapManager = IoCManager.Resolve<IMapManager>(); var mapManager = IoCManager.Resolve<IMapManager>();
int mapId; int mapId;
string mapName; string mapName;

View File

@@ -1,8 +1,10 @@
#nullable enable #nullable enable
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Mobs.Speech;
using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.Components.Movement;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.GameObjects.Components.Mobs.Speech;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -47,6 +49,8 @@ namespace Content.Server.Commands
entity.EnsureComponent<MindComponent>(); entity.EnsureComponent<MindComponent>();
entity.EnsureComponent<PlayerInputMoverComponent>(); entity.EnsureComponent<PlayerInputMoverComponent>();
entity.EnsureComponent<SharedSpeechComponent>();
entity.EnsureComponent<SharedEmotingComponent>();
} }
} }
} }

View File

@@ -1,8 +1,10 @@
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Explosion; using Content.Server.GameObjects.Components.Explosion;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Tag;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Maps; using Content.Shared.Maps;
using Content.Shared.Physics; using Content.Shared.Physics;
@@ -39,7 +41,7 @@ namespace Content.Server.Explosions
private static readonly float LightBreakChance = 0.3f; private static readonly float LightBreakChance = 0.3f;
private static readonly float HeavyBreakChance = 0.8f; private static readonly float HeavyBreakChance = 0.8f;
private static bool IgnoreExplosivePassable(IEntity e) => (e.GetComponent<IPhysicsComponent>().CollisionLayer & (int) CollisionGroup.ExplosivePassable) != 0; private static bool IgnoreExplosivePassable(IEntity e) => e.HasTag("ExplosivePassable");
private static ExplosionSeverity CalculateSeverity(float distance, float devastationRange, float heaveyRange) private static ExplosionSeverity CalculateSeverity(float distance, float devastationRange, float heaveyRange)
{ {
@@ -96,7 +98,7 @@ namespace Content.Server.Explosions
continue; continue;
} }
if (!entity.TryGetComponent(out IPhysicsComponent body) || body.PhysicsShapes.Count < 1) if (!entity.TryGetComponent(out IPhysicsComponent? body) || body.PhysicsShapes.Count < 1)
{ {
continue; continue;
} }
@@ -117,7 +119,7 @@ namespace Content.Server.Explosions
// Impassable entities are handled first. If they are damaged enough, they are destroyed and they may // Impassable entities are handled first. If they are damaged enough, they are destroyed and they may
// be able to spawn a new entity. I.e Wall -> Girder. // be able to spawn a new entity. I.e Wall -> Girder.
// Girder has a layer ExplosivePassable, and the predicate make it so the entities with this layer are ignored // Girder has a tag ExplosivePassable, and the predicate make it so the entities with this tag are ignored
var epicenterMapPos = epicenter.ToMap(entityManager); var epicenterMapPos = epicenter.ToMap(entityManager);
foreach (var (entity, distance) in impassableEntities) foreach (var (entity, distance) in impassableEntities)
{ {
@@ -228,7 +230,7 @@ namespace Content.Server.Explosions
var players = playerManager.GetPlayersInRange(epicenter, (int) Math.Ceiling(maxRange)); var players = playerManager.GetPlayersInRange(epicenter, (int) Math.Ceiling(maxRange));
foreach (var player in players) foreach (var player in players)
{ {
if (player.AttachedEntity == null || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent recoil)) if (player.AttachedEntity == null || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent? recoil))
{ {
continue; continue;
} }
@@ -279,7 +281,7 @@ namespace Content.Server.Explosions
int lightImpactRange = 0, int flashRange = 0) int lightImpactRange = 0, int flashRange = 0)
{ {
// If you want to directly set off the explosive // If you want to directly set off the explosive
if (!entity.Deleted && entity.TryGetComponent(out ExplosiveComponent explosive) && !explosive.Exploding) if (!entity.Deleted && entity.TryGetComponent(out ExplosiveComponent? explosive) && !explosive.Exploding)
{ {
explosive.Explosion(); explosive.Explosion();
} }

View File

@@ -1,15 +1,13 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.ActionBlocking; using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.GameObjects.Verbs; using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
@@ -46,24 +44,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
private Container _container = default!; private Container _container = default!;
private float _interactRange; // TODO: Make a component message
private IHandsComponent _hands; public event Action? OnCuffedStateChanged;
public event Action OnCuffedStateChanged; private bool _uncuffing;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner); _container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_interactRange = SharedInteractionSystem.InteractionRange / 2;
Owner.EntityManager.EventBus.SubscribeEvent<HandCountChangedEvent>(EventSource.Local, this, HandleHandCountChange); Owner.EntityManager.EventBus.SubscribeEvent<HandCountChangedEvent>(EventSource.Local, this, HandleHandCountChange);
if (!Owner.TryGetComponent(out _hands)) Owner.EnsureComponentWarn<HandsComponent>();
{
Logger.Warning("Player does not have an IHandsComponent!");
}
} }
public override ComponentState GetComponentState() public override ComponentState GetComponentState()
@@ -99,27 +93,35 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// Add a set of cuffs to an existing CuffedComponent. /// Add a set of cuffs to an existing CuffedComponent.
/// </summary> /// </summary>
/// <param name="prototype"></param> /// <param name="prototype"></param>
public void AddNewCuffs(IEntity handcuff) public bool TryAddNewCuffs(IEntity user, IEntity handcuff)
{ {
if (!handcuff.HasComponent<HandcuffComponent>()) if (!handcuff.HasComponent<HandcuffComponent>())
{ {
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!"); Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
return; return false;
} }
if (!handcuff.InRangeUnobstructed(Owner, _interactRange)) if (!handcuff.InRangeUnobstructed(Owner))
{ {
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!"); Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
return; return true;
}
// Success!
if (user.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.IsHolding(handcuff))
{
// Good lord handscomponent is scuffed, I hope some smug person will fix it someday
handsComponent.Drop(handcuff);
} }
_container.Insert(handcuff); _container.Insert(handcuff);
CanStillInteract = _hands.Hands.Count() > CuffedHandCount; CanStillInteract = Owner.TryGetComponent(out HandsComponent? ownerHands) && ownerHands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged?.Invoke(); OnCuffedStateChanged?.Invoke();
UpdateAlert(); UpdateAlert();
UpdateHeldItems(); UpdateHeldItems();
Dirty(); Dirty();
return true;
} }
/// <summary> /// <summary>
@@ -128,7 +130,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
private void UpdateHandCount() private void UpdateHandCount()
{ {
var dirty = false; var dirty = false;
var handCount = _hands.Hands.Count(); var handCount = Owner.TryGetComponent(out HandsComponent? handsComponent) ? handsComponent.Hands.Count() : 0;
while (CuffedHandCount > handCount && CuffedHandCount > 0) while (CuffedHandCount > handCount && CuffedHandCount > 0)
{ {
@@ -142,7 +144,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (dirty) if (dirty)
{ {
CanStillInteract = handCount > CuffedHandCount; CanStillInteract = handCount > CuffedHandCount;
OnCuffedStateChanged.Invoke(); OnCuffedStateChanged?.Invoke();
Dirty(); Dirty();
} }
} }
@@ -160,17 +162,19 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// </summary> /// </summary>
public void UpdateHeldItems() public void UpdateHeldItems()
{ {
var itemCount = _hands.GetAllHeldItems().Count(); if (!Owner.TryGetComponent(out HandsComponent? handsComponent)) return;
var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
var itemCount = handsComponent.GetAllHeldItems().Count();
var freeHandCount = handsComponent.Hands.Count() - CuffedHandCount;
if (freeHandCount < itemCount) if (freeHandCount < itemCount)
{ {
foreach (var item in _hands.GetAllHeldItems()) foreach (var item in handsComponent.GetAllHeldItems())
{ {
if (freeHandCount < itemCount) if (freeHandCount < itemCount)
{ {
freeHandCount++; freeHandCount++;
_hands.Drop(item.Owner, false); handsComponent.Drop(item.Owner, false);
} }
else else
{ {
@@ -185,7 +189,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// </summary> /// </summary>
private void UpdateAlert() private void UpdateAlert()
{ {
if (Owner.TryGetComponent(out ServerAlertsComponent status)) if (Owner.TryGetComponent(out ServerAlertsComponent? status))
{ {
if (CanStillInteract) if (CanStillInteract)
{ {
@@ -204,8 +208,10 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// </summary> /// </summary>
/// <param name="user">The cuffed entity</param> /// <param name="user">The cuffed entity</param>
/// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param> /// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null) public async void TryUncuff(IEntity user, IEntity? cuffsToRemove = null)
{ {
if (_uncuffing) return;
var isOwner = user == Owner; var isOwner = user == Owner;
if (cuffsToRemove == null) if (cuffsToRemove == null)
@@ -232,13 +238,13 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return; return;
} }
if (!isOwner && !user.InRangeUnobstructed(Owner, _interactRange)) if (!isOwner && !user.InRangeUnobstructed(Owner))
{ {
user.PopupMessage(Loc.GetString("You are too far away to remove the cuffs.")); user.PopupMessage(Loc.GetString("You are too far away to remove the cuffs."));
return; return;
} }
if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange)) if (!cuffsToRemove.InRangeUnobstructed(Owner))
{ {
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!"); Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return; return;
@@ -247,7 +253,17 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
user.PopupMessage(Loc.GetString("You start removing the cuffs.")); user.PopupMessage(Loc.GetString("You start removing the cuffs."));
var audio = EntitySystem.Get<AudioSystem>(); var audio = EntitySystem.Get<AudioSystem>();
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner); if (isOwner)
{
if (cuff.StartBreakoutSound != null)
audio.PlayFromEntity(cuff.StartBreakoutSound, Owner);
}
else
{
if (cuff.StartUncuffSound != null)
audio.PlayFromEntity(cuff.StartUncuffSound, Owner);
}
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime; var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime) var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
@@ -259,11 +275,16 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
}; };
var doAfterSystem = EntitySystem.Get<DoAfterSystem>(); var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
_uncuffing = true;
var result = await doAfterSystem.DoAfter(doAfterEventArgs); var result = await doAfterSystem.DoAfter(doAfterEventArgs);
_uncuffing = false;
if (result != DoAfterStatus.Cancelled) if (result != DoAfterStatus.Cancelled)
{ {
audio.PlayFromEntity(cuff.EndUncuffSound, Owner); if (cuff.EndUncuffSound != null)
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
_container.ForceRemove(cuffsToRemove); _container.ForceRemove(cuffsToRemove);
cuffsToRemove.Transform.AttachToGridOrMap(); cuffsToRemove.Transform.AttachToGridOrMap();
@@ -276,14 +297,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
cuffsToRemove.Name = cuff.BrokenName; cuffsToRemove.Name = cuff.BrokenName;
cuffsToRemove.Description = cuff.BrokenDesc; cuffsToRemove.Description = cuff.BrokenDesc;
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite)) if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite) && cuff.BrokenState != null)
{ {
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state? sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
} }
} }
CanStillInteract = _hands.Hands.Count() > CuffedHandCount; CanStillInteract = Owner.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged.Invoke(); OnCuffedStateChanged?.Invoke();
UpdateAlert(); UpdateAlert();
Dirty(); Dirty();

View File

@@ -1,10 +1,10 @@
using System; #nullable enable
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.Components.ActionBlocking; using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
@@ -14,7 +14,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -22,6 +21,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.ActionBlocking namespace Content.Server.GameObjects.Components.ActionBlocking
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedHandcuffComponent))]
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
{ {
/// <summary> /// <summary>
@@ -58,31 +58,31 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
/// The path of the RSI file used for the player cuffed overlay. /// The path of the RSI file used for the player cuffed overlay.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string CuffedRSI { get; set; } public string? CuffedRSI { get; set; }
/// <summary> /// <summary>
/// The iconstate used with the RSI file for the player cuffed overlay. /// The iconstate used with the RSI file for the player cuffed overlay.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string OverlayIconState { get; set; } public string? OverlayIconState { get; set; }
/// <summary> /// <summary>
/// The iconstate used for broken handcuffs /// The iconstate used for broken handcuffs
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string BrokenState { get; set; } public string? BrokenState { get; set; }
/// <summary> /// <summary>
/// The iconstate used for broken handcuffs /// The iconstate used for broken handcuffs
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string BrokenName { get; set; } public string BrokenName { get; set; } = default!;
/// <summary> /// <summary>
/// The iconstate used for broken handcuffs /// The iconstate used for broken handcuffs
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string BrokenDesc { get; set; } public string BrokenDesc { get; set; } = default!;
[ViewVariables] [ViewVariables]
public bool Broken public bool Broken
@@ -102,25 +102,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
} }
} }
public string StartCuffSound { get; set; } public string? StartCuffSound { get; set; }
public string EndCuffSound { get; set; } public string? EndCuffSound { get; set; }
public string StartBreakoutSound { get; set; } public string? StartBreakoutSound { get; set; }
public string StartUncuffSound { get; set; } public string? StartUncuffSound { get; set; }
public string EndUncuffSound { get; set; } public string? EndUncuffSound { get; set; }
public Color Color { get; set; } public Color Color { get; set; }
// Non-exposed data fields // Non-exposed data fields
private bool _isBroken = false; private bool _isBroken = false;
private float _interactRange;
private AudioSystem _audioSystem;
public override void Initialize() /// <summary>
{ /// Used to prevent DoAfter getting spammed.
base.Initialize(); /// </summary>
private bool _cuffing;
_audioSystem = EntitySystem.Get<AudioSystem>();
_interactRange = SharedInteractionSystem.InteractionRange / 2;
}
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
@@ -150,6 +145,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (_cuffing) return true;
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed)) if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
{ {
return false; return false;
@@ -179,7 +176,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return true; return true;
} }
if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true)) if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true))
{ {
eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!")); eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!"));
return true; return true;
@@ -187,7 +184,9 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target)); eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User)); eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
if (StartCuffSound != null)
EntitySystem.Get<AudioSystem>().PlayFromEntity(StartCuffSound, Owner);
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed); TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
return true; return true;
@@ -214,22 +213,21 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
NeedHand = true NeedHand = true
}; };
_cuffing = true;
var result = await EntitySystem.Get<DoAfterSystem>().DoAfter(doAfterEventArgs); var result = await EntitySystem.Get<DoAfterSystem>().DoAfter(doAfterEventArgs);
_cuffing = false;
if (result != DoAfterStatus.Cancelled) if (result != DoAfterStatus.Cancelled)
{ {
_audioSystem.PlayFromEntity(EndCuffSound, Owner); if (cuffs.TryAddNewCuffs(user, Owner))
user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target)); {
target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user)); if (EndCuffSound != null)
EntitySystem.Get<AudioSystem>().PlayFromEntity(EndCuffSound, Owner);
if (user.TryGetComponent<HandsComponent>(out var hands)) user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
{ target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
hands.Drop(Owner);
cuffs.AddNewCuffs(Owner);
}
else
{
Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
} }
} }
else else

View File

@@ -101,7 +101,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
_appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_initialInletDirection, _initialOutletDirection, PumpEnabled)); _appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_initialInletDirection, _initialOutletDirection, PumpEnabled));
} }
public void Activate(ActivateEventArgs eventArgs) void IActivate.Activate(ActivateEventArgs eventArgs)
{ {
PumpEnabled = !PumpEnabled; PumpEnabled = !PumpEnabled;
} }

View File

@@ -1,7 +1,9 @@
#nullable enable #nullable enable
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -56,6 +58,13 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
newEntity.EnsureComponent<MindComponent>(); newEntity.EnsureComponent<MindComponent>();
var oldMind = oldEntity.EnsureComponent<MindComponent>(); var oldMind = oldEntity.EnsureComponent<MindComponent>();
if (!newEntity.HasComponent<IGhostOnMove>())
newEntity.AddComponent<GhostOnMoveComponent>();
// TODO: This is an awful solution.
if (!newEntity.HasComponent<IMoverComponent>())
newEntity.AddComponent<SharedDummyInputMoverComponent>();
oldMind.Mind?.TransferTo(newEntity); oldMind.Mind?.TransferTo(newEntity);
} }
} }

View File

@@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System; using System;
using Content.Server.Commands.Observer; using Content.Server.Commands.Observer;
using Content.Server.GameObjects.Components.Observer;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Body.Part; using Content.Shared.GameObjects.Components.Body.Part;
@@ -25,7 +26,8 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedBodyComponent))] [ComponentReference(typeof(SharedBodyComponent))]
[ComponentReference(typeof(IBody))] [ComponentReference(typeof(IBody))]
public class BodyComponent : SharedBodyComponent, IRelayMoveInput [ComponentReference(typeof(IGhostOnMove))]
public class BodyComponent : SharedBodyComponent, IRelayMoveInput, IGhostOnMove
{ {
private Container _partContainer = default!; private Container _partContainer = default!;

View File

@@ -1,3 +1,4 @@
using Content.Server.GameObjects.Components.Construction;
using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -87,6 +88,9 @@ namespace Content.Server.GameObjects.Components
if(!container.Insert(board)) if(!container.Insert(board))
Logger.Warning($"Couldn't insert board {board} to computer {Owner}!"); Logger.Warning($"Couldn't insert board {board} to computer {Owner}!");
if (Owner.TryGetComponent(out ConstructionComponent construction))
construction.AddContainer("board");
} }
public void MapInit() public void MapInit()

View File

@@ -22,22 +22,15 @@ namespace Content.Server.GameObjects.Components.Destructible
public override string Name => "Destructible"; public override string Name => "Destructible";
[ViewVariables] [ViewVariables] private List<Threshold> _thresholds = new();
private SortedDictionary<int, Threshold> _lowestToHighestThresholds = new();
[ViewVariables] private int PreviousTotalDamage { get; set; } public IReadOnlyList<Threshold> Thresholds => _thresholds;
public IReadOnlyDictionary<int, Threshold> LowestToHighestThresholds => _lowestToHighestThresholds;
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
base.ExposeData(serializer); base.ExposeData(serializer);
serializer.DataReadWriteFunction( serializer.DataField(ref _thresholds, "thresholds", new List<Threshold>());
"thresholds",
new Dictionary<int, Threshold>(),
thresholds => _lowestToHighestThresholds = new SortedDictionary<int, Threshold>(thresholds),
() => new Dictionary<int, Threshold>(_lowestToHighestThresholds));
} }
public override void Initialize() public override void Initialize()
@@ -60,32 +53,17 @@ namespace Content.Server.GameObjects.Components.Destructible
break; break;
} }
foreach (var (damage, threshold) in _lowestToHighestThresholds) foreach (var threshold in _thresholds)
{ {
if (threshold.Triggered) if (threshold.Reached(msg.Damageable, _destructibleSystem))
{ {
if (threshold.TriggersOnce) var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold);
{
continue;
}
if (PreviousTotalDamage >= damage)
{
continue;
}
}
if (msg.Damageable.TotalDamage >= damage)
{
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold, msg.Damageable.TotalDamage, damage);
SendMessage(thresholdMessage); SendMessage(thresholdMessage);
threshold.Trigger(Owner, _destructibleSystem); threshold.Execute(Owner, _destructibleSystem);
} }
} }
PreviousTotalDamage = msg.Damageable.TotalDamage;
break; break;
} }
} }

View File

@@ -5,26 +5,14 @@ namespace Content.Server.GameObjects.Components.Destructible
{ {
public class DestructibleThresholdReachedMessage : ComponentMessage public class DestructibleThresholdReachedMessage : ComponentMessage
{ {
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold, int totalDamage, int thresholdAmount) public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold)
{ {
Parent = parent; Parent = parent;
Threshold = threshold; Threshold = threshold;
TotalDamage = totalDamage;
ThresholdAmount = thresholdAmount;
} }
public DestructibleComponent Parent { get; } public DestructibleComponent Parent { get; }
public Threshold Threshold { get; } public Threshold Threshold { get; }
/// <summary>
/// The amount of total damage currently had that triggered this threshold.
/// </summary>
public int TotalDamage { get; }
/// <summary>
/// The amount of damage at which this threshold triggers.
/// </summary>
public int ThresholdAmount { get; }
} }
} }

View File

@@ -1,11 +1,13 @@
using Content.Server.GameObjects.EntitySystems; using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[Serializable]
public class DoActsBehavior : IThresholdBehavior public class DoActsBehavior : IThresholdBehavior
{ {
private int _acts; private int _acts;
@@ -30,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
return (_acts & (int) act) != 0; return (_acts & (int) act) != 0;
} }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (HasAct(ThresholdActs.Breakage)) if (HasAct(ThresholdActs.Breakage))
{ {

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[UsedImplicitly] [UsedImplicitly]
public class GibBehavior : IThresholdBehavior public class GibBehavior : IThresholdBehavior
@@ -17,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
serializer.DataField(ref _recursive, "recursive", true); serializer.DataField(ref _recursive, "recursive", true);
} }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (owner.TryGetComponent(out IBody body)) if (owner.TryGetComponent(out IBody body))
{ {

View File

@@ -2,18 +2,18 @@
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
public interface IThresholdBehavior : IExposeData public interface IThresholdBehavior : IExposeData
{ {
/// <summary> /// <summary>
/// Triggers this behavior. /// Executes this behavior.
/// </summary> /// </summary>
/// <param name="owner">The entity that owns this behavior.</param> /// <param name="owner">The entity that owns this behavior.</param>
/// <param name="system"> /// <param name="system">
/// An instance of <see cref="DestructibleSystem"/> to pull dependencies /// An instance of <see cref="DestructibleSystem"/> to pull dependencies
/// and other systems from. /// and other systems from.
/// </param> /// </param>
void Trigger(IEntity owner, DestructibleSystem system); void Execute(IEntity owner, DestructibleSystem system);
} }
} }

View File

@@ -1,11 +1,13 @@
using Content.Server.GameObjects.EntitySystems; using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio; using Content.Shared.Audio;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[Serializable]
public class PlaySoundBehavior : IThresholdBehavior public class PlaySoundBehavior : IThresholdBehavior
{ {
/// <summary> /// <summary>
@@ -18,7 +20,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
serializer.DataField(this, x => x.Sound, "sound", string.Empty); serializer.DataField(this, x => x.Sound, "sound", string.Empty);
} }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (string.IsNullOrEmpty(Sound)) if (string.IsNullOrEmpty(Sound))
{ {

View File

@@ -1,11 +1,13 @@
using Content.Server.GameObjects.EntitySystems; using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio; using Content.Shared.Audio;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[Serializable]
public class PlaySoundCollectionBehavior : IThresholdBehavior public class PlaySoundCollectionBehavior : IThresholdBehavior
{ {
/// <summary> /// <summary>
@@ -18,7 +20,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
serializer.DataField(this, x => x.SoundCollection, "soundCollection", string.Empty); serializer.DataField(this, x => x.SoundCollection, "soundCollection", string.Empty);
} }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (string.IsNullOrEmpty(SoundCollection)) if (string.IsNullOrEmpty(SoundCollection))
{ {

View File

@@ -1,4 +1,5 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameObjects.Components.Stack; using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
@@ -7,8 +8,9 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[Serializable]
public class SpawnEntitiesBehavior : IThresholdBehavior public class SpawnEntitiesBehavior : IThresholdBehavior
{ {
/// <summary> /// <summary>
@@ -21,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
serializer.DataField(this, x => x.Spawn, "spawn", new Dictionary<string, MinMax>()); serializer.DataField(this, x => x.Spawn, "spawn", new Dictionary<string, MinMax>());
} }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
foreach (var (entityId, minMax) in Spawn) foreach (var (entityId, minMax) in Spawn)
{ {

View File

@@ -7,14 +7,14 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
{ {
[UsedImplicitly] [UsedImplicitly]
public class SpillBehavior : IThresholdBehavior public class SpillBehavior : IThresholdBehavior
{ {
void IExposeData.ExposeData(ObjectSerializer serializer) { } void IExposeData.ExposeData(ObjectSerializer serializer) { }
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (!owner.TryGetComponent(out SolutionContainerComponent? solutionContainer)) if (!owner.TryGetComponent(out SolutionContainerComponent? solutionContainer))
return; return;

View File

@@ -1,9 +1,11 @@
using Robust.Shared.Interfaces.Serialization; using System;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{ {
[Serializable]
public struct MinMax : IExposeData public struct MinMax : IExposeData
{ {
[ViewVariables] [ViewVariables]

View File

@@ -1,7 +1,9 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior; using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -13,6 +15,12 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{ {
private List<IThresholdBehavior> _behaviors = new(); private List<IThresholdBehavior> _behaviors = new();
/// <summary>
/// Whether or not this threshold was triggered in the previous call to
/// <see cref="Reached"/>.
/// </summary>
[ViewVariables] public bool OldTriggered { get; private set; }
/// <summary> /// <summary>
/// Whether or not this threshold has already been triggered. /// Whether or not this threshold has already been triggered.
/// </summary> /// </summary>
@@ -26,6 +34,11 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
/// </summary> /// </summary>
[ViewVariables] public bool TriggersOnce { get; set; } [ViewVariables] public bool TriggersOnce { get; set; }
/// <summary>
/// The trigger that decides if this threshold has been reached.
/// </summary>
[ViewVariables] public IThresholdTrigger? Trigger { get; set; }
/// <summary> /// <summary>
/// Behaviors to activate once this threshold is triggered. /// Behaviors to activate once this threshold is triggered.
/// </summary> /// </summary>
@@ -35,9 +48,37 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{ {
serializer.DataField(this, x => x.Triggered, "triggered", false); serializer.DataField(this, x => x.Triggered, "triggered", false);
serializer.DataField(this, x => x.TriggersOnce, "triggersOnce", false); serializer.DataField(this, x => x.TriggersOnce, "triggersOnce", false);
serializer.DataField(this, x => x.Trigger, "trigger", null);
serializer.DataField(ref _behaviors, "behaviors", new List<IThresholdBehavior>()); serializer.DataField(ref _behaviors, "behaviors", new List<IThresholdBehavior>());
} }
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
if (Trigger == null)
{
return false;
}
if (Triggered && TriggersOnce)
{
return false;
}
if (OldTriggered)
{
OldTriggered = Trigger.Reached(damageable, system);
return false;
}
if (!Trigger.Reached(damageable, system))
{
return false;
}
OldTriggered = true;
return true;
}
/// <summary> /// <summary>
/// Triggers this threshold. /// Triggers this threshold.
/// </summary> /// </summary>
@@ -46,7 +87,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
/// An instance of <see cref="DestructibleSystem"/> to get dependency and /// An instance of <see cref="DestructibleSystem"/> to get dependency and
/// system references from, if relevant. /// system references from, if relevant.
/// </param> /// </param>
public void Trigger(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
Triggered = true; Triggered = true;
@@ -56,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
if (owner.Deleted) if (owner.Deleted)
return; return;
behavior.Trigger(owner, system); behavior.Execute(owner, system);
} }
} }
} }

View File

@@ -0,0 +1,21 @@
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
{
public interface IThresholdTrigger : IExposeData
{
/// <summary>
/// Checks if this trigger has been reached.
/// </summary>
/// <param name="damageable">The damageable component to check with.</param>
/// <param name="system">
/// An instance of <see cref="DestructibleSystem"/> to pull
/// dependencies from, if any.
/// </param>
/// <returns>true if this trigger has been reached, false otherwise.</returns>
bool Reached(IDamageableComponent damageable, DestructibleSystem system);
}
}

View File

@@ -0,0 +1,48 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
{
/// <summary>
/// A trigger that will activate when all of the damage classes received
/// are above the specified threshold.
/// </summary>
[Serializable]
public class TotalDamageClassesTrigger : IThresholdTrigger
{
/// <summary>
/// The amount of damage at which this threshold will trigger.
/// The damage requirements of all <see cref="DamageClass"/> must be met.
/// </summary>
private Dictionary<DamageClass, int> Damage { get; set; } = new();
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Damage, "damage", new Dictionary<DamageClass, int>());
}
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
foreach (var (type, damageRequired) in Damage)
{
if (!damageable.TryGetDamage(type, out var damageReceived))
{
return false;
}
if (damageReceived < damageRequired)
{
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,32 @@
#nullable enable
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
{
/// <summary>
/// A trigger that will activate when the amount of total damage received
/// is above the specified threshold.
/// </summary>
[Serializable]
public class TotalDamageTrigger : IThresholdTrigger
{
/// <summary>
/// The amount of total damage at which this threshold will trigger.
/// </summary>
public int Damage { get; private set; }
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Damage, "damage", 0);
}
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
return damageable.TotalDamage >= Damage;
}
}
}

View File

@@ -0,0 +1,48 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
{
/// <summary>
/// A trigger that will activate when all of the damage types received
/// are above the specified threshold.
/// </summary>
[Serializable]
public class TotalDamageTypesTrigger : IThresholdTrigger
{
/// <summary>
/// The amount of damage at which this threshold will trigger.
/// The damage requirements of all <see cref="DamageType"/> must be met.
/// </summary>
private Dictionary<DamageType, int> Damage { get; set; } = new();
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Damage, "damage", new Dictionary<DamageType, int>());
}
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
foreach (var (type, damageRequired) in Damage)
{
if (!damageable.TryGetDamage(type, out var damageReceived))
{
return false;
}
if (damageReceived < damageRequired)
{
return false;
}
}
return true;
}
}
}

View File

@@ -80,9 +80,18 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
private TimeSpan _flushDelay; private TimeSpan _flushDelay;
/// <summary>
/// Delay from trying to enter disposals ourselves.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
private float _entryDelay; private float _entryDelay;
/// <summary>
/// Delay from trying to shove someone else into disposals.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private float _draggedEntryDelay;
/// <summary> /// <summary>
/// Token used to cancel the automatic engage of a disposal unit /// Token used to cancel the automatic engage of a disposal unit
/// after an entity enters it. /// after an entity enters it.
@@ -192,11 +201,15 @@ namespace Content.Server.GameObjects.Components.Disposal
if (!CanInsert(entity)) if (!CanInsert(entity))
return false; return false;
if (user != null && _entryDelay > 0f) var delay = user == entity ? _entryDelay : _draggedEntryDelay;
if (user != null && delay > 0.0f)
{ {
var doAfterSystem = EntitySystem.Get<DoAfterSystem>(); var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner) // Can't check if our target AND disposals moves currently so we'll just check target.
// if you really want to check if disposals moves then add a predicate.
var doAfterArgs = new DoAfterEventArgs(user, delay, default, entity)
{ {
BreakOnDamage = true, BreakOnDamage = true,
BreakOnStun = true, BreakOnStun = true,
@@ -209,7 +222,6 @@ namespace Content.Server.GameObjects.Components.Disposal
if (result == DoAfterStatus.Cancelled) if (result == DoAfterStatus.Cancelled)
return false; return false;
} }
if (!_container.Insert(entity)) if (!_container.Insert(entity))
@@ -535,7 +547,8 @@ namespace Content.Server.GameObjects.Components.Disposal
() => (int) _flushDelay.TotalSeconds); () => (int) _flushDelay.TotalSeconds);
serializer.DataField(this, x => x.Air, "air", new GasMixture(Atmospherics.CellVolume)); serializer.DataField(this, x => x.Air, "air", new GasMixture(Atmospherics.CellVolume));
serializer.DataField(ref _entryDelay, "entryDelay", 0.5f); serializer.DataField(ref _entryDelay, "entryDelay", 1.0f);
serializer.DataField(ref _draggedEntryDelay, "draggedEntryDelay", 3.0f);
} }
public override void Initialize() public override void Initialize()

View File

@@ -24,6 +24,7 @@ using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefine
namespace Content.Server.GameObjects.Components.GUI namespace Content.Server.GameObjects.Components.GUI
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedStrippableComponent))]
public sealed class StrippableComponent : SharedStrippableComponent public sealed class StrippableComponent : SharedStrippableComponent
{ {
public const float StripDelay = 2f; public const float StripDelay = 2f;

View File

@@ -1,4 +1,11 @@
namespace Content.Server.GameObjects.Components.Mobs.Speech #nullable enable
using Content.Shared.GameObjects.Components.Mobs.Speech;
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{ {
internal interface IAccentComponent internal interface IAccentComponent
{ {

View File

@@ -1,5 +1,6 @@
using Content.Server.Mobs; using Content.Server.Mobs;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Mobs namespace Content.Server.GameObjects.Components.Mobs
{ {
@@ -8,6 +9,7 @@ namespace Content.Server.GameObjects.Components.Mobs
{ {
public override string Name => "VisitingMind"; public override string Name => "VisitingMind";
[ViewVariables]
public Mind Mind { get; set; } public Mind Mind { get; set; }
public override void OnRemove() public override void OnRemove()

View File

@@ -1,15 +1,17 @@
using System.Linq; #nullable enable
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Body.Behavior; using Content.Server.GameObjects.Components.Body.Behavior;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.Fluids; using Content.Server.GameObjects.Components.Fluids;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition; using Content.Shared.GameObjects.Components.Nutrition;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -36,22 +38,39 @@ namespace Content.Server.GameObjects.Components.Nutrition
public override string Name => "Drink"; public override string Name => "Drink";
[ViewVariables] [ViewVariables]
private SolutionContainerComponent _contents; private bool _opened;
[ViewVariables] [ViewVariables]
private string _useSound; private string _useSound = string.Empty;
[ViewVariables] [ViewVariables]
private bool _defaultToOpened; private bool _defaultToOpened;
[ViewVariables(VVAccess.ReadWrite)]
public ReagentUnit TransferAmount { get; private set; } = ReagentUnit.New(2);
[ViewVariables(VVAccess.ReadWrite)]
public bool Opened { get; protected set; }
[ViewVariables]
public bool Empty => _contents.CurrentVolume.Float() <= 0;
private AppearanceComponent _appearanceComponent; [ViewVariables(VVAccess.ReadWrite)]
private string _soundCollection; public ReagentUnit TransferAmount { get; [UsedImplicitly] private set; } = ReagentUnit.New(2);
[ViewVariables(VVAccess.ReadWrite)]
public bool Opened
{
get => _opened;
set
{
if (_opened == value)
{
return;
}
_opened = value;
OpenedChanged();
}
}
[ViewVariables]
public bool Empty => Owner.GetComponentOrNull<ISolutionInteractionsComponent>()?.DrainAvailable <= 0;
private string _soundCollection = string.Empty;
private bool _pressurized; private bool _pressurized;
private string _burstSound; private string _burstSound = string.Empty;
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
@@ -66,18 +85,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
Owner.TryGetComponent(out _appearanceComponent);
if (!Owner.TryGetComponent(out _contents))
{
_contents = Owner.AddComponent<SolutionContainerComponent>();
}
_contents.Capabilities = SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable;
Opened = _defaultToOpened; Opened = _defaultToOpened;
UpdateAppearance(); UpdateAppearance();
} }
private void OpenedChanged()
{
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? contents))
{
return;
}
if (Opened)
{
contents.Capabilities |= SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable;
}
else
{
contents.Capabilities &= ~(SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable);
}
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{ {
UpdateAppearance(); UpdateAppearance();
@@ -85,7 +114,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
private void UpdateAppearance() private void UpdateAppearance()
{ {
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.Visual, _contents.CurrentVolume.Float()); if (!Owner.TryGetComponent(out AppearanceComponent? appearance) ||
!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents))
{
return;
}
appearance.SetData(SharedFoodComponent.FoodVisuals.Visual, contents.DrainAvailable.Float());
} }
bool IUse.UseEntity(UseEntityEventArgs args) bool IUse.UseEntity(UseEntityEventArgs args)
@@ -101,7 +136,8 @@ namespace Content.Server.GameObjects.Components.Nutrition
return false; return false;
} }
if (_contents.CurrentVolume.Float() <= 0) if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents) ||
contents.DrainAvailable <= 0)
{ {
args.User.PopupMessage(Loc.GetString("{0:theName} is empty!", Owner)); args.User.PopupMessage(Loc.GetString("{0:theName} is empty!", Owner));
return true; return true;
@@ -113,7 +149,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
//Force feeding a drink to someone. //Force feeding a drink to someone.
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
TryUseDrink(eventArgs.Target, forced: true); if (eventArgs.Target == null)
{
return false;
}
TryUseDrink(eventArgs.Target, true);
return true; return true;
} }
@@ -130,18 +172,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
private bool TryUseDrink(IEntity target, bool forced = false) private bool TryUseDrink(IEntity target, bool forced = false)
{ {
if (target == null || !_contents.CanDrain)
{
return false;
}
if (!Opened) if (!Opened)
{ {
target.PopupMessage(Loc.GetString("Open {0:theName} first!", Owner)); target.PopupMessage(Loc.GetString("Open {0:theName} first!", Owner));
return false; return false;
} }
if (_contents.CurrentVolume.Float() <= 0) if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions) ||
!interactions.CanDrain ||
interactions.DrainAvailable <= 0)
{ {
if (!forced) if (!forced)
{ {
@@ -151,26 +190,33 @@ namespace Content.Server.GameObjects.Components.Nutrition
return false; return false;
} }
if (!target.TryGetComponent(out IBody body) || if (!target.TryGetComponent(out IBody? body) ||
!body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs)) !body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
{ {
target.PopupMessage(Loc.GetString("You can't drink {0:theName}!", Owner));
return false; return false;
} }
var transferAmount = ReagentUnit.Min(TransferAmount, _contents.CurrentVolume); var transferAmount = ReagentUnit.Min(TransferAmount, interactions.DrainAvailable);
var split = _contents.SplitSolution(transferAmount); var drain = interactions.Drain(transferAmount);
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split)); var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain));
// All stomach are full or can't handle whatever solution we have. // All stomach are full or can't handle whatever solution we have.
if (firstStomach == null) if (firstStomach == null)
{ {
_contents.TryAddSolution(split);
target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner)); target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner));
if (!interactions.CanRefill)
{
drain.SpillAt(target, "PuddleSmear");
return false;
}
interactions.Refill(drain);
return false; return false;
} }
if (_useSound != null) if (!string.IsNullOrEmpty(_useSound))
{ {
EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f)); EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f));
} }
@@ -180,9 +226,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
// TODO: Account for partial transfer. // TODO: Account for partial transfer.
split.DoEntityReaction(target, ReactionMethod.Ingestion); drain.DoEntityReaction(target, ReactionMethod.Ingestion);
firstStomach.TryTransferSolution(split); firstStomach.TryTransferSolution(drain);
return true; return true;
} }
@@ -192,11 +238,16 @@ namespace Content.Server.GameObjects.Components.Nutrition
if (_pressurized && if (_pressurized &&
!Opened && !Opened &&
_random.Prob(0.25f) && _random.Prob(0.25f) &&
Owner.TryGetComponent(out SolutionContainerComponent component)) Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions))
{ {
Opened = true; Opened = true;
var solution = component.SplitSolution(component.CurrentVolume); if (!interactions.CanDrain)
{
return;
}
var solution = interactions.Drain(interactions.DrainAvailable);
solution.SpillAt(Owner, "PuddleSmear"); solution.SpillAt(Owner, "PuddleSmear");
EntitySystem.Get<AudioSystem>().PlayFromEntity(_burstSound, Owner, EntitySystem.Get<AudioSystem>().PlayFromEntity(_burstSound, Owner,

View File

@@ -0,0 +1,41 @@
#nullable enable
using System;
using Content.Server.Commands.Observer;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Movement;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.Console;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Observer
{
[RegisterComponent]
[ComponentReference(typeof(IGhostOnMove))]
public class GhostOnMoveComponent : Component, IRelayMoveInput, IGhostOnMove
{
public override string Name => "GhostOnMove";
public bool CanReturn { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.CanReturn, "canReturn", true);
}
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
{
// Let's not ghost if our mind is visiting...
if (Owner.HasComponent<VisitingMindComponent>()) return;
if (!Owner.TryGetComponent(out MindComponent? mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) return;
var host = IoCManager.Resolve<IServerConsoleHost>();
new Ghost().Execute(new ConsoleShell(host, session), string.Empty, Array.Empty<string>());
}
}
}

View File

@@ -0,0 +1,8 @@
using Content.Shared.GameObjects.Components.Movement;
namespace Content.Server.GameObjects.Components.Observer
{
public interface IGhostOnMove
{
}
}

View File

@@ -38,6 +38,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2); private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2);
private TimeSpan _lastThunk; private TimeSpan _lastThunk;
private bool _hasLampOnSpawn;
[ViewVariables] private bool _on; [ViewVariables] private bool _on;
@@ -148,6 +149,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
{ {
serializer.DataField(ref BulbType, "bulb", LightBulbType.Tube); serializer.DataField(ref BulbType, "bulb", LightBulbType.Tube);
serializer.DataField(ref _on, "on", true); serializer.DataField(ref _on, "on", true);
serializer.DataField(ref _hasLampOnSpawn, "hasLampOnSpawn", true);
} }
/// <summary> /// <summary>
@@ -229,15 +231,19 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
void IMapInit.MapInit() void IMapInit.MapInit()
{ {
var prototype = BulbType switch if (_hasLampOnSpawn)
{ {
LightBulbType.Bulb => "LightBulb", var prototype = BulbType switch
LightBulbType.Tube => "LightTube", {
_ => throw new ArgumentOutOfRangeException() LightBulbType.Bulb => "LightBulb",
}; LightBulbType.Tube => "LightTube",
_ => throw new ArgumentOutOfRangeException()
};
var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates); var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates);
_lightBulbContainer.Insert(entity); _lightBulbContainer.Insert(entity);
UpdateLight();
}
} }
public void TriggerSignal(bool signal) public void TriggerSignal(bool signal)

View File

@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System; using System;
using System.Linq;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
@@ -7,6 +8,7 @@ using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Server.GameObjects.Components.Destructible; using Content.Server.GameObjects.Components.Destructible;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
using Content.Shared.Utility; using Content.Shared.Utility;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -22,7 +24,6 @@ namespace Content.Server.GameObjects.Components
[ComponentReference(typeof(SharedWindowComponent))] [ComponentReference(typeof(SharedWindowComponent))]
public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand
{ {
public override void HandleMessage(ComponentMessage message, IComponent? component) public override void HandleMessage(ComponentMessage message, IComponent? component)
{ {
base.HandleMessage(message, component); base.HandleMessage(message, component);
@@ -40,26 +41,52 @@ namespace Content.Server.GameObjects.Components
private void UpdateVisuals(int currentDamage) private void UpdateVisuals(int currentDamage)
{ {
if (Owner.TryGetComponent(out AppearanceComponent? appearance)) if (Owner.TryGetComponent(out AppearanceComponent? appearance) &&
Owner.TryGetComponent(out DestructibleComponent? destructible))
{ {
if (Owner.TryGetComponent(out DestructibleComponent? destructible)) foreach (var threshold in destructible.Thresholds)
{ {
var damageThreshold = destructible.LowestToHighestThresholds.FirstOrNull()?.Key; if (threshold.Trigger is not TotalDamageTrigger trigger)
if (damageThreshold == null) return; {
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / damageThreshold); continue;
}
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / trigger.Damage);
} }
} }
} }
void IExamine.Examine(FormattedMessage message, bool inDetailsRange) void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{ {
var damage = Owner.GetComponentOrNull<IDamageableComponent>()?.TotalDamage; if (!Owner.TryGetComponent(out IDamageableComponent? damageable) ||
var damageThreshold = Owner.GetComponentOrNull<DestructibleComponent>()?.LowestToHighestThresholds.FirstOrNull()?.Key; !Owner.TryGetComponent(out DestructibleComponent? destructible))
if (damage == null || damageThreshold == null) return; {
var fraction = ((damage == 0 || damageThreshold == 0) return;
}
var damage = damageable.TotalDamage;
TotalDamageTrigger? trigger = null;
// TODO: Pretend this does not exist until https://github.com/space-wizards/space-station-14/pull/2783 is merged
foreach (var threshold in destructible.Thresholds)
{
if ((trigger = threshold.Trigger as TotalDamageTrigger) != null)
{
break;
}
}
if (trigger == null)
{
return;
}
var damageThreshold = trigger.Damage;
var fraction = damage == 0 || damageThreshold == 0
? 0f ? 0f
: (float) damage / damageThreshold); : (float) damage / damageThreshold;
var level = Math.Min(ContentHelpers.RoundToLevels((double) fraction, 1, 7), 5); var level = Math.Min(ContentHelpers.RoundToLevels((double) fraction, 1, 7), 5);
switch (level) switch (level)
{ {
case 0: case 0:

View File

@@ -13,6 +13,7 @@ namespace Content.Server.GameObjects.EntitySystems
[Dependency] public readonly IRobustRandom Random = default!; [Dependency] public readonly IRobustRandom Random = default!;
public AudioSystem AudioSystem { get; private set; } public AudioSystem AudioSystem { get; private set; }
public ActSystem ActSystem { get; private set; } public ActSystem ActSystem { get; private set; }
public override void Initialize() public override void Initialize()

View File

@@ -1,9 +1,8 @@
// ReSharper disable ArrangeTrailingCommaInMultilineLists
namespace Content.Server namespace Content.Server
{ {
public static class IgnoredComponents public static class IgnoredComponents
{ {
public static string[] List => new [] { public static string[] List => new [] {
"ConstructionGhost", "ConstructionGhost",
"IconSmooth", "IconSmooth",
@@ -18,9 +17,7 @@ namespace Content.Server
"Clickable", "Clickable",
"RadiatingLight", "RadiatingLight",
"Icon", "Icon",
"ClientEntitySpawner" "ClientEntitySpawner",
}; };
} }
} }

View File

@@ -29,7 +29,7 @@ namespace Content.Shared
GameLobbyEnabled = CVarDef.Create("game.lobbyenabled", false, CVar.ARCHIVE); GameLobbyEnabled = CVarDef.Create("game.lobbyenabled", false, CVar.ARCHIVE);
public static readonly CVarDef<int> public static readonly CVarDef<int>
GameLobbyDuration = CVarDef.Create("game.lobbyduration", 20, CVar.ARCHIVE); GameLobbyDuration = CVarDef.Create("game.lobbyduration", 60, CVar.ARCHIVE);
public static readonly CVarDef<string> public static readonly CVarDef<string>
GameLobbyDefaultPreset = CVarDef.Create("game.defaultpreset", "Suspicion", CVar.ARCHIVE); GameLobbyDefaultPreset = CVarDef.Create("game.defaultpreset", "Suspicion", CVar.ARCHIVE);

View File

@@ -0,0 +1,56 @@
#nullable enable
using Content.Shared.GameObjects.Components.Tag;
using Content.Shared.Physics;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using System.Linq;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Shared.Construction.ConstructionConditions
{
[UsedImplicitly]
public class WallmountCondition : IConstructionCondition
{
void IExposeData.ExposeData(ObjectSerializer serializer) { }
public bool Condition(IEntity user, EntityCoordinates location, Direction direction)
{
var entManager = IoCManager.Resolve<IEntityManager>();
// get blueprint and user position
var userWorldPosition = user.Transform.WorldPosition;
var objWorldPosition = location.ToMap(entManager).Position;
// find direction from user to blueprint
var userToObject = (objWorldPosition - userWorldPosition);
// dot product will be positive if user direction and blueprint are co-directed
var dotProd = Vector2.Dot(direction.ToVec(), userToObject);
if (dotProd > 0)
return false;
// now we need to check that user actually tries to build wallmount on a wall
var physics = IoCManager.Resolve<IPhysicsManager>();
var rUserToObj = new CollisionRay(userWorldPosition, userToObject.Normalized, (int) CollisionGroup.Impassable);
var length = userToObject.Length;
var userToObjRaycastResults = physics.IntersectRayWithPredicate(user.Transform.MapID, rUserToObj, maxLength: length,
predicate: (e) => !e.HasTag("Wall"));
if (!userToObjRaycastResults.Any())
return false;
// get this wall entity
var targetWall = userToObjRaycastResults.First().HitEntity;
// check that we didn't try to build wallmount that facing another adjacent wall
var rAdjWall = new CollisionRay(objWorldPosition, direction.ToVec(), (int) CollisionGroup.Impassable);
var adjWallRaycastResults = physics.IntersectRayWithPredicate(user.Transform.MapID, rAdjWall, maxLength: 0.5f,
predicate: (e) => e == targetWall || !e.HasTag("Wall"));
return !adjWallRaycastResults.Any();
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Damage namespace Content.Shared.Damage

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.ActionBlocking namespace Content.Shared.GameObjects.Components.ActionBlocking
{ {
public class SharedHandcuffComponent : Component public abstract class SharedHandcuffComponent : Component
{ {
public override string Name => "Handcuff"; public override string Name => "Handcuff";
public override uint? NetID => ContentNetIDs.HANDCUFFS; public override uint? NetID => ContentNetIDs.HANDCUFFS;

View File

@@ -12,13 +12,13 @@ namespace Content.Shared.GameObjects.Components.GUI
{ {
public override string Name => "Stripping"; public override string Name => "Stripping";
public bool CanDragDropOn(DragDropEventArgs eventArgs) bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
{ {
if (!eventArgs.Dragged.TryGetComponent(out SharedStrippableComponent? strippable)) return false; if (!eventArgs.Dragged.TryGetComponent(out SharedStrippableComponent? strippable)) return false;
return strippable.CanBeStripped(Owner); return strippable.CanBeStripped(Owner);
} }
public bool DragDropOn(DragDropEventArgs eventArgs) bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{ {
// Handled by StrippableComponent // Handled by StrippableComponent
return true; return true;

View File

@@ -0,0 +1,33 @@
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Mobs.Speech
{
[RegisterComponent]
public class SharedEmotingComponent : Component, IActionBlocker
{
private bool _enabled = true;
public override string Name => "Emoting";
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
Dirty();
}
}
bool IActionBlocker.CanEmote() => Enabled;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.Enabled, "enabled", true);
}
}
}

View File

@@ -0,0 +1,36 @@
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Mobs.Speech
{
/// <summary>
/// Component required for entities to be able to speak.
/// </summary>
[RegisterComponent]
public class SharedSpeechComponent : Component, IActionBlocker
{
private bool _enabled = true;
public override string Name => "Speech";
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
Dirty();
}
}
bool IActionBlocker.CanSpeak() => Enabled;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.Enabled, "enabled", true);
}
}
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.Shared.GameObjects.Components.Movement
{
[RegisterComponent]
[ComponentReference(typeof(IMoverComponent))]
public class SharedDummyInputMoverComponent : Component, IMoverComponent
{
public override string Name => "DummyInputMover";
public float CurrentWalkSpeed => 0f;
public float CurrentSprintSpeed => 0f;
public float CurrentPushSpeed => 0f;
public float GrabRange => 0f;
public bool Sprinting => false;
public (Vector2 walking, Vector2 sprinting) VelocityDir => (Vector2.Zero, Vector2.Zero);
public EntityCoordinates LastPosition { get; set; }
public float StepSoundDistance { get; set; }
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled)
{
}
public void SetSprinting(ushort subTick, bool walking)
{
}
}
}

View File

@@ -1,8 +1,11 @@
#nullable enable #nullable enable
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Tag namespace Content.Shared.GameObjects.Components.Tag
{ {
[Serializable, NetSerializable]
public class TagComponentState : ComponentState public class TagComponentState : ComponentState
{ {
public TagComponentState(string[] tags) : base(ContentNetIDs.TAG) public TagComponentState(string[] tags) : base(ContentNetIDs.TAG)

View File

@@ -1,4 +1,5 @@
using Content.Shared.GameObjects.EntitySystems.EffectBlocker; using Content.Shared.GameObjects.Components.Mobs.Speech;
using Content.Shared.GameObjects.EntitySystems.EffectBlocker;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -62,6 +63,9 @@ namespace Content.Shared.GameObjects.EntitySystems.ActionBlocker
public static bool CanSpeak(IEntity entity) public static bool CanSpeak(IEntity entity)
{ {
if (!entity.HasComponent<SharedSpeechComponent>())
return false;
var canSpeak = true; var canSpeak = true;
foreach (var blocker in entity.GetAllComponents<IActionBlocker>()) foreach (var blocker in entity.GetAllComponents<IActionBlocker>())
@@ -98,6 +102,9 @@ namespace Content.Shared.GameObjects.EntitySystems.ActionBlocker
public static bool CanEmote(IEntity entity) public static bool CanEmote(IEntity entity)
{ {
if (!entity.HasComponent<SharedEmotingComponent>())
return false;
var canEmote = true; var canEmote = true;
foreach (var blocker in entity.GetAllComponents<IActionBlocker>()) foreach (var blocker in entity.GetAllComponents<IActionBlocker>())

View File

@@ -1,3 +1,4 @@
#nullable enable
using System; using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -23,7 +24,6 @@ namespace Content.Shared.Physics
GhostImpassable = 1 << 6, // 64 Things impassible by ghosts/observers, ie blessed tiles or forcefields GhostImpassable = 1 << 6, // 64 Things impassible by ghosts/observers, ie blessed tiles or forcefields
Underplating = 1 << 7, // 128 Things that are under plating Underplating = 1 << 7, // 128 Things that are under plating
Passable = 1 << 8, // 256 Things that are passable Passable = 1 << 8, // 256 Things that are passable
ExplosivePassable = 1 << 9, // 512 Things that let the pressure of a explosion through
MapGrid = MapGridHelpers.CollisionGroup, // Map grids, like shuttles. This is the actual grid itself, not the walls or other entities connected to the grid. MapGrid = MapGridHelpers.CollisionGroup, // Map grids, like shuttles. This is the actual grid itself, not the walls or other entities connected to the grid.
MobMask = Impassable | MobImpassable | VaultImpassable | SmallImpassable, MobMask = Impassable | MobImpassable | VaultImpassable | SmallImpassable,

View File

@@ -17,7 +17,7 @@ namespace Content.Shared.Prototypes.Tag
{ {
public string ID { get; [UsedImplicitly] private set; } = default!; public string ID { get; [UsedImplicitly] private set; } = default!;
public void ExposeData(ObjectSerializer serializer) void IExposeData.ExposeData(ObjectSerializer serializer)
{ {
serializer.DataField(this, x => x.ID, "id", ""); serializer.DataField(this, x => x.ID, "id", "");
} }

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" Version="9.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
</Project>

83
Content.Tools/Map.cs Normal file
View File

@@ -0,0 +1,83 @@
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using YamlDotNet.Core;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Tools
{
public class Map
{
public Map(string path)
{
Path = path;
using var reader = new StreamReader(path);
var stream = new YamlStream();
stream.Load(reader);
Root = stream.Documents[0].RootNode;
TilemapNode = (YamlMappingNode) Root["tilemap"];
GridsNode = (YamlSequenceNode) Root["grids"];
_entitiesNode = (YamlSequenceNode) Root["entities"];
foreach (var entity in _entitiesNode)
{
var uid = uint.Parse(entity["uid"].AsString());
if (uid >= NextAvailableEntityId)
NextAvailableEntityId = uid + 1;
Entities[uid] = (YamlMappingNode) entity;
}
}
// Core
public string Path { get; }
public YamlNode Root { get; }
// Useful
public YamlMappingNode TilemapNode { get; }
public YamlSequenceNode GridsNode { get; }
// Entities lookup
private YamlSequenceNode _entitiesNode { get; }
public Dictionary<uint, YamlMappingNode> Entities { get; } = new Dictionary<uint, YamlMappingNode>();
public uint MaxId => Entities.Max(entry => entry.Key);
public uint NextAvailableEntityId { get; set; }
// ----
public void Save(string fileName)
{
// Update entities node
_entitiesNode.Children.Clear();
foreach (var kvp in Entities)
_entitiesNode.Add(kvp.Value);
using var writer = new StreamWriter(fileName);
var document = new YamlDocument(Root);
var stream = new YamlStream(document);
var emitter = new Emitter(writer);
var fixer = new TypeTagPreserver(emitter);
stream.Save(fixer, false);
writer.Flush();
}
public void Save()
{
Save(Path);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Content.Tools
{
internal static class MappingMergeDriver
{
/// %A: Our file
/// %O: Origin (common, base) file
/// %B: Other file
/// %P: Actual filename of the resulting file
public static void Main(string[] args)
{
var ours = new Map(args[0]);
var based = new Map(args[1]); // On what?
var other = new Map(args[2]);
if ((ours.GridsNode.Children.Count != 1) || (based.GridsNode.Children.Count != 1) || (other.GridsNode.Children.Count != 1))
{
Console.WriteLine("one or more files had an amount of grids not equal to 1");
Environment.Exit(1);
}
if (!(new Merger(ours, based, other).Merge()))
{
Console.WriteLine("unable to merge!");
Environment.Exit(1);
}
ours.Save();
Environment.Exit(0);
}
}
}

356
Content.Tools/Merger.cs Normal file
View File

@@ -0,0 +1,356 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Content.Tools
{
public class Merger
{
public Map MapOurs { get; }
public Map MapBased { get; }
public Map MapOther { get; }
public Dictionary<uint, uint> TileMapFromOtherToOurs { get; } = new Dictionary<uint, uint>();
public Dictionary<uint, uint> TileMapFromBasedToOurs { get; } = new Dictionary<uint, uint>();
public Dictionary<uint, uint> EntityMapFromOtherToOurs { get; } = new Dictionary<uint, uint>();
public List<uint> EntityListDirectMerge { get; } = new List<uint>();
private const int ExpectedChunkSize = 16 * 16 * 4;
public Merger(Map ours, Map based, Map other)
{
MapOurs = ours;
MapBased = based;
MapOther = other;
}
public bool Merge()
{
PlanTileMapping(TileMapFromOtherToOurs, MapOther);
PlanTileMapping(TileMapFromBasedToOurs, MapBased);
MergeTiles();
PlanEntityMapping();
return MergeEntities();
}
// -- Tiles --
public void PlanTileMapping(Dictionary<uint, uint> relativeOtherToOurs, Map relativeOther)
{
var mapping = new Dictionary<string, uint>();
uint nextAvailable = 0;
foreach (var kvp in MapOurs.TilemapNode)
{
var k = uint.Parse(kvp.Key.ToString());
var v = kvp.Value.ToString();
mapping[v] = k;
if (k >= nextAvailable)
nextAvailable = k + 1;
}
foreach (var kvp in relativeOther.TilemapNode)
{
var k = uint.Parse(kvp.Key.ToString());
var v = kvp.Value.ToString();
if (mapping.ContainsKey(v))
{
relativeOtherToOurs[k] = mapping[v];
}
else
{
MapOurs.TilemapNode.Add(nextAvailable.ToString(CultureInfo.InvariantCulture), v);
relativeOtherToOurs[k] = nextAvailable++;
}
}
}
public void MergeTiles()
{
var a = MapOurs.GridsNode.Children[0];
var b = MapBased.GridsNode.Children[0];
var c = MapOther.GridsNode.Children[0];
var aChunks = a["chunks"];
var bChunks = b["chunks"];
var cChunks = c["chunks"];
MergeTileChunks((YamlSequenceNode) aChunks, (YamlSequenceNode) bChunks, (YamlSequenceNode) cChunks);
}
public void MergeTileChunks(YamlSequenceNode aChunks, YamlSequenceNode bChunks, YamlSequenceNode cChunks)
{
var aMap = ConvertTileChunks(aChunks);
var bMap = ConvertTileChunks(bChunks);
var cMap = ConvertTileChunks(cChunks);
var xMap = new HashSet<string>();
foreach (var kvp in aMap)
xMap.Add(kvp.Key);
// don't include b because that would mess with chunk deletion
foreach (var kvp in cMap)
xMap.Add(kvp.Key);
foreach (var ind in xMap)
{
using var a = new MemoryStream(GetChunkBytes(aMap, ind));
using var b = new MemoryStream(GetChunkBytes(bMap, ind));
using var c = new MemoryStream(GetChunkBytes(cMap, ind));
using var aR = new BinaryReader(a);
using var bR = new BinaryReader(b);
using var cR = new BinaryReader(c);
var outB = new byte[ExpectedChunkSize];
{
using (var outS = new MemoryStream(outB))
using (var outW = new BinaryWriter(outS))
for (var i = 0; i < ExpectedChunkSize; i += 4)
{
var aI = aR.ReadUInt32();
var bI = MapTileId(bR.ReadUInt32(), TileMapFromBasedToOurs);
var cI = MapTileId(cR.ReadUInt32(), TileMapFromOtherToOurs);
// cI needs translation.
uint result = aI;
if (aI == bI)
{
// If aI == bI then aI did not change anything, so cI always wins
result = cI;
}
else if (bI != cI)
{
// If bI != cI then cI definitely changed something (conflict, but overrides aI)
result = cI;
Console.WriteLine("WARNING: Tile (" + ind + ")[" + i + "] was changed by both branches.");
}
outW.Write(result);
}
}
// Actually output chunk
if (!aMap.ContainsKey(ind))
{
var res = new YamlMappingNode();
res.Children["ind"] = ind;
aMap[ind] = res;
}
aMap[ind].Children["tiles"] = Convert.ToBase64String(outB);
}
}
public uint MapTileId(uint src, Dictionary<uint, uint> mapping)
{
return (src & 0xFFFF0000) | mapping[src & 0xFFFF];
}
public Dictionary<string, YamlMappingNode> ConvertTileChunks(YamlSequenceNode chunks)
{
var map = new Dictionary<string, YamlMappingNode>();
foreach (var chunk in chunks)
map[chunk["ind"].ToString()] = (YamlMappingNode) chunk;
return map;
}
public byte[] GetChunkBytes(Dictionary<string, YamlMappingNode> chunks, string ind)
{
if (!chunks.ContainsKey(ind))
return new byte[ExpectedChunkSize];
return Convert.FromBase64String(chunks[ind]["tiles"].ToString());
}
// -- Entities --
public void PlanEntityMapping()
{
// Ok, so here's how it works:
// 1. Entities that do not exist in "based" are additions.
// 2. Entities that exist in "based" but do not exist in the one map or the other are removals.
// Find modifications and deletions
foreach (var kvp in MapBased.Entities)
{
var deletedByOurs = !MapOurs.Entities.ContainsKey(kvp.Key);
var deletedByOther = !MapOther.Entities.ContainsKey(kvp.Key);
if (deletedByOther && !deletedByOurs)
{
// Delete
MapOurs.Entities.Remove(kvp.Key);
}
else if (!(deletedByOurs || deletedByOther))
{
// Modify
EntityMapFromOtherToOurs[kvp.Key] = kvp.Key;
}
}
// Find additions
foreach (var kvp in MapOther.Entities)
{
if (!MapBased.Entities.ContainsKey(kvp.Key))
{
// New
var newId = MapOurs.NextAvailableEntityId++;
EntityMapFromOtherToOurs[kvp.Key] = newId;
}
}
}
public bool MergeEntities()
{
bool success = true;
foreach (var kvp in EntityMapFromOtherToOurs)
{
// For debug use.
// Console.WriteLine("Entity C/" + kvp.Key + " -> A/" + kvp.Value);
YamlMappingNode oursEnt;
if (MapOurs.Entities.ContainsKey(kvp.Value))
{
oursEnt = MapOurs.Entities[kvp.Value];
if (!MapBased.Entities.TryGetValue(kvp.Value, out var basedEnt))
{
basedEnt = oursEnt;
}
if (!MergeEntityNodes(oursEnt, basedEnt, MapOther.Entities[kvp.Key]))
{
Console.WriteLine("Unable to successfully merge entity C/" + kvp.Key);
success = false;
}
}
else
{
oursEnt = (YamlMappingNode) YamlTools.CopyYamlNodes(MapOther.Entities[kvp.Key]);
if (!MapEntity(oursEnt)) {
Console.WriteLine("Unable to successfully import entity C/" + kvp.Key);
success = false;
} else {
MapOurs.Entities[kvp.Value] = oursEnt;
}
}
oursEnt.Children["uid"] = kvp.Value.ToString(CultureInfo.InvariantCulture);
}
return success;
}
public bool MergeEntityNodes(YamlMappingNode ours, YamlMappingNode based, YamlMappingNode other)
{
// Copy to intermediate
var otherMapped = (YamlMappingNode) YamlTools.CopyYamlNodes(other);
if (!MapEntity(otherMapped))
return false;
// Merge stuff that isn't components
var path = "Entity" + (other["uid"].ToString());
YamlTools.MergeYamlMappings(ours, based, otherMapped, path, new string[] {"components"});
// Components are special
var ourComponents = new Dictionary<string, YamlMappingNode>();
var basedComponents = new Dictionary<string, YamlMappingNode>();
var ourComponentsNode = (YamlSequenceNode) ours["components"];
var basedComponentsNode = (YamlSequenceNode) based["components"];
var otherComponentsNode = (YamlSequenceNode) otherMapped["components"];
foreach (var component in ourComponentsNode)
{
var name = component["type"].ToString();
ourComponents[name] = (YamlMappingNode) component;
}
foreach (var component in basedComponentsNode)
{
var name = component["type"].ToString();
basedComponents[name] = (YamlMappingNode) component;
}
foreach (var otherComponent in otherComponentsNode)
{
var name = otherComponent["type"].ToString();
if (ourComponents.ContainsKey(name))
{
var ourComponent = ourComponents[name];
if (!basedComponents.TryGetValue(name, out var basedComponent))
basedComponent = new YamlMappingNode();
YamlTools.MergeYamlNodes(ourComponent, basedComponent, otherComponent, path + "/components/" + name);
}
else
{
ourComponentsNode.Add(otherComponent);
}
}
return true;
}
public bool MapEntity(YamlMappingNode other)
{
var path = "Entity" + (other["uid"].ToString());
if (other.Children.ContainsKey("components"))
{
var components = (YamlSequenceNode) other["components"];
foreach (var component in components)
{
var type = component["type"].ToString();
if (type == "Transform")
{
if (!MapEntityProperty((YamlMappingNode) component, "parent", path))
return false;
}
else if (type == "ContainerContainer")
{
MapEntityRecursiveAndBadly(component, path);
}
}
}
return true;
}
public bool MapEntityProperty(YamlMappingNode node, string property, string path)
{
if (node.Children.ContainsKey(property)) {
var prop = node[property];
if (prop is YamlScalarNode)
return MapEntityProperty((YamlScalarNode) prop, path + "/" + property);
}
return true;
}
public bool MapEntityProperty(YamlScalarNode node, string path)
{
if (uint.TryParse(node.ToString(), out var uid))
{
if (EntityMapFromOtherToOurs.ContainsKey(uid))
{
node.Value = EntityMapFromOtherToOurs[uid].ToString(CultureInfo.InvariantCulture);
}
else
{
Console.WriteLine($"Error finding UID in MapEntityRecursiveAndBadly {path}. To fix this, the merge driver needs to be improved.");
return false;
}
}
return true;
}
public bool MapEntityRecursiveAndBadly(YamlNode node, string path)
{
switch (node)
{
case YamlSequenceNode subSequence:
var idx = 0;
foreach (var val in subSequence)
if (!MapEntityRecursiveAndBadly(val, path + "/" + (idx++)))
return false;
return true;
case YamlMappingNode subMapping:
foreach (var kvp in subMapping)
if (!MapEntityRecursiveAndBadly(kvp.Key, path))
return false;
foreach (var kvp in subMapping)
if (!MapEntityRecursiveAndBadly(kvp.Value, path + "/" + kvp.Key.ToString()))
return false;
return true;
case YamlScalarNode subScalar:
return MapEntityProperty(subScalar, path);
default:
throw new ArgumentException($"Unrecognized YAML node type: {node.GetType()} at {path}");
}
}
}
}

View File

@@ -0,0 +1,25 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
namespace Content.Tools
{
public class TypeTagPreserver : IEmitter
{
public TypeTagPreserver(IEmitter emitter)
{
Emitter = emitter;
}
private IEmitter Emitter { get; }
public void Emit(ParsingEvent @event)
{
if (@event is MappingStart mapping)
{
@event = new MappingStart(mapping.Anchor, mapping.Tag, false, mapping.Style, mapping.Start, mapping.End);
}
Emitter.Emit(@event);
}
}
}

199
Content.Tools/YamlTools.cs Normal file
View File

@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Content.Tools
{
public static class YamlTools
{
public static YamlNode CopyYamlNodes(YamlNode other)
{
switch (other)
{
case YamlSequenceNode subSequence:
YamlSequenceNode tmp1 = new YamlSequenceNode();
MergeYamlSequences((YamlSequenceNode) tmp1, new YamlSequenceNode(), (YamlSequenceNode) other, "");
return tmp1;
case YamlMappingNode subMapping:
YamlMappingNode tmp2 = new YamlMappingNode();
MergeYamlMappings((YamlMappingNode) tmp2, new YamlMappingNode(), (YamlMappingNode) other, "", new string[] {});
return tmp2;
case YamlScalarNode subScalar:
YamlScalarNode tmp3 = new YamlScalarNode();
CopyYamlScalar(tmp3, subScalar);
return tmp3;
default:
throw new ArgumentException($"Unrecognized YAML node type for copy: {other.GetType()}", nameof(other));
}
}
public static bool TriTypeMatch(YamlNode ours, YamlNode based, YamlNode other)
{
var refType = other.GetType();
if (refType != based.GetType())
return false;
if (refType != ours.GetType())
return false;
return true;
}
public static void MergeYamlNodes(YamlNode ours, YamlNode based, YamlNode other, string path)
{
if (!TriTypeMatch(ours, based, other))
throw new ArgumentException($"Node type mismatch at {path}");
switch (other)
{
case YamlSequenceNode subSequence:
MergeYamlSequences((YamlSequenceNode) ours, (YamlSequenceNode) based, (YamlSequenceNode) other, path);
break;
case YamlMappingNode subMapping:
MergeYamlMappings((YamlMappingNode) ours, (YamlMappingNode) based, (YamlMappingNode) other, path, new string[] {});
break;
case YamlScalarNode subScalar:
// Console.WriteLine(path + " - " + ours + " || " + based + " || " + other);
var scalarA = (YamlScalarNode) ours;
var scalarB = (YamlScalarNode) based;
var scalarC = (YamlScalarNode) other;
var aeb = (scalarA.Value == scalarB.Value);
var cneb = (scalarC.Value != scalarB.Value);
if (aeb || cneb)
CopyYamlScalar(scalarA, scalarC);
// Console.WriteLine(path + " . " + ours + " || " + based + " || " + other);
break;
default:
throw new ArgumentException($"Unrecognized YAML node type at {path}: {other.GetType()}", nameof(other));
}
}
public static void MergeYamlSequences(YamlSequenceNode ours, YamlSequenceNode based, YamlSequenceNode other, string path)
{
if ((ours.Children.Count == based.Children.Count) && (other.Children.Count == ours.Children.Count))
{
// this is terrible and doesn't do proper rearrange detection
// but it looks as if vectors might be arrays
// so rearrange detection might break more stuff...
// nope, they aren't, but still good to have
for (var i = 0; i < ours.Children.Count; i++)
MergeYamlNodes(ours.Children[i], based.Children[i], other.Children[i], path + "/" + i);
return;
}
// for now, just copy other -> ours
// I am aware this is terrible
ours.Children.Clear();
foreach (var c in other.Children)
ours.Add(CopyYamlNodes(c));
}
public static void MergeYamlMappings(YamlMappingNode ours, YamlMappingNode based, YamlMappingNode other, string path, string[] ignoreThese)
{
// Deletions/modifications
foreach (var kvp in based)
{
if (ignoreThese.Contains(kvp.Key.ToString()))
continue;
var localPath = path + "/" + kvp.Key.ToString();
var deletedByOurs = !ours.Children.ContainsKey(kvp.Key);
var deletedByOther = !other.Children.ContainsKey(kvp.Key);
if (deletedByOther && (!deletedByOurs))
{
// Delete
ours.Children.Remove(kvp.Key);
}
else if (!(deletedByOurs || deletedByOther))
{
// Modify
var a = ours[kvp.Key];
var b = kvp.Value; // based[kvp.Key]
var c = other[kvp.Key];
if (!TriTypeMatch(a, b, c))
{
Console.WriteLine("Warning: Type mismatch (defaulting to value C) at " + localPath);
ours.Children[kvp.Key] = CopyYamlNodes(c);
}
else
{
MergeYamlNodes(a, b, c, localPath);
}
}
}
// Additions
foreach (var kvp in other)
{
if (ignoreThese.Contains(kvp.Key.ToString()))
continue;
var localPath = path + "/" + kvp.Key.ToString();
if (!based.Children.ContainsKey(kvp.Key))
{
if (ours.Children.ContainsKey(kvp.Key))
{
// Both sides added the same key. Try to merge.
var a = ours[kvp.Key];
var b = based[kvp.Key];
var c = kvp.Value; // other[kvp.Key]
if (!TriTypeMatch(a, b, c))
{
Console.WriteLine("Warning: Type mismatch (defaulting to value C) at " + localPath);
ours.Children[kvp.Key] = CopyYamlNodes(c);
}
else
{
MergeYamlNodes(a, b, c, localPath);
}
}
else
{
// Well that was easy
ours.Children[kvp.Key] = CopyYamlNodes(kvp.Value);
}
}
}
}
// NOTE: This is a heuristic ONLY! And is also not used at the moment because sequence matching isn't in place.
// It could also be massively improved.
public static float YamlNodesHeuristic(YamlNode a, YamlNode b)
{
if (a.GetType() != b.GetType())
return 0.0f;
switch (a)
{
case YamlSequenceNode x:
return YamlSequencesHeuristic((YamlSequenceNode) a, (YamlSequenceNode) b);
case YamlMappingNode y:
return YamlMappingsHeuristic((YamlMappingNode) a, (YamlMappingNode) b);
case YamlScalarNode z:
return (((YamlScalarNode) a).Value == ((YamlScalarNode) b).Value) ? 1.0f : 0.0f;
default:
throw new ArgumentException($"Unrecognized YAML node type: {a.GetType()}", nameof(a));
}
}
public static float YamlSequencesHeuristic(YamlSequenceNode a, YamlSequenceNode b)
{
if (a.Children.Count != b.Children.Count)
return 0.0f;
if (a.Children.Count == 0)
return 1.0f;
var total = 0.0f;
for (var i = 0; i < a.Children.Count; i++)
total += YamlNodesHeuristic(a.Children[i], b.Children[i]);
return total / a.Children.Count;
}
public static float YamlMappingsHeuristic(YamlMappingNode a, YamlMappingNode b)
{
return (a == b) ? 1.0f : 0.0f;
}
public static void CopyYamlScalar(YamlScalarNode dst, YamlScalarNode src)
{
dst.Value = src.Value;
dst.Style = src.Style;
}
}
}

95
Content.Tools/test/0A.yml Normal file
View File

@@ -0,0 +1,95 @@
meta:
format: 2
name: DemoStation
author: Space-Wizards
postmapinit: false
tilemap:
0: space
7: floor_dark
8: floor_elevator_shaft
9: floor_freezer
10: floor_gold
11: floor_green_circuit
12: floor_hydro
13: floor_lino
14: floor_mono
15: floor_reinforced
16: floor_rock_vault
17: floor_showroom
18: floor_snow
19: floor_steel
20: floor_steel_dirty
21: floor_techmaint
22: floor_white
23: floor_wood
24: lattice
25: plating
26: underplating
grids:
- settings:
chunksize: 16
tilesize: 1
snapsize: 1
chunks:
- ind: "-2,-2"
comment: "Ew in A | FQ in B | Fg in C | Fg in Out - SHOULD CAUSE CONFLICT WARNING"
tiles: EwAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABcAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAaAAAAGgAAABMAAAATAAAAEwAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAaAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAVAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAGgAAABMAAAATAAAAEwAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGQAAABUAAAAaAAAAFQAAAA==
- ind: "-1,-1"
tiles: EwAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAWAAAAFgAAABYAAAAWAAAAEwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAAaAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAGgAAABMAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAAA==
- ind: "-1,0"
tiles: EwAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAWAAAAFgAAABYAAAAWAAAAEwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAAaAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAGgAAABMAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAAA==
- ind: "0,0"
tiles: FwAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABcAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAaAAAAGgAAABMAAAATAAAAEwAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAaAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAVAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAGgAAABMAAAATAAAAEwAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGQAAABUAAAAaAAAAFQAAAA==
entities:
- uid: 0
type: FakeTestDummy
components:
- parent: 855
pos: -5.5,-1.5
rot: -1.5707963267948966 rad
type: Transform
- type: ContainerContainer
typeChange:
- 0
- 1
- 2
example:
- 0
- 1
- 2
- 3
- 855
- "contributionA"
- uid: 1
type: FakeTestDummy
components:
- parent: 0
pos: -15.5,-14.5
rot: -1.5707963267948966 rad
type: Transform
- uid: 2
type: FakeTestDummy
components:
- parent: 1
pos: -15.5,-14.5
rot: -1.5707963267948966 rad
type: Transform
- type: ThereShouldBeTwoOfTheseInOutputOfMergeTestAAA
- uid: 3
type: AnnoyingPlaceholderOnPurpose
components:
- parent: 0
type: Transform
- uid: 855
components:
- name: Saltern Station
type: MetaData
- parent: null
type: Transform
- index: 0
type: MapGrid
- shapes:
- !type:PhysShapeGrid
grid: 0
type: Physics

86
Content.Tools/test/0B.yml Normal file
View File

@@ -0,0 +1,86 @@
meta:
format: 2
name: DemoStation
author: Space-Wizards
postmapinit: false
tilemap:
0: space
7: floor_dark
8: floor_elevator_shaft
9: floor_freezer
10: floor_gold
11: floor_green_circuit
12: floor_hydro
13: floor_lino
14: floor_mono
15: floor_reinforced
16: floor_rock_vault
17: floor_showroom
18: floor_snow
19: floor_steel
20: floor_steel_dirty
21: floor_techmaint
22: floor_white
23: floor_wood
24: lattice
25: plating
26: underplating
grids:
- settings:
chunksize: 16
tilesize: 1
snapsize: 1
chunks:
- ind: "-2,-2"
comment: "Ew in A | FQ in B | Fg in C | Fg in Out - SHOULD CAUSE CONFLICT WARNING"
tiles: FQAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABcAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAaAAAAGgAAABMAAAATAAAAEwAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAaAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAVAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAGgAAABMAAAATAAAAEwAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGQAAABUAAAAaAAAAFQAAAA==
- ind: "-1,-1"
tiles: FQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAVAAAAFQAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAGgAAABUAAAAaAAAAGgAAABoAAAAZAAAAFQAAABUAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFQAAABUAAAAVAAAAFQAAABUAAAAaAAAAGQAAABUAAAAVAAAAGgAAABYAAAAWAAAAFgAAABoAAAAaAAAAGgAAABUAAAAVAAAAFQAAABUAAAAVAAAAGgAAABkAAAAVAAAAFQAAABoAAAAaAAAAFgAAABoAAAAaAAAAGQAAABkAAAAVAAAAFQAAABUAAAAVAAAAFQAAABoAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAaAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABoAAAAaAAAAGgAAABUAAAAaAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAZAAAAFQAAAA0AAAANAAAADQAAAA0AAAANAAAAGgAAABUAAAAVAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAANAAAADQAAAA0AAAANAAAADQAAABoAAAAVAAAAFQAAABkAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAADQAAAA0AAAANAAAADQAAAA0AAAAaAAAAGgAAABoAAAAZAAAAEwAAAAkAAAAJAAAACQAAAAkAAAAJAAAAGgAAAA0AAAANAAAADQAAAA0AAAANAAAAFwAAABcAAAAXAAAAGgAAABoAAAAJAAAACQAAAAkAAAAJAAAACQAAABoAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAaAAAAGgAAABMAAAAaAAAAGgAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAGgAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAAA==
- ind: "-1,0"
tiles: EwAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAWAAAAFgAAABYAAAAWAAAAEwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAAaAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAGgAAABMAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAAA==
- ind: "0,-1"
tiles: FgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFgAAABYAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABoAAAAWAAAAFgAAABYAAAAWAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAWAAAAGgAAABoAAAAaAAAAFgAAABYAAAAWAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFQAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFQAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABYAAAAaAAAAGgAAABYAAAAWAAAAGgAAABoAAAAWAAAAFgAAABoAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAXAAAAGgAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFwAAABoAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABoAAAAaAAAAFgAAABcAAAATAAAAEwAAABMAAAATAAAAEwAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAXAAAAEwAAABMAAAATAAAAEwAAABMAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFwAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABoAAAAWAAAAFgAAAA==
- ind: "0,0"
tiles: FwAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABcAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAaAAAAGgAAABMAAAATAAAAEwAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAaAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAVAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAGgAAABMAAAATAAAAEwAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGQAAABUAAAAaAAAAFQAAAA==
entities:
- uid: 0
type: FakeTestDummy
components:
- parent: 855
pos: -15.5,-11.5
rot: -1.5707963267948966 rad
type: Transform
- type: ContainerContainer
typeChange:
- 0
- 1
example:
- 0
- 1
- 3
- 855
- uid: 1
type: FakeTestDummy
components:
- parent: 0
pos: -15.5,-14.5
rot: -1.5707963267948966 rad
type: Transform
- uid: 3
type: AnnoyingPlaceholderOnPurpose
components:
- parent: 0
type: Transform
- uid: 855
components:
- name: Saltern Station
type: MetaData
- parent: null
type: Transform
- index: 0
type: MapGrid
- shapes:
- !type:PhysShapeGrid
grid: 0
type: Physics

105
Content.Tools/test/0C.yml Normal file
View File

@@ -0,0 +1,105 @@
meta:
format: 2
name: DemoStation
author: Space-Wizards
postmapinit: false
tilemap:
0: space
1: floor_asteroid_coarse_sand0
2: floor_asteroid_coarse_sand1
3: floor_asteroid_coarse_sand2
4: floor_asteroid_coarse_sand_dug
5: floor_asteroid_sand
6: floor_asteroid_tile
7: floor_dark
8: floor_elevator_shaft
9: floor_freezer
10: floor_gold
11: floor_green_circuit
12: floor_hydro
13: floor_lino
14: floor_mono
15: floor_reinforced
16: floor_rock_vault
17: floor_showroom
18: floor_snow
19: floor_steel
20: floor_steel_dirty
21: floor_techmaint
22: floor_white
23: floor_wood
24: lattice
25: plating
26: underplating
grids:
- settings:
chunksize: 16
tilesize: 1
snapsize: 1
chunks:
- ind: "-2,-2"
comment: "Ew in A | FQ in B | Fg in C | Fg in Out - SHOULD CAUSE CONFLICT WARNING"
tiles: FgAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABcAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAaAAAAGgAAABMAAAATAAAAEwAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAaAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAVAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAGgAAABMAAAATAAAAEwAAABUAAAAZAAAAFQAAABUAAAAVAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGQAAABUAAAAaAAAAFQAAAA==
- ind: "-1,-1"
tiles: FQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAVAAAAFQAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAGgAAABUAAAAaAAAAGgAAABoAAAAZAAAAFQAAABUAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFQAAABUAAAAVAAAAFQAAABUAAAAaAAAAGQAAABUAAAAVAAAAGgAAABYAAAAWAAAAFgAAABoAAAAaAAAAGgAAABUAAAAVAAAAFQAAABUAAAAVAAAAGgAAABkAAAAVAAAAFQAAABoAAAAaAAAAFgAAABoAAAAaAAAAGQAAABkAAAAVAAAAFQAAABUAAAAVAAAAFQAAABoAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAaAAAAGQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABoAAAAaAAAAGgAAABUAAAAaAAAAGgAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAZAAAAFQAAAA0AAAANAAAADQAAAA0AAAANAAAAGgAAABUAAAAVAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAANAAAADQAAAA0AAAANAAAADQAAABoAAAAVAAAAFQAAABkAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAADQAAAA0AAAANAAAADQAAAA0AAAAaAAAAGgAAABoAAAAZAAAAEwAAAAkAAAAJAAAACQAAAAkAAAAJAAAAGgAAAA0AAAANAAAADQAAAA0AAAANAAAAFwAAABcAAAAXAAAAGgAAABoAAAAJAAAACQAAAAkAAAAJAAAACQAAABoAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAaAAAAGgAAABMAAAAaAAAAGgAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAGgAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAAA==
- ind: "-1,0"
tiles: EwAAABYAAAAWAAAAFgAAABYAAAATAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABoAAAAWAAAAFgAAABYAAAAWAAAAEwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAAaAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAGgAAABMAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABoAAAATAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAEwAAABoAAAAaAAAAEwAAABMAAAAaAAAAGgAAABMAAAAaAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAZAAAAFQAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAEwAAABMAAAATAAAAEwAAABoAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAaAAAAGgAAAA==
- ind: "0,-1"
tiles: FgAAABMAAAATAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAATAAAAEwAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFgAAABYAAAAaAAAAEwAAABMAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABoAAAAWAAAAFgAAABYAAAAWAAAAGgAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABoAAAAWAAAAGgAAABoAAAAaAAAAFgAAABYAAAAWAAAAGgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAGgAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGQAAABoAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABkAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAZAAAAFQAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFQAAABoAAAATAAAAEwAAABMAAAAaAAAAGgAAABYAAAAaAAAAGgAAABYAAAAWAAAAGgAAABoAAAAWAAAAFgAAABoAAAAaAAAAEwAAABMAAAATAAAAGgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAXAAAAGgAAABMAAAATAAAAEwAAABoAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFwAAABoAAAATAAAAEwAAABMAAAAaAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABoAAAAaAAAAFgAAABcAAAATAAAAEwAAABMAAAATAAAAEwAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAaAAAAFgAAABYAAAAXAAAAEwAAABMAAAATAAAAEwAAABMAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGgAAABYAAAAWAAAAFwAAABMAAAATAAAAEwAAABMAAAATAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABoAAAAWAAAAFgAAAA==
entities:
- uid: 0
type: FakeTestDummy
components:
- parent: 855
pos: -15.5,-11.5
rot: -1.5707963267948966 rad
type: Transform
- type: ContainerContainer
typeChange:
something: true
example:
- 0
- 1
- 2
- 3
- 4
- 855
- "contributionC"
- uid: 1
type: FakeTestDummy
components:
- parent: 0
pos: -5.5,-4.5
rot: -1.5707963267948966 rad
type: Transform
- uid: 2
type: FakeTestDummy
components:
- parent: 1
pos: -15.5,-14.5
rot: -1.5707963267948966 rad
type: Transform
- type: ThereShouldBeTwoOfTheseInOutputOfMergeTestBBB
- uid: 3
type: AnnoyingPlaceholderOnPurpose
components:
- parent: 0
type: Transform
- uid: 4
type: ThisShouldHaveParentChangedToFollowUID
components:
- parent: 2
type: Transform
- uid: 855
components:
- name: Saltern Station
type: MetaData
- parent: null
type: Transform
- index: 0
type: MapGrid
- shapes:
- !type:PhysShapeGrid
grid: 0
type: Physics

3
Content.Tools/test/run.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
cp 0A.yml out.yml
../bin/Debug/net5.0/Content.Tools out.yml 0B.yml 0C.yml

View File

@@ -1,17 +1,21 @@
- Name: Jigglypuff - Name: clyf
Tier: Syndicate Agent Tier: Nuclear Operative
- Name: Kerb 755
Tier: Revolutionary
- Name: queednyeb
Tier: Revolutionary
- Name: Robin Rottstock
Tier: Revolutionary
- Name: Third
Tier: Revolutionary
- Name: Altana - Name: Altana
Tier: Revolutionary Tier: Revolutionary
- Name: showgun117
Tier: Syndicate Agent
- Name: Wolfiten - Name: Wolfiten
Tier: Revolutionary Tier: Revolutionary
- Name: Durp - Name: Durp
Tier: Revolutionary Tier: Revolutionary
- Name: Joshington Awesomahee - Name: Joshington Awesomahee
Tier: Revolutionary Tier: Revolutionary
- Name: Diklyquill
Tier: Revolutionary
- Name: Eric VW - Name: Eric VW
Tier: Revolutionary Tier: Revolutionary
- Name: Evan Armstrong - Name: Evan Armstrong
@@ -20,16 +24,14 @@
Tier: Revolutionary Tier: Revolutionary
- Name: Christopher Marmentini - Name: Christopher Marmentini
Tier: Nuclear Operative Tier: Nuclear Operative
- Name: Curtis Pearson
Tier: Revolutionary
- Name: Mono - Name: Mono
Tier: Revolutionary Tier: Revolutionary
- Name: Shootmister
Tier: Syndicate Agent
- Name: MonkeePawl - Name: MonkeePawl
Tier: Nuclear Operative Tier: Nuclear Operative
- Name: Star Lord - Name: Star Lord
Tier: Syndicate Agent Tier: Syndicate Agent
- Name: Ethan Keller
Tier: Revolutionary
- Name: creadth - Name: creadth
Tier: Nuclear Operative Tier: Nuclear Operative
- Name: Robert Reed - Name: Robert Reed
@@ -52,8 +54,6 @@
Tier: Revolutionary Tier: Revolutionary
- Name: arthropods - Name: arthropods
Tier: Revolutionary Tier: Revolutionary
- Name: Nicholas Perry
Tier: Syndicate Agent
- Name: Mathieu Déom - Name: Mathieu Déom
Tier: Syndicate Agent Tier: Syndicate Agent
- Name: merklaw - Name: merklaw
@@ -64,10 +64,8 @@
Tier: Nuclear Operative Tier: Nuclear Operative
- Name: ThatGuyGW - Name: ThatGuyGW
Tier: Nuclear Operative Tier: Nuclear Operative
- Name: MetalClone
Tier: Nuclear Operative
- Name: dean - Name: dean
Tier: Nuclear Operative Tier: Syndicate Agent
- Name: Await Future - Name: Await Future
Tier: Syndicate Agent Tier: Syndicate Agent
- Name: Kyle Hipke - Name: Kyle Hipke

View File

@@ -0,0 +1,54 @@
meta:
format: 2
name: DemoStation
author: Space-Wizards
postmapinit: false
tilemap:
0: space
1: floor_asteroid_coarse_sand0
2: floor_asteroid_coarse_sand1
3: floor_asteroid_coarse_sand2
4: floor_asteroid_coarse_sand_dug
5: floor_asteroid_sand
6: floor_asteroid_tile
7: floor_dark
8: floor_elevator_shaft
9: floor_freezer
10: floor_gold
11: floor_green_circuit
12: floor_hydro
13: floor_lino
14: floor_mono
15: floor_reinforced
16: floor_rock_vault
17: floor_showroom
18: floor_snow
19: floor_steel
20: floor_steel_dirty
21: floor_techmaint
22: floor_white
23: floor_wood
24: lattice
25: plating
26: underplating
grids:
- settings:
chunksize: 16
tilesize: 1
snapsize: 1
chunks:
- ind: "-1,-1"
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAA==
entities:
- uid: 0
components:
- parent: null
pos: 0,0
type: Transform
- index: 0
type: MapGrid
- shapes:
- !type:PhysShapeGrid
grid: 0
type: Physics
...

View File

@@ -20002,7 +20002,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -12.5,-5 pos: -12.5,-5
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -21324,7 +21324,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -6.5,-26 pos: -6.5,-26
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -21430,6 +21430,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: -7,-23.5 pos: -7,-23.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -22573,7 +22574,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -29.5,15 pos: -29.5,15
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -22602,7 +22603,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -34,-0.5 pos: -34,-0.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -22616,6 +22617,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: -18,-4.5 pos: -18,-4.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -22700,7 +22702,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -29.5,-9 pos: -29.5,-9
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -22883,6 +22885,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: -19,9.5 pos: -19,9.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -22912,7 +22915,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -14.5,-16 pos: -14.5,-16
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -22926,6 +22929,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: -7,-12.5 pos: -7,-12.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -22940,6 +22944,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: -11,-10.5 pos: -11,-10.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -23112,7 +23117,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -19,16.5 pos: -19,16.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23127,7 +23132,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -14.5,26 pos: -14.5,26
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23142,7 +23147,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -19,22.5 pos: -19,22.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23157,7 +23162,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -18,9.5 pos: -18,9.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23172,7 +23177,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -0.5,-12 pos: -0.5,-12
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23386,7 +23391,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -1,8.5 pos: -1,8.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23401,7 +23406,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -4,8.5 pos: -4,8.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23528,7 +23533,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -7.5,26 pos: -7.5,26
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23550,7 +23555,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -4,17.5 pos: -4,17.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -23783,6 +23788,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 17,15.5 pos: 17,15.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -23797,6 +23803,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 14,16.5 pos: 14,16.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -23812,7 +23819,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 8.5,15 pos: 8.5,15
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24274,7 +24281,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 30.5,-6 pos: 30.5,-6
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24289,7 +24296,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 25.5,-1 pos: 25.5,-1
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24304,7 +24311,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 9.5,-18 pos: 9.5,-18
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24347,7 +24354,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 12.5,-19 pos: 12.5,-19
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24369,7 +24376,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 25.5,-19 pos: 25.5,-19
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24383,6 +24390,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 28,-15.5 pos: 28,-15.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -24432,6 +24440,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 28,-10.5 pos: 28,-10.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -24453,6 +24462,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 24,0.5 pos: 24,0.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -24468,7 +24478,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 26,-4.5 pos: 26,-4.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24483,7 +24493,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 35.5,-6 pos: 35.5,-6
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24498,7 +24508,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 38.5,-3 pos: 38.5,-3
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24513,7 +24523,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 26,11.5 pos: 26,11.5
rot: 3.141592653589793 rad rot: 0 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -24709,6 +24719,7 @@ entities:
type: PoweredSmallLight type: PoweredSmallLight
components: components:
- parent: 853 - parent: 853
rot: 3.141592653589793 rad
pos: 44,-1.5 pos: 44,-1.5
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
@@ -26432,7 +26443,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -12.5,-6 pos: -12.5,-6
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -26447,7 +26458,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -25.5,-10 pos: -25.5,-10
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- color: '#FFFFFFFF' - color: '#FFFFFFFF'
type: PointLight type: PointLight
@@ -42390,7 +42401,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: 22.528679,-9.003884 pos: 22.528679,-9.003884
rot: 1.5707963267948966 rad rot: -1.5707963267948966 rad
type: Transform type: Transform
- powerLoad: 0 - powerLoad: 0
type: PowerReceiver type: PowerReceiver
@@ -42772,7 +42783,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -1.4929452,19.970068 pos: -1.4929452,19.970068
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- powerLoad: 0 - powerLoad: 0
type: PowerReceiver type: PowerReceiver
@@ -42799,7 +42810,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -15.494916,15.968084 pos: -15.494916,15.968084
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- powerLoad: 0 - powerLoad: 0
type: PowerReceiver type: PowerReceiver
@@ -44360,7 +44371,7 @@ entities:
components: components:
- parent: 853 - parent: 853
pos: -22.5,-16 pos: -22.5,-16
rot: -1.5707963267948966 rad rot: 1.5707963267948966 rad
type: Transform type: Transform
- powerLoad: 0 - powerLoad: 0
type: PowerReceiver type: PowerReceiver

View File

@@ -89,9 +89,10 @@
- type: alert - type: alert
alertType: Handcuffed alertType: Handcuffed
onClick: !type:RemoveCuffs { }
icon: /Textures/Interface/Alerts/Handcuffed/Handcuffed.png icon: /Textures/Interface/Alerts/Handcuffed/Handcuffed.png
name: "[color=yellow]Handcuffed[/color]" name: "[color=yellow]Handcuffed[/color]"
description: "You're [color=yellow]handcuffed[/color] and can't use your hands. If anyone drags you, you won't be able to resist.." description: "You're [color=yellow]handcuffed[/color] and can't use your hands. If anyone drags you, you won't be able to resist."
- type: alert - type: alert
alertType: Buckled alertType: Buckled

View File

@@ -60,10 +60,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
500: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 500
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: IconSmooth - type: IconSmooth
key: walls key: walls
mode: NoSprite mode: NoSprite

View File

@@ -25,9 +25,11 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
75: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 75
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
placement: placement:
mode: SnapgridCenter mode: SnapgridCenter

View File

@@ -23,17 +23,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
30: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 30
sound: /Audio/Effects/woodhit.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/woodhit.ogg
WoodPlank: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 WoodPlank:
- !type:DoActsBehavior min: 1
acts: ["Destruction"] max: 1
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Occluder - type: Occluder
sizeX: 32 sizeX: 32

View File

@@ -24,10 +24,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
50: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 50
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: UserInterface - type: UserInterface
interfaces: interfaces:
- key: enum.InstrumentUiKey.Key - key: enum.InstrumentUiKey.Key

View File

@@ -15,10 +15,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: ShuttleController - type: ShuttleController
- type: Strap - type: Strap
position: Stand position: Stand

View File

@@ -34,10 +34,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
50: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 50
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: entity - type: entity
name: chair name: chair

View File

@@ -31,17 +31,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
30: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 30
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: entity - type: entity
id: Shelf id: Shelf
@@ -76,14 +78,16 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
30: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 30
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]

View File

@@ -39,17 +39,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
15: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 15
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: entity - type: entity
id: TableFrame id: TableFrame
@@ -65,17 +67,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
1: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 1
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: TableFrame node: TableFrame
@@ -94,17 +98,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
1: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 1
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: entity - type: entity
id: TableMetal id: TableMetal
@@ -120,17 +126,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
15: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 15
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: MetalTable node: MetalTable
@@ -149,17 +157,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
75: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 75
sound: /Audio/Effects/metalbreak.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/metalbreak.ogg
SteelSheet1: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 SteelSheet1:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: ReinforcedTable node: ReinforcedTable
@@ -178,17 +188,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
5: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 5
sound: /Audio/Effects/glass_break2.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/glass_break2.ogg
ShardGlass: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 ShardGlass:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: GlassTable node: GlassTable
@@ -207,17 +219,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
20: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 20
sound: /Audio/Effects/glass_break2.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/glass_break2.ogg
ShardGlass: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 ShardGlass:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: RGlassTable node: RGlassTable
@@ -236,17 +250,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
15: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 15
sound: /Audio/Effects/woodhit.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/woodhit.ogg
WoodPlank: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 WoodPlank:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: WoodTable node: WoodTable
@@ -265,17 +281,19 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
15: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 15
sound: /Audio/Effects/woodhit.ogg behaviors:
- !type:SpawnEntitiesBehavior - !type:PlaySoundBehavior
spawn: sound: /Audio/Effects/woodhit.ogg
WoodPlank: - !type:SpawnEntitiesBehavior
min: 1 spawn:
max: 1 WoodPlank:
- !type:DoActsBehavior min: 1
acts: [ "Destruction" ] max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Construction - type: Construction
graph: Tables graph: Tables
node: PokerTable node: PokerTable
@@ -294,12 +312,14 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
50: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:PlaySoundBehavior damage: 50
sound: /Audio/Effects/picaxe2.ogg behaviors:
- !type:DoActsBehavior - !type:PlaySoundBehavior
acts: [ "Destruction" ] sound: /Audio/Effects/picaxe2.ogg
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: entity - type: entity
id: TableDebug id: TableDebug
@@ -315,7 +335,9 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
1: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 1
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]

View File

@@ -19,10 +19,12 @@
- type: Damageable - type: Damageable
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: GasCanisterPort - type: GasCanisterPort
- type: entity - type: entity

View File

@@ -13,10 +13,12 @@
- type: Damageable - type: Damageable
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: GasCanister - type: GasCanister
- type: Anchorable - type: Anchorable
- type: Pullable - type: Pullable

View File

@@ -13,10 +13,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Sprite - type: Sprite
netsync: false netsync: false
sprite: Constructible/Atmos/gasfilter.rsi sprite: Constructible/Atmos/gasfilter.rsi

View File

@@ -23,8 +23,10 @@
- type: Damageable - type: Damageable
- type: Destructible - type: Destructible
thresholds: thresholds:
50: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 50
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: KitchenSpike - type: KitchenSpike

View File

@@ -1,4 +1,4 @@
- type: entity - type: entity
abstract: true abstract: true
id: PipeBase id: PipeBase
name: Pipe name: Pipe
@@ -14,10 +14,12 @@
- type: Damageable - type: Damageable
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Sprite - type: Sprite
netsync: false netsync: false
- type: Appearance - type: Appearance

View File

@@ -13,10 +13,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Sprite - type: Sprite
netsync: false netsync: false
sprite: Constructible/Atmos/pump.rsi sprite: Constructible/Atmos/pump.rsi
@@ -44,4 +46,3 @@
- type: PressurePump - type: PressurePump
inletDirection: West inletDirection: West
outletDirection: East outletDirection: East

View File

@@ -13,10 +13,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Sprite - type: Sprite
netsync: false netsync: false
sprite: Constructible/Atmos/scrubber.rsi sprite: Constructible/Atmos/scrubber.rsi

View File

@@ -13,10 +13,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Sprite - type: Sprite
netsync: false netsync: false
sprite: Constructible/Atmos/vent.rsi sprite: Constructible/Atmos/vent.rsi

View File

@@ -98,10 +98,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Destruction"] behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- type: BreakableConstruction - type: BreakableConstruction
node: monitorBroken node: monitorBroken
- type: Sprite - type: Sprite

View File

@@ -31,10 +31,12 @@
resistances: metallicResistances resistances: metallicResistances
- type: Destructible - type: Destructible
thresholds: thresholds:
100: - trigger:
behaviors: !type:TotalDamageTrigger
- !type:DoActsBehavior damage: 100
acts: ["Breakage"] behaviors:
- !type:DoActsBehavior
acts: ["Breakage"]
- type: Anchorable - type: Anchorable
- type: entity - type: entity

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