diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 945465607f..83bca6f97b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,6 @@ { "recommendations": [ - "ms-vscode.csharp", + "ms-dotnettools.csharp", "editorconfig.editorconfig" ] } diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs index 741a4efb33..7eb128092d 100644 --- a/Content.Benchmarks/Program.cs +++ b/Content.Benchmarks/Program.cs @@ -6,7 +6,7 @@ namespace Content.Benchmarks { public static void Main(string[] args) { - BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/Content.Benchmarks/StereoToMonoBenchmark.cs b/Content.Benchmarks/StereoToMonoBenchmark.cs new file mode 100644 index 0000000000..89dec8a3dd --- /dev/null +++ b/Content.Benchmarks/StereoToMonoBenchmark.cs @@ -0,0 +1,70 @@ +using System.Runtime.Intrinsics.X86; +using BenchmarkDotNet.Attributes; + +namespace Content.Benchmarks +{ + public class StereoToMonoBenchmark + { + [Params(128, 256, 512)] + public int N { get; set; } + + private short[] _input; + private short[] _output; + + [GlobalSetup] + public void Setup() + { + _input = new short[N * 2]; + _output = new short[N]; + } + + [Benchmark] + public void BenchSimple() + { + var l = N; + for (var j = 0; j < l; j++) + { + var k = j + l; + _output[j] = (short) ((_input[k] + _input[j]) / 2); + } + } + + [Benchmark] + public unsafe void BenchSse() + { + var l = N; + fixed (short* iPtr = _input) + fixed (short* oPtr = _output) + { + for (var j = 0; j < l; j += 8) + { + var k = j + l; + + var jV = Sse2.ShiftRightArithmetic(Sse2.LoadVector128(iPtr + j), 1); + var kV = Sse2.ShiftRightArithmetic(Sse2.LoadVector128(iPtr + k), 1); + + Sse2.Store(j + oPtr, Sse2.Add(jV, kV)); + } + } + } + + [Benchmark] + public unsafe void BenchAvx2() + { + var l = N; + fixed (short* iPtr = _input) + fixed (short* oPtr = _output) + { + for (var j = 0; j < l; j += 16) + { + var k = j + l; + + var jV = Avx2.ShiftRightArithmetic(Avx.LoadVector256(iPtr + j), 1); + var kV = Avx2.ShiftRightArithmetic(Avx.LoadVector256(iPtr + k), 1); + + Avx.Store(j + oPtr, Avx2.Add(jV, kV)); + } + } + } + } +} diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index 8a0fcdeabd..61d1d4ae03 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -19,6 +19,12 @@ namespace Content.Client.Chat { internal sealed class ChatManager : IChatManager { + private struct SpeechBubbleData + { + public string Message; + public SpeechBubble.SpeechType Type; + } + /// /// The max amount of chars allowed to fit in a single speech bubble. /// @@ -117,7 +123,7 @@ namespace Content.Client.Chat var msg = queueData.MessageQueue.Dequeue(); - queueData.TimeLeft += BubbleDelayBase + msg.Length * BubbleDelayFactor; + queueData.TimeLeft += BubbleDelayBase + msg.Message.Length * BubbleDelayFactor; // We keep the queue around while it has 0 items. This allows us to keep the timer. // When the timer hits 0 and there's no messages left, THEN we can clear it up. @@ -211,19 +217,19 @@ namespace Content.Client.Chat case OOCAlias: { var conInput = text.Substring(1); - _console.ProcessCommand($"ooc \"{conInput}\""); + _console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\""); break; } case MeAlias: { var conInput = text.Substring(1); - _console.ProcessCommand($"me \"{conInput}\""); + _console.ProcessCommand($"me \"{CommandParsing.Escape(conInput)}\""); break; } default: { var conInput = _currentChatBox.DefaultChatFormat != null - ? string.Format(_currentChatBox.DefaultChatFormat, text) + ? string.Format(_currentChatBox.DefaultChatFormat, CommandParsing.Escape(text)) : text; _console.ProcessCommand(conInput); break; @@ -291,13 +297,23 @@ namespace Content.Client.Chat WriteChatMessage(storedMessage); // Local messages that have an entity attached get a speech bubble. - if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Dead) && msg.SenderEntity != default) + if (msg.SenderEntity == default) + return; + + switch (msg.Channel) { - AddSpeechBubble(msg); + case ChatChannel.Local: + case ChatChannel.Dead: + AddSpeechBubble(msg, SpeechBubble.SpeechType.Say); + break; + + case ChatChannel.Emotes: + AddSpeechBubble(msg, SpeechBubble.SpeechType.Emote); + break; } } - private void AddSpeechBubble(MsgChatMessage msg) + private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType) { if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity)) { @@ -305,8 +321,18 @@ namespace Content.Client.Chat return; } + var messages = SplitMessage(msg.Message); + + foreach (var message in messages) + { + EnqueueSpeechBubble(entity, message, speechType); + } + } + + private List SplitMessage(string msg) + { // Split message into words separated by spaces. - var words = msg.Message.Split(' '); + var words = msg.Split(' '); var messages = new List(); var currentBuffer = new List(); @@ -346,13 +372,10 @@ namespace Content.Client.Chat messages.Add(string.Join(" ", currentBuffer)); } - foreach (var message in messages) - { - EnqueueSpeechBubble(entity, message); - } + return messages; } - private void EnqueueSpeechBubble(IEntity entity, string contents) + private void EnqueueSpeechBubble(IEntity entity, string contents, SpeechBubble.SpeechType speechType) { if (!_queuedSpeechBubbles.TryGetValue(entity.Uid, out var queueData)) { @@ -360,12 +383,16 @@ namespace Content.Client.Chat _queuedSpeechBubbles.Add(entity.Uid, queueData); } - queueData.MessageQueue.Enqueue(contents); + queueData.MessageQueue.Enqueue(new SpeechBubbleData + { + Message = contents, + Type = speechType, + }); } - private void CreateSpeechBubble(IEntity entity, string contents) + private void CreateSpeechBubble(IEntity entity, SpeechBubbleData speechData) { - var bubble = new SpeechBubble(contents, entity, _eyeManager, this); + var bubble = SpeechBubble.CreateSpeechBubble(speechData.Type, speechData.Message, entity, _eyeManager, this); if (_activeSpeechBubbles.TryGetValue(entity.Uid, out var existing)) { @@ -405,7 +432,7 @@ namespace Content.Client.Chat /// public float TimeLeft { get; set; } - public Queue MessageQueue { get; } = new Queue(); + public Queue MessageQueue { get; } = new Queue(); } } } diff --git a/Content.Client/Chat/SpeechBubble.cs b/Content.Client/Chat/SpeechBubble.cs index 2766ab1b0b..147c3a8d5a 100644 --- a/Content.Client/Chat/SpeechBubble.cs +++ b/Content.Client/Chat/SpeechBubble.cs @@ -1,4 +1,5 @@ -using Content.Client.Interfaces.Chat; +using System; +using Content.Client.Interfaces.Chat; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -9,8 +10,14 @@ using Robust.Shared.Timing; namespace Content.Client.Chat { - public class SpeechBubble : Control + public abstract class SpeechBubble : Control { + public enum SpeechType + { + Emote, + Say + } + /// /// The total time a speech bubble stays on screen. /// @@ -38,6 +45,21 @@ namespace Content.Client.Chat public float ContentHeight { get; private set; } + public static SpeechBubble CreateSpeechBubble(SpeechType type, string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager) + { + switch (type) + { + case SpeechType.Emote: + return new EmoteSpeechBubble(text, senderEntity, eyeManager, chatManager); + + case SpeechType.Say: + return new SaySpeechBubble(text, senderEntity, eyeManager, chatManager); + + default: + throw new ArgumentOutOfRangeException(); + } + } + public SpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager) { _chatManager = chatManager; @@ -47,27 +69,18 @@ namespace Content.Client.Chat // Use text clipping so new messages don't overlap old ones being pushed up. RectClipContent = true; - var label = new RichTextLabel - { - MaxWidth = 256, - }; - label.SetMessage(text); + var bubble = BuildBubble(text); - var panel = new PanelContainer - { - StyleClasses = { "tooltipBox" }, - Children = { label }, - ModulateSelfOverride = Color.White.WithAlpha(0.75f) - }; - - AddChild(panel); + AddChild(bubble); ForceRunStyleUpdate(); - ContentHeight = panel.CombinedMinimumSize.Y; + ContentHeight = bubble.CombinedMinimumSize.Y; _verticalOffsetAchieved = -ContentHeight; } + protected abstract Control BuildBubble(string text); + protected override Vector2 CalculateMinimumSize() { return (base.CalculateMinimumSize().X, 0); @@ -134,4 +147,57 @@ namespace Content.Client.Chat } } } + + public class EmoteSpeechBubble : SpeechBubble + + { + public EmoteSpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager) + : base(text, senderEntity, eyeManager, chatManager) + { + } + + protected override Control BuildBubble(string text) + { + var label = new RichTextLabel + { + MaxWidth = 256, + }; + label.SetMessage(text); + + var panel = new PanelContainer + { + StyleClasses = { "speechBox", "emoteBox" }, + Children = { label }, + ModulateSelfOverride = Color.White.WithAlpha(0.75f) + }; + + return panel; + } + } + + public class SaySpeechBubble : SpeechBubble + { + public SaySpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager) + : base(text, senderEntity, eyeManager, chatManager) + { + } + + protected override Control BuildBubble(string text) + { + var label = new RichTextLabel + { + MaxWidth = 256, + }; + label.SetMessage(text); + + var panel = new PanelContainer + { + StyleClasses = { "speechBox", "sayBox" }, + Children = { label }, + ModulateSelfOverride = Color.White.WithAlpha(0.75f) + }; + + return panel; + } + } } diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index 690de5c18a..93e75a88f1 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -31,6 +31,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/ClientNotifyManager.cs b/Content.Client/ClientNotifyManager.cs index 1370dcca16..867bee8144 100644 --- a/Content.Client/ClientNotifyManager.cs +++ b/Content.Client/ClientNotifyManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Content.Client.Interfaces; +using Content.Client.UserInterface.Stylesheets; using Content.Shared; using Robust.Client.Interfaces.Console; using Robust.Client.Interfaces.Graphics.ClientEye; @@ -57,7 +58,11 @@ namespace Content.Client public void PopupMessage(ScreenCoordinates coordinates, string message) { - var label = new PopupLabel {Text = message}; + var label = new PopupLabel + { + Text = message, + StyleClasses = { StyleNano.StyleClassPopupMessage }, + }; var minimumSize = label.CombinedMinimumSize; LayoutContainer.SetPosition(label, label.InitialPos = coordinates.Position - minimumSize / 2); _userInterfaceManager.PopupRoot.AddChild(label); diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index c8ab1376c7..7604f3e8e9 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -12,9 +12,11 @@ using Content.Client.UserInterface.Stylesheets; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Cargo; using Content.Shared.GameObjects.Components.Chemistry; +using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Markers; using Content.Shared.GameObjects.Components.Research; using Content.Shared.GameObjects.Components.VendingMachines; +using Content.Shared.Kitchen; using Robust.Client; using Robust.Client.Interfaces; using Robust.Client.Interfaces.Graphics.Overlays; @@ -142,7 +144,7 @@ namespace Content.Client "Mop", "Bucket", "Puddle", - "CanSpill", + "CanSpill" }; foreach (var ignoreName in registerIgnore) @@ -160,6 +162,8 @@ namespace Content.Client factory.Register(); factory.Register(); factory.Register(); + factory.Register(); + factory.Register(); prototypes.RegisterIgnore("material"); prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side. @@ -178,6 +182,7 @@ namespace Content.Client IoCManager.Resolve().LoadParallax(); IoCManager.Resolve().PlayerJoinedServer += SubscribePlayerAttachmentEvents; IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); IoCManager.InjectDependencies(this); diff --git a/Content.Client/GameObjects/Components/Gravity/GravityGeneratorBoundUserInterface.cs b/Content.Client/GameObjects/Components/Gravity/GravityGeneratorBoundUserInterface.cs new file mode 100644 index 0000000000..450e73c288 --- /dev/null +++ b/Content.Client/GameObjects/Components/Gravity/GravityGeneratorBoundUserInterface.cs @@ -0,0 +1,104 @@ +using System; +using Content.Shared.GameObjects.Components.Gravity; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; + +namespace Content.Client.GameObjects.Components.Gravity +{ + public class GravityGeneratorBoundUserInterface: BoundUserInterface + { + private GravityGeneratorWindow _window; + + public bool IsOn; + + public GravityGeneratorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base (owner, uiKey) + { + SendMessage(new SharedGravityGeneratorComponent.GeneratorStatusRequestMessage()); + } + + protected override void Open() + { + base.Open(); + + IsOn = false; + + _window = new GravityGeneratorWindow(this); + + _window.Switch.OnPressed += (args) => + { + SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(!IsOn)); + SendMessage(new SharedGravityGeneratorComponent.GeneratorStatusRequestMessage()); + }; + + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (SharedGravityGeneratorComponent.GeneratorState) state; + IsOn = castState.On; + _window.UpdateButton(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + + _window?.Dispose(); + } + } + + public class GravityGeneratorWindow : SS14Window + { + public Label Status; + + public Button Switch; + + public GravityGeneratorBoundUserInterface Owner; + + public GravityGeneratorWindow(GravityGeneratorBoundUserInterface gravityGeneratorInterface = null) + { + IoCManager.InjectDependencies(this); + + Owner = gravityGeneratorInterface; + + Title = Loc.GetString("Gravity Generator Control"); + + var vBox = new VBoxContainer + { + CustomMinimumSize = new Vector2(250, 100) + }; + Status = new Label + { + Text = Loc.GetString("Current Status: " + (Owner.IsOn ? "On" : "Off")), + FontColorOverride = Owner.IsOn ? Color.ForestGreen : Color.Red + }; + Switch = new Button + { + Text = Loc.GetString(Owner.IsOn ? "Turn Off" : "Turn On"), + TextAlign = Label.AlignMode.Center, + CustomMinimumSize = new Vector2(150, 60) + }; + + vBox.AddChild(Status); + vBox.AddChild(Switch); + + Contents.AddChild(vBox); + } + + public void UpdateButton() + { + Status.Text = Loc.GetString("Current Status: " + (Owner.IsOn ? "On" : "Off")); + Status.FontColorOverride = Owner.IsOn ? Color.ForestGreen : Color.Red; + Switch.Text = Loc.GetString(Owner.IsOn ? "Turn Off" : "Turn On"); + } + } +} diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs index f824ac1f6c..2410ca7c26 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/HumanInventoryInterfaceController.cs @@ -114,13 +114,13 @@ namespace Content.Client.GameObjects } } - protected override void HandleInventoryKeybind(BaseButton.ButtonEventArgs args, Slots slot) + protected override void HandleInventoryKeybind(GUIBoundKeyEventArgs args, Slots slot) { if (!_inventoryButtons.TryGetValue(slot, out var buttons)) return; if (!Owner.TryGetSlot(slot, out var item)) return; - if (_itemSlotManager.OnButtonPressed(args.Event, item)) + if (_itemSlotManager.OnButtonPressed(args, item)) return; base.HandleInventoryKeybind(args, slot); diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs index da6fa94b5c..62237e09cf 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/InventoryInterfaceController.cs @@ -2,8 +2,9 @@ using Content.Client.UserInterface; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.Input; -using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Input; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -62,39 +63,35 @@ namespace Content.Client.GameObjects { } - protected virtual void HandleInventoryKeybind(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot) + protected virtual void HandleInventoryKeybind(GUIBoundKeyEventArgs args, EquipmentSlotDefines.Slots slot) { - if (args.Event.CanFocus) + if (args.Function == EngineKeyFunctions.UIClick) { - UseItemOnInventory(args, slot); + UseItemOnInventory(slot); } } - protected void AddToInventory(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot) + protected void AddToInventory(GUIBoundKeyEventArgs args, EquipmentSlotDefines.Slots slot) { - if (!args.Event.CanFocus) + if (args.Function != EngineKeyFunctions.UIClick) { return; } - args.Button.Pressed = false; Owner.SendEquipMessage(slot); } - protected void UseItemOnInventory(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot) + protected void UseItemOnInventory(EquipmentSlotDefines.Slots slot) { - args.Button.Pressed = false; - Owner.SendUseMessage(slot); } - protected void OpenStorage(BaseButton.ButtonEventArgs args, EquipmentSlotDefines.Slots slot) + protected void OpenStorage(GUIBoundKeyEventArgs args, EquipmentSlotDefines.Slots slot) { - if (!args.Event.CanFocus && args.Event.Function != ContentKeyFunctions.ActivateItemInWorld) + if (args.Function != EngineKeyFunctions.UIClick && args.Function != ContentKeyFunctions.ActivateItemInWorld) { return; } - args.Button.Pressed = false; Owner.SendOpenStorageUIMessage(slot); } diff --git a/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs b/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs new file mode 100644 index 0000000000..e93d016806 --- /dev/null +++ b/Content.Client/GameObjects/Components/Kitchen/MicrowaveBoundUserInterface.cs @@ -0,0 +1,119 @@ +using Robust.Client.GameObjects.Components.UserInterface; +using Content.Shared.Kitchen; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Content.Shared.Chemistry; +using Robust.Shared.GameObjects; +using System.Collections.Generic; +using Robust.Shared.Interfaces.GameObjects; + + +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.GameObjects.Components.Kitchen +{ + public class MicrowaveBoundUserInterface : BoundUserInterface + { +#pragma warning disable 649 + [Dependency] private readonly IEntityManager _entityManager; + [Dependency] private readonly IPrototypeManager _prototypeManager; +#pragma warning restore 649 + private MicrowaveMenu _menu; + + private Dictionary _solids = new Dictionary(); + private Dictionary _reagents =new Dictionary(); + + public MicrowaveBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner,uiKey) + { + + } + + protected override void Open() + { + base.Open(); + _menu = new MicrowaveMenu(this); + _menu.OpenCentered(); + _menu.OnClose += Close; + _menu.StartButton.OnPressed += args => SendMessage(new SharedMicrowaveComponent.MicrowaveStartCookMessage()); + _menu.EjectButton.OnPressed += args => SendMessage(new SharedMicrowaveComponent.MicrowaveEjectMessage()); + _menu.IngredientsList.OnItemSelected += args => + { + SendMessage(new SharedMicrowaveComponent.MicrowaveEjectSolidIndexedMessage(_solids[args.ItemIndex])); + + }; + + _menu.IngredientsListReagents.OnItemSelected += args => + { + SendMessage( + new SharedMicrowaveComponent.MicrowaveVaporizeReagentIndexedMessage(_reagents[args.ItemIndex])); + }; + + _menu.OnCookTimeSelected += args => + { + var actualButton = args.Button as Button; + var newTime = (uint) int.Parse(actualButton.Text); + _menu.VisualCookTime = newTime; + SendMessage(new SharedMicrowaveComponent.MicrowaveSelectCookTimeMessage(newTime)); + }; + + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + { + return; + } + _solids?.Clear(); + _menu?.Dispose(); + } + + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (!(state is MicrowaveUpdateUserInterfaceState cstate)) + { + return; + } + + RefreshContentsDisplay(cstate.ReagentsReagents, cstate.ContainedSolids); + + } + + + private void RefreshContentsDisplay(IReadOnlyList reagents, List solids) + { + _reagents.Clear(); + _menu.IngredientsListReagents.Clear(); + foreach (var reagent in reagents) + { + _prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto); + var reagentAdded = _menu.IngredientsListReagents.AddItem($"{reagent.Quantity} {proto.Name}"); + var reagentIndex = _menu.IngredientsListReagents.IndexOf(reagentAdded); + _reagents.Add(reagentIndex, reagent); + } + + _solids.Clear(); + _menu.IngredientsList.Clear(); + foreach (var entityID in solids) + { + var entity = _entityManager.GetEntity(entityID); + + if (entity.TryGetComponent(out IconComponent icon)) + { + var solidItem = _menu.IngredientsList.AddItem(entity.Name, icon.Icon.Default); + + var solidIndex = _menu.IngredientsList.IndexOf(solidItem); + _solids.Add(solidIndex, entityID); + } + + + } + + } + } +} diff --git a/Content.Client/GameObjects/Components/Kitchen/MicrowaveMenu.cs b/Content.Client/GameObjects/Components/Kitchen/MicrowaveMenu.cs new file mode 100644 index 0000000000..a2a9e02625 --- /dev/null +++ b/Content.Client/GameObjects/Components/Kitchen/MicrowaveMenu.cs @@ -0,0 +1,206 @@ +using System; +using Robust.Client.Graphics.Drawing; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Localization; +using Robust.Shared.Maths; + +namespace Content.Client.GameObjects.Components.Kitchen +{ + public class MicrowaveMenu : SS14Window + { + protected override Vector2? CustomSize => (512, 256); + + private MicrowaveBoundUserInterface Owner { get; set; } + + public event Action OnCookTimeSelected; + + public uint VisualCookTime = 1; + + public Button StartButton { get;} + public Button EjectButton { get;} + + public PanelContainer TimerFacePlate { get; } + + public ButtonGroup CookTimeButtonGroup { get; } + private VBoxContainer CookTimeButtonVbox { get; } + + public ItemList IngredientsList { get;} + + public ItemList IngredientsListReagents { get; } + private Label _cookTimeInfoLabel { get; } + + public MicrowaveMenu(MicrowaveBoundUserInterface owner = null) + { + Owner = owner; + Title = Loc.GetString("Microwave"); + var hSplit = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsVertical = SizeFlags.Fill + }; + + IngredientsListReagents = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SelectMode = ItemList.ItemListSelectMode.Button, + SizeFlagsStretchRatio = 2, + CustomMinimumSize = (100,128) + }; + + IngredientsList = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SelectMode = ItemList.ItemListSelectMode.Button, + SizeFlagsStretchRatio = 2, + CustomMinimumSize = (100,128) + }; + + hSplit.AddChild(IngredientsListReagents); + //Padding between the lists. + hSplit.AddChild(new Control + { + CustomMinimumSize = (0,5), + }); + + hSplit.AddChild(IngredientsList); + + var vSplit = new VBoxContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + }; + + hSplit.AddChild(vSplit); + + var buttonGridContainer = new VBoxContainer + { + Align = BoxContainer.AlignMode.Center, + SizeFlagsStretchRatio = 3 + }; + + StartButton = new Button + { + Text = Loc.GetString("Start"), + TextAlign = Label.AlignMode.Center, + + }; + + EjectButton = new Button + { + Text = Loc.GetString("Eject All Contents"), + ToolTip = Loc.GetString("This vaporizes all reagents, but ejects any solids."), + TextAlign = Label.AlignMode.Center, + }; + + buttonGridContainer.AddChild(StartButton); + buttonGridContainer.AddChild(EjectButton); + vSplit.AddChild(buttonGridContainer); + + //Padding + vSplit.AddChild(new Control + { + CustomMinimumSize = (0, 15), + SizeFlagsVertical = SizeFlags.Fill, + }); + + CookTimeButtonGroup = new ButtonGroup(); + CookTimeButtonVbox = new VBoxContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + Align = BoxContainer.AlignMode.Center, + }; + + var index = 0; + for (var i = 0; i <= 12; i++) + { + var newButton = new Button + { + Text = (index <= 0 ? 1 : index).ToString(), + TextAlign = Label.AlignMode.Center, + Group = CookTimeButtonGroup, + }; + CookTimeButtonVbox.AddChild(newButton); + newButton.OnPressed += args => + { + OnCookTimeSelected?.Invoke(args); + _cookTimeInfoLabel.Text = $"{Loc.GetString("COOK TIME")}: {VisualCookTime}"; + }; + index+=5; + } + + var cookTimeOneSecondButton = (Button)CookTimeButtonVbox.GetChild(0); + cookTimeOneSecondButton.Pressed = true; + + _cookTimeInfoLabel = new Label + { + Text = Loc.GetString($"COOK TIME: {VisualCookTime}"), + Align = Label.AlignMode.Center, + Modulate = Color.White, + SizeFlagsVertical = SizeFlags.ShrinkCenter + }; + + var innerTimerPanel = new PanelContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + ModulateSelfOverride = Color.Red, + CustomMinimumSize = (100, 128), + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.5f)}, + + Children = + { + + new VBoxContainer + { + + Children = + { + + new PanelContainer + { + PanelOverride = new StyleBoxFlat(){BackgroundColor = Color.Gray.WithAlpha(0.2f)}, + + Children = + { + _cookTimeInfoLabel + } + }, + + new ScrollContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + + Children = + { + CookTimeButtonVbox, + } + }, + + } + } + + + } + }; + + TimerFacePlate = new PanelContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + + innerTimerPanel + }, + }; + + vSplit.AddChild(TimerFacePlate); + Contents.AddChild(hSplit); + + } + + } +} diff --git a/Content.Client/GameObjects/Components/Kitchen/MicrowaveVisualizer.cs b/Content.Client/GameObjects/Components/Kitchen/MicrowaveVisualizer.cs new file mode 100644 index 0000000000..1591670f16 --- /dev/null +++ b/Content.Client/GameObjects/Components/Kitchen/MicrowaveVisualizer.cs @@ -0,0 +1,67 @@ +using Content.Client.GameObjects.Components.Sound; +using Content.Shared.GameObjects.Components.Power; +using Content.Shared.GameObjects.Components.Sound; +using Content.Shared.Kitchen; +using Robust.Client.GameObjects; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Audio; +using Robust.Shared.Log; + + +namespace Content.Client.GameObjects.Components.Kitchen +{ + public sealed class MicrowaveVisualizer : AppearanceVisualizer + { + private SoundComponent _soundComponent; + private const string MicrowaveSoundLoop = "/Audio/machines/microwave_loop.ogg"; + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + var sprite = component.Owner.GetComponent(); + _soundComponent ??= component.Owner.GetComponent(); + if (!component.TryGetData(PowerDeviceVisuals.VisualState, out MicrowaveVisualState state)) + { + state = MicrowaveVisualState.Idle; + } + switch (state) + { + case MicrowaveVisualState.Idle: + sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); + sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit"); + _soundComponent.StopAllSounds(); + break; + + case MicrowaveVisualState.Cooking: + sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); + sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit"); + var audioParams = AudioParams.Default; + audioParams.Loop = true; + var schedSound = new ScheduledSound(); + schedSound.Filename = MicrowaveSoundLoop; + schedSound.AudioParams = audioParams; + _soundComponent.AddScheduledSound(schedSound); + break; + + default: + Logger.Debug($"Something terrible happened in {this}"); + break; + + } + + var glowingPartsVisible = !(component.TryGetData(PowerDeviceVisuals.Powered, out bool powered) && !powered); + sprite.LayerSetVisible(MicrowaveVisualizerLayers.BaseUnlit, glowingPartsVisible); + + + } + + + private enum MicrowaveVisualizerLayers + { + Base, + BaseUnlit + } + } + + +} diff --git a/Content.Client/GameObjects/Components/Sound/SoundComponent.cs b/Content.Client/GameObjects/Components/Sound/SoundComponent.cs index c36f79fe83..03b5f088d3 100644 --- a/Content.Client/GameObjects/Components/Sound/SoundComponent.cs +++ b/Content.Client/GameObjects/Components/Sound/SoundComponent.cs @@ -15,7 +15,7 @@ namespace Content.Client.GameObjects.Components.Sound [RegisterComponent] public class SoundComponent : SharedSoundComponent { - private readonly List _schedules = new List(); + private readonly Dictionary _audioStreams = new Dictionary(); private AudioSystem _audioSystem; #pragma warning disable 649 [Dependency] private readonly IRobustRandom _random; @@ -23,26 +23,27 @@ namespace Content.Client.GameObjects.Components.Sound public override void StopAllSounds() { - foreach (var schedule in _schedules) + foreach (var kvp in _audioStreams) { - schedule.Play = false; + kvp.Key.Play = false; + kvp.Value.Stop(); } - _schedules.Clear(); + _audioStreams.Clear(); } public override void StopScheduledSound(string filename) { - foreach (var schedule in _schedules.ToArray()) + foreach (var kvp in _audioStreams) { - if (schedule.Filename != filename) continue; - schedule.Play = false; - _schedules.Remove(schedule); + if (kvp.Key.Filename != filename) continue; + kvp.Key.Play = false; + kvp.Value.Stop(); + _audioStreams.Remove(kvp.Key); } } public override void AddScheduledSound(ScheduledSound schedule) { - _schedules.Add(schedule); Play(schedule); } @@ -54,16 +55,11 @@ namespace Content.Client.GameObjects.Components.Sound { if (!schedule.Play) return; // We make sure this hasn't changed. if (_audioSystem == null) _audioSystem = IoCManager.Resolve().GetEntitySystem(); - _audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams); + _audioStreams.Add(schedule,_audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams)); - if (schedule.Times == 0) - { - _schedules.Remove(schedule); - return; - } + if (schedule.Times == 0) return; - if (schedule.Times > 0) - schedule.Times--; + if (schedule.Times > 0) schedule.Times--; Play(schedule); }); diff --git a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs index bdc5bc0a44..60ecdcd3fe 100644 --- a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs @@ -25,8 +25,6 @@ namespace Content.Client.GameObjects.EntitySystems [UsedImplicitly] public sealed class CombatModeSystem : SharedCombatModeSystem { - private const float AttackTimeThreshold = 0.15f; - #pragma warning disable 649 [Dependency] private readonly IGameHud _gameHud; [Dependency] private readonly IPlayerManager _playerManager; @@ -37,9 +35,6 @@ namespace Content.Client.GameObjects.EntitySystems private InputSystem _inputSystem; - public bool UseOrAttackIsDown { get; private set; } - private float _timeHeld; - public override void Initialize() { base.Initialize(); @@ -48,10 +43,8 @@ namespace Content.Client.GameObjects.EntitySystems _gameHud.OnTargetingZoneChanged = OnTargetingZoneChanged; _inputSystem = EntitySystemManager.GetEntitySystem(); - _inputSystem.BindMap.BindFunction(ContentKeyFunctions.UseOrAttack, new InputHandler(this)); _inputSystem.BindMap.BindFunction(ContentKeyFunctions.ToggleCombatMode, InputCmdHandler.FromDelegate(CombatModeToggled)); - _overlayManager.AddOverlay(new CombatModeOverlay(this)); } private void CombatModeToggled(ICommonSession session) @@ -60,20 +53,10 @@ namespace Content.Client.GameObjects.EntitySystems { EntityManager.RaisePredictiveEvent( new CombatModeSystemMessages.SetCombatModeActiveMessage(!IsInCombatMode())); - - // Just in case. - UseOrAttackIsDown = false; } } - public override void Shutdown() - { - base.Shutdown(); - - _overlayManager.RemoveOverlay(nameof(CombatModeOverlay)); - } - - private bool IsInCombatMode() + public bool IsInCombatMode() { var entity = _playerManager.LocalPlayer.ControlledEntity; if (entity == null || !entity.TryGetComponent(out CombatModeComponent combatMode)) @@ -92,104 +75,6 @@ namespace Content.Client.GameObjects.EntitySystems private void OnCombatModeChanged(bool obj) { EntityManager.RaisePredictiveEvent(new CombatModeSystemMessages.SetCombatModeActiveMessage(obj)); - - // Just in case. - UseOrAttackIsDown = false; - } - - private bool HandleInputMessage(ICommonSession session, InputCmdMessage message) - { - if (!(message is FullInputCmdMessage msg)) - return false; - - void SendMsg(BoundKeyFunction function, BoundKeyState state) - { - var functionId = _inputManager.NetworkBindMap.KeyFunctionID(function); - - var sendMsg = new FullInputCmdMessage(msg.Tick, functionId, state, - msg.Coordinates, msg.ScreenCoordinates, msg.Uid); - _inputSystem.HandleInputCommand(session, function, sendMsg); - } - - // If we are not in combat mode, relay it as a regular Use instead. - if (!IsInCombatMode()) - { - SendMsg(EngineKeyFunctions.Use, msg.State); - return true; - } - - if (msg.State == BoundKeyState.Down) - { - UseOrAttackIsDown = true; - _timeHeld = 0; - return true; - } - - // Up. - if (UseOrAttackIsDown && _timeHeld >= AttackTimeThreshold) - { - // Attack. - SendMsg(ContentKeyFunctions.Attack, BoundKeyState.Down); - SendMsg(ContentKeyFunctions.Attack, BoundKeyState.Up); - } - else - { - // Use. - SendMsg(EngineKeyFunctions.Use, BoundKeyState.Down); - SendMsg(EngineKeyFunctions.Use, BoundKeyState.Up); - } - - UseOrAttackIsDown = false; - - return true; - } - - public override void FrameUpdate(float frameTime) - { - if (UseOrAttackIsDown) - { - _timeHeld += frameTime; - } - } - - // Custom input handler type so we get the ENTIRE InputCmdMessage. - private sealed class InputHandler : InputCmdHandler - { - private readonly CombatModeSystem _combatModeSystem; - - public InputHandler(CombatModeSystem combatModeSystem) - { - _combatModeSystem = combatModeSystem; - } - - public override bool HandleCmdMessage(ICommonSession session, InputCmdMessage message) - { - return _combatModeSystem.HandleInputMessage(session, message); - } - } - - private sealed class CombatModeOverlay : Overlay - { - private readonly CombatModeSystem _system; - - public CombatModeOverlay(CombatModeSystem system) : base(nameof(CombatModeOverlay)) - { - _system = system; - } - - protected override void Draw(DrawingHandleBase handle) - { - var screenHandle = (DrawingHandleScreen) handle; - - var mousePos = IoCManager.Resolve().MouseScreenPosition; - - if (_system.UseOrAttackIsDown && _system._timeHeld > AttackTimeThreshold) - { - var tex = ResC.GetTexture($"/Textures/Objects/Tools/toolbox_r.png"); - - screenHandle.DrawTextureRect(tex, UIBox2.FromDimensions(mousePos, tex.Size * 2)); - } - } } } } diff --git a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs index d34001cb27..f606e932c3 100644 --- a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs @@ -39,8 +39,8 @@ namespace Content.Client.GameObjects.EntitySystems base.Update(frameTime); var canFireSemi = _isFirstShot; - var state = _inputSystem.CmdStates.GetState(ContentKeyFunctions.Attack); - if (!_combatModeSystem.UseOrAttackIsDown && state != BoundKeyState.Down) + var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); + if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down) { _isFirstShot = true; _blocked = false; diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs index c88bf50150..7b91aaedaa 100644 --- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs @@ -150,7 +150,10 @@ namespace Content.Client.GameObjects.EntitySystems if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity)) continue; - var disabled = verb.GetVisibility(user, component) != VerbVisibility.Visible; + if (VerbUtility.IsVerbInvisible(verb, user, component, out var vis)) + continue; + + var disabled = vis != VerbVisibility.Visible; var category = verb.GetCategory(user, component); @@ -166,7 +169,10 @@ namespace Content.Client.GameObjects.EntitySystems if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity)) continue; - var disabled = globalVerb.GetVisibility(user, entity) != VerbVisibility.Visible; + if (VerbUtility.IsVerbInvisible(globalVerb, user, entity, out var vis)) + continue; + + var disabled = vis != VerbVisibility.Visible; var category = globalVerb.GetCategory(user, entity); if(!buttons.ContainsKey(category)) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 26e850a3a3..279b238217 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -15,7 +15,8 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.FocusChat); common.AddFunction(ContentKeyFunctions.ExamineEntity); common.AddFunction(ContentKeyFunctions.OpenTutorial); - common.AddFunction(ContentKeyFunctions.UseOrAttack); + common.AddFunction(ContentKeyFunctions.TakeScreenshot); + common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI); var human = contexts.GetContext("human"); human.AddFunction(ContentKeyFunctions.SwapHands); @@ -27,9 +28,11 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.OpenContextMenu); human.AddFunction(ContentKeyFunctions.OpenCraftingMenu); human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); + human.AddFunction(ContentKeyFunctions.SmartEquipBackpack); + human.AddFunction(ContentKeyFunctions.SmartEquipBelt); human.AddFunction(ContentKeyFunctions.MouseMiddle); human.AddFunction(ContentKeyFunctions.ToggleCombatMode); - human.AddFunction(ContentKeyFunctions.Attack); + human.AddFunction(ContentKeyFunctions.WideAttack); var ghost = contexts.New("ghost", "common"); ghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Client/ScreenshotHook.cs b/Content.Client/ScreenshotHook.cs new file mode 100644 index 0000000000..529a469b72 --- /dev/null +++ b/Content.Client/ScreenshotHook.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Content.Shared.Input; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.Interfaces.Input; +using Robust.Shared.Input; +using Robust.Shared.Interfaces.Resources; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Utility; +using SixLabors.ImageSharp; + +namespace Content.Client +{ + internal class ScreenshotHook : IScreenshotHook + { + private static readonly ResourcePath BaseScreenshotPath = new ResourcePath("/Screenshots"); + + [Dependency] private readonly IInputManager _inputManager = default; + [Dependency] private readonly IClyde _clyde = default; + [Dependency] private readonly IResourceManager _resourceManager = default; + + public void Initialize() + { + _inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshot, InputCmdHandler.FromDelegate(_ => + { + Take(ScreenshotType.AfterUI); + })); + + _inputManager.SetInputCommand(ContentKeyFunctions.TakeScreenshotNoUI, InputCmdHandler.FromDelegate(_ => + { + Take(ScreenshotType.BeforeUI); + })); + } + + private async void Take(ScreenshotType type) + { + var screenshot = await _clyde.ScreenshotAsync(type); + + var time = DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss"); + + if (!_resourceManager.UserData.IsDir(BaseScreenshotPath)) + { + _resourceManager.UserData.CreateDir(BaseScreenshotPath); + } + + for (var i = 0; i < 5; i++) + { + try + { + var filename = time; + + if (i != 0) + { + filename = $"{filename}-{i}"; + } + + await using var file = + _resourceManager.UserData.Open(BaseScreenshotPath / $"{filename}.png", FileMode.CreateNew); + + await Task.Run(() => + { + // Saving takes forever, so don't hang the game on it. + screenshot.SaveAsPng(file); + }); + + Logger.InfoS("screenshot", "Screenshot taken as {0}.png", filename); + return; + } + catch (IOException e) + { + Logger.WarningS("screenshot", "Failed to save screenshot, retrying?:\n{0}", e); + } + } + + Logger.ErrorS("screenshot", "Unable to save screenshot."); + } + } + + public interface IScreenshotHook + { + void Initialize(); + } +} diff --git a/Content.Client/UserInterface/HandsGui.cs b/Content.Client/UserInterface/HandsGui.cs index 2356d7e24e..88915e620e 100644 --- a/Content.Client/UserInterface/HandsGui.cs +++ b/Content.Client/UserInterface/HandsGui.cs @@ -7,6 +7,7 @@ using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; @@ -57,10 +58,10 @@ namespace Content.Client.UserInterface AddChild(hBox); - _leftButton.OnPressed += args => HandKeyBindDown(args.Event, HandNameLeft); - _leftButton.OnStoragePressed += args => _OnStoragePressed(args.Event, HandNameLeft); - _rightButton.OnPressed += args => HandKeyBindDown(args.Event, HandNameRight); - _rightButton.OnStoragePressed += args => _OnStoragePressed(args.Event, HandNameRight); + _leftButton.OnPressed += args => HandKeyBindDown(args, HandNameLeft); + _leftButton.OnStoragePressed += args => _OnStoragePressed(args, HandNameLeft); + _rightButton.OnPressed += args => HandKeyBindDown(args, HandNameRight); + _rightButton.OnStoragePressed += args => _OnStoragePressed(args, HandNameRight); // Active hand _leftButton.AddChild(ActiveHandRect = new TextureRect @@ -118,31 +119,34 @@ namespace Content.Client.UserInterface private void HandKeyBindDown(GUIBoundKeyEventArgs args, string handIndex) { - args.Handle(); - if (!TryGetHands(out var hands)) return; if (args.Function == ContentKeyFunctions.MouseMiddle) { hands.SendChangeHand(handIndex); + args.Handle(); return; } var entity = hands.GetEntity(handIndex); if (entity == null) { - if (args.CanFocus && hands.ActiveIndex != handIndex) + if (args.Function == EngineKeyFunctions.UIClick && hands.ActiveIndex != handIndex) { hands.SendChangeHand(handIndex); + args.Handle(); } return; } if (_itemSlotManager.OnButtonPressed(args, entity)) + { + args.Handle(); return; + } - if (args.CanFocus) + if (args.Function == EngineKeyFunctions.UIClick) { if (hands.ActiveIndex == handIndex) { @@ -152,13 +156,14 @@ namespace Content.Client.UserInterface { hands.AttackByInHand(handIndex); } + args.Handle(); return; } } private void _OnStoragePressed(GUIBoundKeyEventArgs args, string handIndex) { - if (!args.CanFocus) + if (args.Function != EngineKeyFunctions.UIClick) return; if (!TryGetHands(out var hands)) return; diff --git a/Content.Client/UserInterface/ItemSlotButton.cs b/Content.Client/UserInterface/ItemSlotButton.cs index f27583f737..54bc4c027c 100644 --- a/Content.Client/UserInterface/ItemSlotButton.cs +++ b/Content.Client/UserInterface/ItemSlotButton.cs @@ -1,5 +1,7 @@ using System; +using Content.Shared.Input; using Robust.Client.Graphics; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Maths; @@ -8,26 +10,26 @@ namespace Content.Client.GameObjects { public sealed class ItemSlotButton : MarginContainer { - public BaseButton Button { get; } + public TextureRect Button { get; } public SpriteView SpriteView { get; } public BaseButton StorageButton { get; } public TextureRect CooldownCircle { get; } - public Action OnPressed { get; set; } - public Action OnStoragePressed { get; set; } + public Action OnPressed { get; set; } + public Action OnStoragePressed { get; set; } public ItemSlotButton(Texture texture, Texture storageTexture) { CustomMinimumSize = (64, 64); - AddChild(Button = new TextureButton + AddChild(Button = new TextureRect { - TextureNormal = texture, - Scale = (2, 2), - EnableAllKeybinds = true + Texture = texture, + TextureScale = (2, 2), + MouseFilter = MouseFilterMode.Stop }); - Button.OnPressed += OnButtonPressed; + Button.OnKeyBindDown += OnButtonPressed; AddChild(SpriteView = new SpriteView { @@ -42,9 +44,16 @@ namespace Content.Client.GameObjects SizeFlagsHorizontal = SizeFlags.ShrinkEnd, SizeFlagsVertical = SizeFlags.ShrinkEnd, Visible = false, - EnableAllKeybinds = true }); + StorageButton.OnKeyBindDown += args => + { + if (args.Function != EngineKeyFunctions.UIClick) + { + OnButtonPressed(args); + } + }; + StorageButton.OnPressed += OnStorageButtonPressed; AddChild(CooldownCircle = new TextureRect @@ -57,20 +66,20 @@ namespace Content.Client.GameObjects }); } - private void OnButtonPressed(BaseButton.ButtonEventArgs args) + private void OnButtonPressed(GUIBoundKeyEventArgs args) { OnPressed?.Invoke(args); } private void OnStorageButtonPressed(BaseButton.ButtonEventArgs args) { - if (args.Event.Function == EngineKeyFunctions.Use) + if (args.Event.Function == EngineKeyFunctions.UIClick) { - OnStoragePressed?.Invoke(args); + OnStoragePressed?.Invoke(args.Event); } else { - OnPressed?.Invoke(args); + OnPressed?.Invoke(args.Event); } } } diff --git a/Content.Client/UserInterface/ItemSlotManager.cs b/Content.Client/UserInterface/ItemSlotManager.cs index 768ddd30af..5c07cd5f57 100644 --- a/Content.Client/UserInterface/ItemSlotManager.cs +++ b/Content.Client/UserInterface/ItemSlotManager.cs @@ -65,8 +65,6 @@ namespace Content.Client.UserInterface public bool OnButtonPressed(GUIBoundKeyEventArgs args, IEntity item) { - args.Handle(); - if (item == null) return false; @@ -99,6 +97,7 @@ namespace Content.Client.UserInterface { return false; } + args.Handle(); return true; } diff --git a/Content.Client/UserInterface/Stylesheets/StyleNano.cs b/Content.Client/UserInterface/Stylesheets/StyleNano.cs index e182012a55..d2d9941eb8 100644 --- a/Content.Client/UserInterface/Stylesheets/StyleNano.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleNano.cs @@ -21,6 +21,7 @@ namespace Content.Client.UserInterface.Stylesheets public const string StyleClassLabelSecondaryColor = "LabelSecondaryColor"; public const string StyleClassLabelBig = "LabelBig"; public const string StyleClassButtonBig = "ButtonBig"; + public const string StyleClassPopupMessage = "PopupMessage"; public static readonly Color NanoGold = Color.FromHex("#A88B5E"); @@ -41,7 +42,9 @@ namespace Content.Client.UserInterface.Stylesheets public StyleNano(IResourceCache resCache) : base(resCache) { var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10); + var notoSansItalic10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Italic.ttf", 10); var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12); + var notoSansItalic12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Italic.ttf", 12); var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12); var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14); var notoSans16 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 16); @@ -449,6 +452,19 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox) }), + new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {"speechBox", "sayBox"}, null, null), new[] + { + new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox) + }), + + new StyleRule(new SelectorChild( + new SelectorElement(typeof(PanelContainer), new[] {"speechBox", "emoteBox"}, null, null), + new SelectorElement(typeof(RichTextLabel), null, null, null)), + new[] + { + new StyleProperty("font", notoSansItalic12), + }), + // Entity tooltip new StyleRule( new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null, @@ -552,6 +568,14 @@ namespace Content.Client.UserInterface.Stylesheets new StyleProperty("font", notoSans16) }), + // Popup messages + new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessage}, null, null), + new[] + { + new StyleProperty("font", notoSansItalic10), + new StyleProperty("font-color", Color.LightGray), + }), + //APC and SMES power state label colors new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPowerStateNone}, null, null), new[] { diff --git a/Content.Client/UserInterface/TutorialWindow.cs b/Content.Client/UserInterface/TutorialWindow.cs index cc81193dbf..af54907d57 100644 --- a/Content.Client/UserInterface/TutorialWindow.cs +++ b/Content.Client/UserInterface/TutorialWindow.cs @@ -69,10 +69,14 @@ namespace Content.Client.UserInterface Switch hands: [color=#a4885c]{4}[/color] Use held item: [color=#a4885c]{5}[/color] Drop held item: [color=#a4885c]{6}[/color] +Smart equip from backpack: [color=#a4885c]{24}[/color] +Smart equip from belt: [color=#a4885c]{25}[/color] Open inventory: [color=#a4885c]{7}[/color] Open character window: [color=#a4885c]{8}[/color] Open crafting window: [color=#a4885c]{9}[/color] Focus chat: [color=#a4885c]{10}[/color] +Use hand/object in hand: [color=#a4885c]{22}[/color] +Do wide attack: [color=#a4885c]{23}[/color] Use targeted entity: [color=#a4885c]{11}[/color] Throw held item: [color=#a4885c]{12}[/color] Examine entity: [color=#a4885c]{13}[/color] @@ -102,16 +106,20 @@ Toggle sandbox window: [color=#a4885c]{21}[/color]", Key(ShowDebugMonitors), Key(OpenEntitySpawnWindow), Key(OpenTileSpawnWindow), - Key(OpenSandboxWindow))); - - //Gameplay - VBox.AddChild(new Label { FontOverride = headerFont, Text = Loc.GetString("\nSandbox spawner", Key(OpenSandboxWindow)) }); - AddFormattedText(SandboxSpawnerContents); + Key(OpenSandboxWindow), + Key(Use), + Key(WideAttack), + Key(SmartEquipBackpack), + Key(SmartEquipBelt))); //Gameplay VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" }); AddFormattedText(GameplayContents); + //Gameplay + VBox.AddChild(new Label { FontOverride = headerFont, Text = Loc.GetString("\nSandbox spawner", Key(OpenSandboxWindow)) }); + AddFormattedText(SandboxSpawnerContents); + //Feedback VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nFeedback" }); AddFormattedText(FeedbackContents); diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs new file mode 100644 index 0000000000..0b35fa603a --- /dev/null +++ b/Content.IntegrationTests/Tests/GravityGridTest.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; +using Content.Client.GameObjects.Components.Gravity; +using Content.Server.GameObjects.Components.Gravity; +using Content.Server.GameObjects.Components.Power; +using NUnit.Framework; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.IntegrationTests.Tests +{ + /// Tests the behavior of GravityGeneratorComponent, + /// making sure that gravity is applied to the correct grids. + [TestFixture] + [TestOf(typeof(GravityGeneratorComponent))] + public class GravityGridTest : ContentIntegrationTest + { + [Test] + public async Task Test() + { + var server = StartServerDummyTicker(); + + IEntity generator = null; + + IMapGrid grid1 = null; + IMapGrid grid2 = null; + + // Create grids + server.Assert(() => + { + var mapMan = IoCManager.Resolve(); + + mapMan.CreateMap(new MapId(1)); + grid1 = mapMan.CreateGrid(new MapId(1)); + grid2 = mapMan.CreateGrid(new MapId(1)); + + var entityMan = IoCManager.Resolve(); + + generator = entityMan.SpawnEntity("GravityGenerator", new GridCoordinates(new Vector2(0, 0), grid2.Index)); + Assert.That(generator.HasComponent()); + Assert.That(generator.HasComponent()); + var generatorComponent = generator.GetComponent(); + var powerComponent = generator.GetComponent(); + Assert.AreEqual(generatorComponent.Status, GravityGeneratorStatus.Unpowered); + powerComponent.ExternalPowered = true; + }); + server.RunTicks(1); + + server.Assert(() => + { + var generatorComponent = generator.GetComponent(); + + Assert.AreEqual(generatorComponent.Status, GravityGeneratorStatus.On); + + Assert.That(!grid1.HasGravity); + Assert.That(grid2.HasGravity); + }); + + await server.WaitIdleAsync(); + } + } +} diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 7a97e2f538..6eb0c31d8c 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -4,6 +4,7 @@ using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Preferences; using Content.Server.Sandbox; +using Content.Shared.Kitchen; using Robust.Server.Interfaces.Player; using Robust.Shared.ContentPack; using Robust.Shared.Interfaces.GameObjects; @@ -70,6 +71,7 @@ namespace Content.Server logManager.GetSawmill("Storage").Level = LogLevel.Info; IoCManager.Resolve().StartInit(); + } public override void PostInit() @@ -79,6 +81,7 @@ namespace Content.Server _gameTicker.Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().FinishInit(); + IoCManager.Resolve().Initialize(); } public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) diff --git a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs b/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs index 5375c3b783..ab265e3693 100644 --- a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs @@ -151,6 +151,12 @@ namespace Content.Server.GameObjects return success; } + public void PutInHandOrDrop(ItemComponent item) + { + if (!PutInHand(item)) + item.Owner.Transform.GridPosition = Owner.Transform.GridPosition; + } + public bool CanPutInHand(ItemComponent item) { foreach (var hand in ActivePriorityEnumerable()) diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs new file mode 100644 index 0000000000..494a97c9d8 --- /dev/null +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -0,0 +1,206 @@ +using Content.Server.GameObjects.Components.Damage; +using Content.Server.GameObjects.Components.Interactable.Tools; +using Content.Server.GameObjects.Components.Power; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Content.Shared.GameObjects.Components.Gravity; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Gravity +{ + [RegisterComponent] + public class GravityGeneratorComponent: SharedGravityGeneratorComponent, IAttackBy, IBreakAct, IAttackHand + { + private BoundUserInterface _userInterface; + + private PowerDeviceComponent _powerDevice; + + private SpriteComponent _sprite; + + private bool _switchedOn; + + private bool _intact; + + private GravityGeneratorStatus _status; + + public bool Powered => _powerDevice.Powered; + + public bool SwitchedOn => _switchedOn; + + public bool Intact => _intact; + + public GravityGeneratorStatus Status => _status; + + public bool NeedsUpdate + { + get + { + switch (_status) + { + case GravityGeneratorStatus.On: + return !(Powered && SwitchedOn && Intact); + case GravityGeneratorStatus.Off: + return SwitchedOn || !(Powered && Intact); + case GravityGeneratorStatus.Unpowered: + return SwitchedOn || Powered || !Intact; + case GravityGeneratorStatus.Broken: + return SwitchedOn || Powered || Intact; + default: + return true; // This _should_ be unreachable + } + } + } + + public override string Name => "GravityGenerator"; + + public override void Initialize() + { + base.Initialize(); + + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(GravityGeneratorUiKey.Key); + _userInterface.OnReceiveMessage += HandleUIMessage; + _powerDevice = Owner.GetComponent(); + _sprite = Owner.GetComponent(); + _switchedOn = true; + _intact = true; + _status = GravityGeneratorStatus.On; + UpdateState(); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _switchedOn, "switched_on", true); + serializer.DataField(ref _intact, "intact", true); + } + + bool IAttackHand.AttackHand(AttackHandEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out var actor)) + return false; + if (Status != GravityGeneratorStatus.Off && Status != GravityGeneratorStatus.On) + { + return false; + } + OpenUserInterface(actor.playerSession); + return true; + } + + public bool AttackBy(AttackByEventArgs eventArgs) + { + if (!eventArgs.AttackWith.TryGetComponent(out var welder)) return false; + if (welder.TryUse(5.0f)) + { + // Repair generator + var damagable = Owner.GetComponent(); + var breakable = Owner.GetComponent(); + damagable.HealAllDamage(); + breakable.broken = false; + _intact = true; + + var entitySystemManager = IoCManager.Resolve(); + var notifyManager = IoCManager.Resolve(); + + entitySystemManager.GetEntitySystem().Play("/Audio/items/welder2.ogg", Owner); + notifyManager.PopupMessage(Owner, eventArgs.User, Loc.GetString("You repair the gravity generator with the welder")); + + return true; + } else + { + return false; + } + } + + public void OnBreak(BreakageEventArgs eventArgs) + { + _intact = false; + _switchedOn = false; + } + + public void UpdateState() + { + if (!Intact) + { + MakeBroken(); + } else if (!Powered) + { + MakeUnpowered(); + } else if (!SwitchedOn) + { + MakeOff(); + } else + { + MakeOn(); + } + } + + private void HandleUIMessage(ServerBoundUserInterfaceMessage message) + { + switch (message.Message) + { + case GeneratorStatusRequestMessage _: + _userInterface.SetState(new GeneratorState(Status == GravityGeneratorStatus.On)); + break; + case SwitchGeneratorMessage msg: + _switchedOn = msg.On; + UpdateState(); + break; + default: + break; + } + } + + private void OpenUserInterface(IPlayerSession playerSession) + { + _userInterface.Open(playerSession); + } + + private void MakeBroken() + { + _status = GravityGeneratorStatus.Broken; + _sprite.LayerSetState(0, "broken"); + _sprite.LayerSetVisible(1, false); + } + + private void MakeUnpowered() + { + _status = GravityGeneratorStatus.Unpowered; + _sprite.LayerSetState(0, "off"); + _sprite.LayerSetVisible(1, false); + } + + private void MakeOff() + { + _status = GravityGeneratorStatus.Off; + _sprite.LayerSetState(0, "off"); + _sprite.LayerSetVisible(1, false); + } + + private void MakeOn() + { + _status = GravityGeneratorStatus.On; + _sprite.LayerSetState(0, "on"); + _sprite.LayerSetVisible(1, true); + } + } + + public enum GravityGeneratorStatus + { + Broken, + Unpowered, + Off, + On + } +} diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs index 3f451a2ce4..f0bdb1a615 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs @@ -45,6 +45,8 @@ namespace Content.Server.GameObjects private int StorageCapacityMax = 10000; public HashSet SubscribedSessions = new HashSet(); + public IReadOnlyCollection StoredEntities => storage.ContainedEntities; + public override void Initialize() { base.Initialize(); @@ -140,7 +142,6 @@ namespace Content.Server.GameObjects /// public bool AttackBy(AttackByEventArgs eventArgs) { - _ensureInitialCalculated(); Logger.DebugS("Storage", "Storage (UID {0}) attacked by user (UID {1}) with entity (UID {2}).", Owner.Uid, eventArgs.User.Uid, eventArgs.AttackWith.Uid); if(Owner.TryGetComponent(out var placeableSurfaceComponent)) @@ -363,8 +364,10 @@ namespace Content.Server.GameObjects /// /// Inserts an entity into the storage component from the players active hand. /// - private bool PlayerInsertEntity(IEntity player) + public bool PlayerInsertEntity(IEntity player) { + _ensureInitialCalculated(); + if (!player.TryGetComponent(out IHandsComponent hands) || hands.GetActiveHand == null) return false; diff --git a/Content.Server/GameObjects/Components/Kitchen/KitchenMicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/KitchenMicrowaveComponent.cs new file mode 100644 index 0000000000..ac6ab14c65 --- /dev/null +++ b/Content.Server/GameObjects/Components/Kitchen/KitchenMicrowaveComponent.cs @@ -0,0 +1,408 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.Components.Nutrition; +using Content.Shared.Chemistry; +using Robust.Shared.Serialization; +using Robust.Shared.Interfaces.GameObjects; +using Content.Shared.Prototypes.Kitchen; +using Content.Shared.Kitchen; +using Robust.Shared.Timers; +using Robust.Server.GameObjects; +using Content.Shared.GameObjects.Components.Power; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.GameObjects.Components.Container; +using Content.Server.GameObjects.Components.Power; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Localization; +using Content.Server.Interfaces; +using Robust.Shared.Audio; +using YamlDotNet.Serialization.NodeTypeResolvers; + +namespace Content.Server.GameObjects.Components.Kitchen +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class KitchenMicrowaveComponent : SharedMicrowaveComponent, IActivate, IAttackBy, ISolutionChange + { +#pragma warning disable 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager; + [Dependency] private readonly IEntityManager _entityManager; + [Dependency] private readonly RecipeManager _recipeManager; + [Dependency] private readonly IServerNotifyManager _notifyManager; +#pragma warning restore 649 + +#region YAMLSERIALIZE + private int _cookTimeDefault; + private int _cookTimeMultiplier; //For upgrades and stuff I guess? + private string _badRecipeName; + private string _startCookingSound; + private string _cookingCompleteSound; +#endregion + +#region VIEWVARIABLES + [ViewVariables] + private SolutionComponent _solution; + + [ViewVariables] + private bool _busy = false; + + /// + /// This is a fixed offset of 5. + /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. + /// For right now, I don't think any recipe cook time should be greater than 60 seconds. + /// + [ViewVariables] + private uint _currentCookTimerTime { get; set; } = 1; +#endregion + + private bool Powered => _powerDevice.Powered; + + private bool HasContents => _solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0; + + void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface(); + + private AudioSystem _audioSystem; + + private AppearanceComponent _appearance; + private PowerDeviceComponent _powerDevice; + + private BoundUserInterface _userInterface; + + private Container _storage; + + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _badRecipeName, "failureResult", "FoodBadRecipe"); + serializer.DataField(ref _cookTimeDefault, "cookTime", 5); + serializer.DataField(ref _cookTimeMultiplier, "cookTimeMultiplier", 1000); + serializer.DataField(ref _startCookingSound, "beginCookingSound","/Audio/machines/microwave_start_beep.ogg" ); + serializer.DataField(ref _cookingCompleteSound, "foodDoneSound","/Audio/machines/microwave_done_beep.ogg" ); + } + + public override void Initialize() + { + base.Initialize(); + _solution ??= Owner.TryGetComponent(out SolutionComponent solutionComponent) + ? solutionComponent + : Owner.AddComponent(); + + _storage = ContainerManagerComponent.Ensure("microwave_entity_container", Owner, out var existed); + _appearance = Owner.GetComponent(); + _powerDevice = Owner.GetComponent(); + _audioSystem = _entitySystemManager.GetEntitySystem(); + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(MicrowaveUiKey.Key); + + _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; + } + + private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message) + { + if (!Powered || _busy) + { + return; + } + + switch (message.Message) + { + case MicrowaveStartCookMessage msg : + wzhzhzh(); + break; + + case MicrowaveEjectMessage msg : + if (HasContents) + { + VaporizeReagents(); + EjectSolids(); + ClickSound(); + UpdateUserInterface(); + } + + break; + + case MicrowaveEjectSolidIndexedMessage msg: + if (HasContents) + { + EjectSolidWithIndex(msg.EntityID); + ClickSound(); + UpdateUserInterface(); + } + break; + + case MicrowaveVaporizeReagentIndexedMessage msg: + if (HasContents) + { + VaporizeReagentWithReagentQuantity(msg.ReagentQuantity); + ClickSound(); + UpdateUserInterface(); + } + break; + + case MicrowaveSelectCookTimeMessage msg: + _currentCookTimerTime = msg.newCookTime; + ClickSound(); + UpdateUserInterface(); + break; + } + + } + + private void SetAppearance(MicrowaveVisualState state) + { + if (_appearance != null || Owner.TryGetComponent(out _appearance)) + { + _appearance.SetData(PowerDeviceVisuals.VisualState, state); + } + + } + + private void UpdateUserInterface() + { + var solidsVisualList = new List(); + foreach(var item in _storage.ContainedEntities) + { + solidsVisualList.Add(item.Uid); + } + + _userInterface.SetState(new MicrowaveUpdateUserInterfaceState(_solution.Solution.Contents, solidsVisualList)); + } + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor) || !Powered) + { + return; + } + UpdateUserInterface(); + _userInterface.Open(actor.playerSession); + + } + + public bool AttackBy(AttackByEventArgs eventArgs) + { + var itemEntity = eventArgs.User.GetComponent().GetActiveHand.Owner; + + if(itemEntity.TryGetComponent(out var attackPourable)) + { + //Get target and check if it can be poured into + if (!Owner.TryGetComponent(out var mySolution) + || !mySolution.CanPourIn) + { + return false; + } + + if (!itemEntity.TryGetComponent(out var attackSolution) + || !attackSolution.CanPourOut) + { + return false; + } + + //Get transfer amount. May be smaller than _transferAmount if not enough room + var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, mySolution.EmptyVolume); + if (realTransferAmount <= 0) //Special message if container is full + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, + Loc.GetString("Container is full")); + return false; + } + + //Move units from attackSolution to targetSolution + var removedSolution = attackSolution.SplitSolution(realTransferAmount); + if (!mySolution.TryAddSolution(removedSolution)) + { + return false; + } + + _notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, + Loc.GetString("Transferred {0}u", removedSolution.TotalVolume)); + return true; + } + + if (!itemEntity.TryGetComponent(typeof(FoodComponent), out var food)) + { + return false; + } + + var ent = food.Owner; //Get the entity of the ItemComponent. + _storage.Insert(ent); + UpdateUserInterface(); + return true; + + } + + //This is required. It's 'cook'. + private void wzhzhzh() + { + if (!HasContents) + { + return; + } + + _busy = true; + // Convert storage into Dictionary of ingredients + var solidsDict = new Dictionary(); + foreach(var item in _storage.ContainedEntities) + { + if(solidsDict.ContainsKey(item.Prototype.ID)) + { + solidsDict[item.Prototype.ID]++; + } + else + { + solidsDict.Add(item.Prototype.ID, 1); + } + } + + // Check recipes + FoodRecipePrototype recipeToCook = null; + foreach(var r in _recipeManager.Recipes) + { + if (!CanSatisfyRecipe(r, solidsDict)) + { + continue; + } + + recipeToCook = r; + } + + var goodMeal = (recipeToCook != null) + && + (_currentCookTimerTime == (uint)recipeToCook.CookTime) ? true : false; + + SetAppearance(MicrowaveVisualState.Cooking); + _audioSystem.Play(_startCookingSound); + Timer.Spawn((int)(_currentCookTimerTime * _cookTimeMultiplier), () => + { + + if (goodMeal) + { + SubtractContents(recipeToCook); + } + else + { + VaporizeReagents(); + VaporizeSolids(); + } + + var entityToSpawn = goodMeal ? recipeToCook.Result : _badRecipeName; + _entityManager.SpawnEntity(entityToSpawn, Owner.Transform.GridPosition); + _audioSystem.Play(_cookingCompleteSound); + SetAppearance(MicrowaveVisualState.Idle); + _busy = false; + }); + UpdateUserInterface(); + return; + } + + private void VaporizeReagents() + { + _solution.RemoveAllSolution(); + } + + private void VaporizeReagentWithReagentQuantity(Solution.ReagentQuantity reagentQuantity) + { + _solution.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity); + } + + private void VaporizeSolids() + { + for(var i = _storage.ContainedEntities.Count-1; i>=0; i--) + { + var item = _storage.ContainedEntities.ElementAt(i); + _storage.Remove(item); + item.Delete(); + } + } + + private void EjectSolids() + { + + for(var i = _storage.ContainedEntities.Count-1; i>=0; i--) + { + _storage.Remove(_storage.ContainedEntities.ElementAt(i)); + } + } + + private void EjectSolidWithIndex(EntityUid entityID) + { + if (_entityManager.EntityExists(entityID)) + { + _storage.Remove(_entityManager.GetEntity(entityID)); + } + } + + + private void SubtractContents(FoodRecipePrototype recipe) + { + foreach(var recipeReagent in recipe.IngredientsReagents) + { + _solution.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value)); + } + + foreach (var recipeSolid in recipe.IngredientsSolids) + { + for (var i = 0; i < recipeSolid.Value; i++) + { + foreach (var item in _storage.ContainedEntities) + { + if (item.Prototype.ID == recipeSolid.Key) + { + _storage.Remove(item); + item.Delete(); + break; + } + } + } + } + + } + + private bool CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary solids) + { + foreach (var reagent in recipe.IngredientsReagents) + { + if (!_solution.ContainsReagent(reagent.Key, out var amount)) + { + return false; + } + + if (amount.Int() < reagent.Value) + { + return false; + } + } + + foreach (var solid in recipe.IngredientsSolids) + { + if (!solids.ContainsKey(solid.Key)) + { + return false; + } + + if (solids[solid.Key] < solid.Value) + { + return false; + } + } + + return true; + } + + private void ClickSound() + { + + _audioSystem.Play("/Audio/machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f)); + + } + + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs index 1cfc3d9991..8138dfb5d6 100644 --- a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs @@ -118,9 +118,17 @@ namespace Content.Server.GameObjects.Components.Mobs if (!ShowExamineInfo) return; + var dead = false; + + if(Owner.TryGetComponent(out var species)) + if (species.CurrentDamageState is DeadState) + dead = true; + // TODO: Use gendered pronouns depending on the entity if(!HasMind) - message.AddMarkup($"[color=red]They are totally catatonic. The stresses of life in deep-space must have been too much for them. Any recovery is unlikely.[/color]"); + message.AddMarkup(!dead + ? $"[color=red]They are totally catatonic. The stresses of life in deep-space must have been too much for them. Any recovery is unlikely.[/color]" + : $"[color=purple]Their soul has departed.[/color]"); else if(Mind.Session == null) message.AddMarkup("[color=yellow]They have a blank, absent-minded stare and appears completely unresponsive to anything. They may snap out of it soon.[/color]"); } diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs index c26c109d70..14df66475a 100644 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs @@ -177,7 +177,7 @@ namespace Content.Server.GameObjects currentstate = threshold; - EntityEventArgs toRaise = new MobDamageStateChangedMessage(this); + var toRaise = new MobDamageStateChangedMessage(this); Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, toRaise); } diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index f5e947d3d3..aa45e6a4b3 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -90,6 +90,15 @@ namespace Content.Server.GameObjects.Components.Movement } } + /// + [ViewVariables] + public float CurrentPushSpeed => 5.0f; + + /// + [ViewVariables] + public float GrabRange => 0.2f; + + /// /// Is the entity Sprinting (running)? /// diff --git a/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs b/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs index f3229c82ac..f15f81f8fd 100644 --- a/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs @@ -67,6 +67,14 @@ namespace Content.Server.GameObjects.Components.Movement } } + /// + [ViewVariables] + public float CurrentPushSpeed => 5.0f; + + /// + [ViewVariables] + public float GrabRange => 0.2f; + /// /// Is the entity Sprinting (running)? /// diff --git a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs index 9bc123c88a..ebbcae52ea 100644 --- a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs @@ -34,6 +34,15 @@ namespace Content.Server.GameObjects.Components.Movement [ViewVariables(VVAccess.ReadWrite)] public float CurrentWalkSpeed { get; set; } = 8; public float CurrentSprintSpeed { get; set; } + + /// + [ViewVariables] + public float CurrentPushSpeed => 0.0f; + + /// + [ViewVariables] + public float GrabRange => 0.0f; + public bool Sprinting { get; set; } public Vector2 VelocityDir { get; } = Vector2.Zero; public GridCoordinates LastPosition { get; set; } diff --git a/Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent].cs b/Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent.cs similarity index 68% rename from Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent].cs rename to Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent.cs index 96059435a7..0d01ce2bb9 100644 --- a/Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent].cs +++ b/Content.Server/GameObjects/Components/Sound/EmitSoundOnUseComponent.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Audio; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; @@ -16,17 +17,24 @@ namespace Content.Server.GameObjects.Components.Sound public override string Name => "EmitSoundOnUse"; public string _soundName; + public float _pitchVariation; public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(ref _soundName, "sound", ""); + serializer.DataField(ref _soundName, "sound", string.Empty); + serializer.DataField(ref _pitchVariation, "variation", 0.0f); } bool IUse.UseEntity(UseEntityEventArgs eventArgs) { if (!string.IsNullOrWhiteSpace(_soundName)) { + if (_pitchVariation > 0.0) + { + Owner.GetComponent().Play(_soundName, AudioHelpers.WithVariation(_pitchVariation).WithVolume(-2f)); + return true; + } Owner.GetComponent().Play(_soundName, AudioParams.Default.WithVolume(-2f)); return true; } diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 8dc48fdd89..2495669d73 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -235,6 +235,14 @@ namespace Content.Server.GameObjects.Components _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You have no hands.")); return; } + + var interactionSystem = IoCManager.Resolve().GetEntitySystem(); + if (!interactionSystem.InRangeUnobstructed(player.Transform.MapPosition, Owner.Transform.WorldPosition, ignoredEnt: Owner)) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You can't reach there!")); + return; + } + var activeHandEntity = handsComponent.GetActiveHand?.Owner; activeHandEntity.TryGetComponent(out var tool); switch (msg.Action) diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index ec9e7b7322..9d4cecc949 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -417,8 +417,8 @@ namespace Content.Server.GameObjects.EntitySystems var inputSys = EntitySystemManager.GetEntitySystem(); inputSys.BindMap.BindFunction(EngineKeyFunctions.Use, new PointerInputCmdHandler(HandleUseItemInHand)); - inputSys.BindMap.BindFunction(ContentKeyFunctions.Attack, - new PointerInputCmdHandler(HandleAttack)); + inputSys.BindMap.BindFunction(ContentKeyFunctions.WideAttack, + new PointerInputCmdHandler(HandleWideAttack)); inputSys.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInWorld, new PointerInputCmdHandler(HandleActivateItemInWorld)); } @@ -477,7 +477,7 @@ namespace Content.Server.GameObjects.EntitySystems activateComp.Activate(new ActivateEventArgs {User = user}); } - private bool HandleAttack(ICommonSession session, GridCoordinates coords, EntityUid uid) + private bool HandleWideAttack(ICommonSession session, GridCoordinates coords, EntityUid uid) { // client sanitization if (!_mapManager.GridExists(coords.GridID)) diff --git a/Content.Server/GameObjects/EntitySystems/GravitySystem.cs b/Content.Server/GameObjects/EntitySystems/GravitySystem.cs new file mode 100644 index 0000000000..1d60ddeec1 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/GravitySystem.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameObjects.Components.Gravity; +using Content.Server.GameObjects.Components.Mobs; +using JetBrains.Annotations; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class GravitySystem: EntitySystem + { +#pragma warning disable 649 + [Dependency] private readonly IMapManager _mapManager; + [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IEntitySystemManager _entitySystemManager; + [Dependency] private readonly IRobustRandom _random; +#pragma warning restore 649 + + private const float GravityKick = 100.0f; + + private const uint ShakeTimes = 10; + + private Dictionary _gridsToShake; + + private float internalTimer = 0.0f; + + public override void Initialize() + { + EntityQuery = new TypeEntityQuery(); + _gridsToShake = new Dictionary(); + } + + public override void Update(float frameTime) + { + internalTimer += frameTime; + var gridsWithGravity = new List(); + foreach (var entity in RelevantEntities) + { + var generator = entity.GetComponent(); + if (generator.NeedsUpdate) + { + generator.UpdateState(); + } + if (generator.Status == GravityGeneratorStatus.On) + { + gridsWithGravity.Add(entity.Transform.GridID); + } + } + + foreach (var grid in _mapManager.GetAllGrids()) + { + if (grid.HasGravity && !gridsWithGravity.Contains(grid.Index)) + { + grid.HasGravity = false; + ScheduleGridToShake(grid.Index, ShakeTimes); + } else if (!grid.HasGravity && gridsWithGravity.Contains(grid.Index)) + { + grid.HasGravity = true; + ScheduleGridToShake(grid.Index, ShakeTimes); + } + } + + if (internalTimer > 0.2f) + { + ShakeGrids(); + internalTimer = 0.0f; + } + } + + private void ScheduleGridToShake(GridId gridId, uint shakeTimes) + { + if (!_gridsToShake.Keys.Contains(gridId)) + { + _gridsToShake.Add(gridId, shakeTimes); + } + else + { + _gridsToShake[gridId] = shakeTimes; + } + // Play the gravity sound + foreach (var player in _playerManager.GetAllPlayers()) + { + if (player.AttachedEntity == null + || player.AttachedEntity.Transform.GridID != gridId) continue; + _entitySystemManager.GetEntitySystem().Play("/Audio/effects/alert.ogg", player.AttachedEntity); + } + } + + private void ShakeGrids() + { + // I have to copy this because C# doesn't allow changing collections while they're + // getting enumerated. + var gridsToShake = new Dictionary(_gridsToShake); + foreach (var gridId in _gridsToShake.Keys) + { + if (_gridsToShake[gridId] == 0) + { + gridsToShake.Remove(gridId); + continue; + } + ShakeGrid(gridId); + gridsToShake[gridId] -= 1; + } + _gridsToShake = gridsToShake; + } + + private void ShakeGrid(GridId gridId) + { + foreach (var player in _playerManager.GetAllPlayers()) + { + if (player.AttachedEntity == null + || player.AttachedEntity.Transform.GridID != gridId + || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent recoil)) + { + continue; + } + + recoil.Kick(new Vector2(_random.NextFloat(), _random.NextFloat()) * GravityKick); + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index 79d0048f76..855084abba 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -1,9 +1,14 @@ using System; +using System.Linq; +using Content.Server.GameObjects; using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components.Stack; +using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; using Content.Server.Throw; +using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.Input; +using Content.Shared.Interfaces; using Content.Shared.Physics; using JetBrains.Annotations; using Robust.Server.GameObjects; @@ -19,6 +24,7 @@ using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Physics; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -33,6 +39,7 @@ namespace Content.Server.GameObjects.EntitySystems #pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IEntitySystemManager _entitySystemManager; + [Dependency] private readonly IServerNotifyManager _notifyManager; #pragma warning restore 649 private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons @@ -50,6 +57,8 @@ namespace Content.Server.GameObjects.EntitySystems input.BindMap.BindFunction(ContentKeyFunctions.Drop, new PointerInputCmdHandler(HandleDrop)); input.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)); input.BindMap.BindFunction(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)); + input.BindMap.BindFunction(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)); + input.BindMap.BindFunction(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)); } /// @@ -126,7 +135,7 @@ namespace Content.Server.GameObjects.EntitySystems var interactionSystem = _entitySystemManager.GetEntitySystem(); - if(interactionSystem.InRangeUnobstructed(coords.ToMap(_mapManager), ent.Transform.WorldPosition, 0f, ignoredEnt: ent)) + if(interactionSystem.InRangeUnobstructed(coords.ToMap(_mapManager), ent.Transform.WorldPosition, ignoredEnt: ent)) if (coords.InRange(_mapManager, ent.Transform.GridPosition, InteractionSystem.InteractionRange)) { handsComp.Drop(handsComp.ActiveIndex, coords); @@ -190,5 +199,53 @@ namespace Content.Server.GameObjects.EntitySystems return true; } + + private void HandleSmartEquipBackpack(ICommonSession session) + { + HandleSmartEquip(session, EquipmentSlotDefines.Slots.BACKPACK); + } + + private void HandleSmartEquipBelt(ICommonSession session) + { + HandleSmartEquip(session, EquipmentSlotDefines.Slots.BELT); + } + + private void HandleSmartEquip(ICommonSession session, EquipmentSlotDefines.Slots equipementSlot) + { + var plyEnt = ((IPlayerSession) session).AttachedEntity; + + if (plyEnt == null || !plyEnt.IsValid()) + return; + + if (!plyEnt.TryGetComponent(out HandsComponent handsComp) || !plyEnt.TryGetComponent(out InventoryComponent inventoryComp)) + return; + + if (!inventoryComp.TryGetSlotItem(equipementSlot, out ItemComponent equipmentItem) + || !equipmentItem.Owner.TryGetComponent(out var storageComponent)) + { + _notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("You have no {0} to take something out of!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); + return; + } + + var heldItem = handsComp.GetHand(handsComp.ActiveIndex)?.Owner; + + if (heldItem != null) + { + storageComponent.PlayerInsertEntity(plyEnt); + } + else + { + if (storageComponent.StoredEntities.Count == 0) + { + _notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("There's nothing in your {0} to take out!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); + } + else + { + var lastStoredEntity = Enumerable.Last(storageComponent.StoredEntities); + if (storageComponent.Remove(lastStoredEntity)) + handsComp.PutInHandOrDrop(lastStoredEntity.GetComponent()); + } + } + } } } diff --git a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs index 65282a8a74..bdb05b95ce 100644 --- a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs @@ -1,4 +1,6 @@ -using Content.Server.GameObjects.Components; +using System; +using System.Net; +using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.Components.Sound; @@ -15,6 +17,7 @@ using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Timing; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Input; @@ -28,6 +31,7 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Network; +using Robust.Shared.Physics; using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -44,6 +48,7 @@ namespace Content.Server.GameObjects.EntitySystems [Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IRobustRandom _robustRandom; [Dependency] private readonly IConfigurationManager _configurationManager; + [Dependency] private readonly IEntityManager _entityManager; #pragma warning restore 649 private AudioSystem _audioSystem; @@ -130,13 +135,43 @@ namespace Content.Server.GameObjects.EntitySystems } var mover = entity.GetComponent(); var physics = entity.GetComponent(); - - UpdateKinematics(entity.Transform, mover, physics); + if (entity.TryGetComponent(out var collider)) + { + UpdateKinematics(entity.Transform, mover, physics, collider); + } + else + { + UpdateKinematics(entity.Transform, mover, physics); + } } } - private void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, PhysicsComponent physics) + private void UpdateKinematics(ITransformComponent transform, IMoverComponent mover, PhysicsComponent physics, CollidableComponent collider = null) { + bool weightless = false; + + var tile = _mapManager.GetGrid(transform.GridID).GetTileRef(transform.GridPosition).Tile; + + if ((!_mapManager.GetGrid(transform.GridID).HasGravity || tile.IsEmpty) && collider != null) + { + weightless = true; + // No gravity: is our entity touching anything? + var touching = false; + foreach (var entity in _entityManager.GetEntitiesInRange(transform.Owner, mover.GrabRange, true)) + { + if (entity.TryGetComponent(out var otherCollider)) + { + if (otherCollider.Owner == transform.Owner) continue; // Don't try to push off of yourself! + touching |= ((collider.CollisionMask & otherCollider.CollisionLayer) != 0x0 + || (otherCollider.CollisionMask & collider.CollisionLayer) != 0x0) // Ensure collision + && !entity.HasComponent(); // This can't be an item + } + } + if (!touching) + { + return; + } + } if (mover.VelocityDir.LengthSquared < 0.001 || !ActionBlockerSystem.CanMove(mover.Owner)) { if (physics.LinearVelocity != Vector2.Zero) @@ -145,6 +180,13 @@ namespace Content.Server.GameObjects.EntitySystems } else { + if (weightless) + { + physics.LinearVelocity = mover.VelocityDir * mover.CurrentPushSpeed; + transform.LocalRotation = mover.VelocityDir.GetDir().ToAngle(); + return; + } + physics.LinearVelocity = mover.VelocityDir * (mover.Sprinting ? mover.CurrentSprintSpeed : mover.CurrentWalkSpeed); transform.LocalRotation = mover.VelocityDir.GetDir().ToAngle(); diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index b880b162a0..0075f7ae1b 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,11 +1,14 @@ -namespace Content.Server.GameTicking +using System.Collections.Generic; +using Robust.Server.Interfaces.Player; + +namespace Content.Server.GameTicking { /// /// A round-start setup preset, such as which antagonists to spawn. /// public abstract class GamePreset { - public abstract void Start(); + public abstract bool Start(IReadOnlyList players); public virtual string ModeTitle => "Sandbox"; public virtual string Description => "Secret!"; } diff --git a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs index 5b866dc8fa..f45fa125b3 100644 --- a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs +++ b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs @@ -1,5 +1,7 @@ -using Content.Server.GameTicking.GameRules; +using System.Collections.Generic; +using Content.Server.GameTicking.GameRules; using Content.Server.Interfaces.GameTicking; +using Robust.Server.Interfaces.Player; using Robust.Shared.IoC; namespace Content.Server.GameTicking.GamePresets @@ -10,9 +12,10 @@ namespace Content.Server.GameTicking.GamePresets [Dependency] private readonly IGameTicker _gameTicker; #pragma warning restore 649 - public override void Start() + public override bool Start(IReadOnlyList readyPlayers) { _gameTicker.AddGameRule(); + return true; } public override string ModeTitle => "Deathmatch"; diff --git a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs index 05f15c6972..2eeab4a049 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs @@ -1,4 +1,6 @@ -using Content.Server.Sandbox; +using System.Collections.Generic; +using Content.Server.Sandbox; +using Robust.Server.Interfaces.Player; using Robust.Shared.IoC; namespace Content.Server.GameTicking.GamePresets @@ -9,9 +11,10 @@ namespace Content.Server.GameTicking.GamePresets [Dependency] private readonly ISandboxManager _sandboxManager; #pragma warning restore 649 - public override void Start() + public override bool Start(IReadOnlyList readyPlayers) { _sandboxManager.IsSandboxEnabled = true; + return true; } public override string ModeTitle => "Sandbox"; diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs new file mode 100644 index 0000000000..d4692cecf8 --- /dev/null +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameTicking.GameRules; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameTicking; +using Content.Server.Mobs.Roles; +using Content.Server.Players; +using Content.Server.Sandbox; +using NFluidsynth; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Random; +using Logger = Robust.Shared.Log.Logger; + +namespace Content.Server.GameTicking.GamePresets +{ + public class PresetSuspicion : GamePreset + { +#pragma warning disable 649 + [Dependency] private readonly ISandboxManager _sandboxManager; + [Dependency] private readonly IChatManager _chatManager; + [Dependency] private readonly IGameTicker _gameTicker; + [Dependency] private readonly IRobustRandom _random; +#pragma warning restore 649 + + public int MinPlayers { get; set; } = 5; + public int MinTraitors { get; set; } = 2; + public int PlayersPerTraitor { get; set; } = 5; + + public override bool Start(IReadOnlyList readyPlayers) + { + if (readyPlayers.Count < MinPlayers) + { + _chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed."); + return false; + } + + var list = new List(readyPlayers); + var numTraitors = Math.Max(readyPlayers.Count() % PlayersPerTraitor, MinTraitors); + + for (var i = 0; i < numTraitors; i++) + { + var traitor = _random.PickAndTake(list); + var mind = traitor.Data.ContentData().Mind; + mind.AddRole(new SuspicionTraitorRole(mind)); + } + + foreach (var player in list) + { + var mind = player.Data.ContentData().Mind; + mind.AddRole(new SuspicionInnocentRole(mind)); + } + + _gameTicker.AddGameRule(); + return true; + } + + public override string ModeTitle => "Suspicion"; + public override string Description => "Suspicion on the Space Station. There are traitors on board... Can you kill them before they kill you?"; + } +} diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs new file mode 100644 index 0000000000..91b16f3aaf --- /dev/null +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading; +using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Observer; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameTicking; +using Content.Server.Mobs.Roles; +using Content.Server.Players; +using NFluidsynth; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Logger = Robust.Shared.Log.Logger; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Server.GameTicking.GameRules +{ + /// + /// Simple GameRule that will do a free-for-all death match. + /// Kill everybody else to win. + /// + public sealed class RuleSuspicion : GameRule, IEntityEventSubscriber + { + private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1); + +#pragma warning disable 649 + [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IChatManager _chatManager; + [Dependency] private readonly IEntityManager _entityManager; + [Dependency] private readonly IGameTicker _gameTicker; +#pragma warning restore 649 + + private readonly CancellationTokenSource _checkTimerCancel = new CancellationTokenSource(); + + public override void Added() + { + _chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!"); + + _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _onMobDamageStateChanged); + + Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); + } + + private void _onMobDamageStateChanged(MobDamageStateChangedMessage message) + { + var owner = message.Species.Owner; + + if (!(message.Species.CurrentDamageState is DeadState)) + return; + + if (!owner.TryGetComponent(out var mind)) + return; + + if (!mind.HasMind) + return; + + message.Species.Owner.Description += + mind.Mind.HasRole() ? "\nThey were a traitor!" : "\nThey were an innocent!"; + } + + public override void Removed() + { + base.Removed(); + + _checkTimerCancel.Cancel(); + } + + private void _checkWinConditions() + { + var traitorsAlive = 0; + var innocentsAlive = 0; + + foreach (var playerSession in _playerManager.GetAllPlayers()) + { + if (playerSession.AttachedEntity == null + || !playerSession.AttachedEntity.TryGetComponent(out SpeciesComponent species)) + { + continue; + } + + if (!species.CurrentDamageState.IsConscious) + { + continue; + } + + if (playerSession.ContentData().Mind.HasRole()) + traitorsAlive++; + else + innocentsAlive++; + } + + if ((innocentsAlive + traitorsAlive) == 0) + { + _chatManager.DispatchServerAnnouncement("Everybody is dead, it's a stalemate!"); + EndRound(); + } + + else if (traitorsAlive == 0) + { + _chatManager.DispatchServerAnnouncement("The traitors are dead! The innocents win."); + EndRound(); + } + else if (innocentsAlive == 0) + { + _chatManager.DispatchServerAnnouncement("The innocents are dead! The traitors win."); + EndRound(); + } + } + + private void EndRound() + { + _gameTicker.EndRound(); + _chatManager.DispatchServerAnnouncement($"Restarting in 10 seconds."); + _checkTimerCancel.Cancel(); + Timer.Spawn(TimeSpan.FromSeconds(10), () => _gameTicker.RestartRound()); + } + } +} diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 764c65633a..c95211205d 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -104,7 +104,8 @@ namespace Content.Server.GameTicking _configurationManager.RegisterCVar("game.lobbyenabled", false, CVar.ARCHIVE); _configurationManager.RegisterCVar("game.lobbyduration", 20, CVar.ARCHIVE); - _configurationManager.RegisterCVar("game.defaultpreset", "Sandbox", CVar.ARCHIVE); + _configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE); + _configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE); _playerManager.PlayerStatusChanged += _handlePlayerStatusChanged; @@ -181,11 +182,6 @@ namespace Content.Server.GameTicking SendServerMessage("The round is starting now..."); - RunLevel = GameRunLevel.InRound; - - var preset = MakeGamePreset(); - preset.Start(); - List readyPlayers; if (LobbyEnabled) { @@ -196,6 +192,8 @@ namespace Content.Server.GameTicking readyPlayers = _playersInLobby.Keys.ToList(); } + RunLevel = GameRunLevel.InRound; + // Get the profiles for each player for easier lookup. var profiles = readyPlayers.ToDictionary(p => p, GetPlayerProfile); @@ -222,6 +220,18 @@ namespace Content.Server.GameTicking SpawnPlayer(player, job, false); } + // Time to start the preset. + var preset = MakeGamePreset(); + + if (!preset.Start(assignedJobs.Keys.ToList())) + { + SetStartPreset(_configurationManager.GetCVar("game.fallbackpreset")); + var newPreset = MakeGamePreset(); + _chatManager.DispatchServerAnnouncement($"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}..."); + if(!newPreset.Start(readyPlayers)) + throw new ApplicationException("Fallback preset failed to start!"); + } + _roundStartTimeSpan = IoCManager.Resolve().RealTime; _sendStatusToAll(); } @@ -255,15 +265,16 @@ namespace Content.Server.GameTicking var listOfPlayerInfo = new List(); foreach(var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name)) { - if(ply.AttachedEntity.TryGetComponent(out var mindComponent) - && mindComponent.HasMind) + var mind = ply.ContentData().Mind; + if(mind != null) { + var antag = mind.AllRoles.Any(role => role.Antag); var playerEndRoundInfo = new RoundEndPlayerInfo() { PlayerOOCName = ply.Name, - PlayerICName = mindComponent.Mind.CurrentEntity.Name, - Role = mindComponent.Mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"), - Antag = false + PlayerICName = mind.CurrentEntity.Name, + Role = antag ? mind.AllRoles.First(role => role.Antag).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"), + Antag = antag }; listOfPlayerInfo.Add(playerEndRoundInfo); } @@ -339,6 +350,7 @@ namespace Content.Server.GameTicking { "Sandbox" => typeof(PresetSandbox), "DeathMatch" => typeof(PresetDeathMatch), + "Suspicion" => typeof(PresetSuspicion), _ => throw new NotSupportedException() }); diff --git a/Content.Server/Interfaces/GameObjects/Components/Movement/IMoverComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Movement/IMoverComponent.cs index 5e3ed05180..9a5196ad3f 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Movement/IMoverComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Movement/IMoverComponent.cs @@ -19,6 +19,17 @@ namespace Content.Server.Interfaces.GameObjects.Components.Movement /// float CurrentSprintSpeed { get; } + + /// + /// The movement speed (m/s) of the entity when it pushes off of a solid object in zero gravity. + /// + float CurrentPushSpeed { get; } + + /// + /// How far an entity can reach (in meters) to grab hold of a solid object in zero gravity. + /// + float GrabRange { get; } + /// /// Is the entity Sprinting (running)? /// diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index e931b80fbc..5e631f68ec 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Linq; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; using Content.Server.Players; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.Mobs @@ -130,6 +133,13 @@ namespace Content.Server.Mobs _roles.Remove(role); } + public bool HasRole() where T : Role + { + var t = typeof(T); + + return _roles.Any(role => role.GetType() == t); + } + /// /// Transfer this mind's control over to a new entity. /// diff --git a/Content.Server/Mobs/Role.cs b/Content.Server/Mobs/Role.cs index c7f0c617a7..f78b579f11 100644 --- a/Content.Server/Mobs/Role.cs +++ b/Content.Server/Mobs/Role.cs @@ -1,6 +1,9 @@ // Hey look, // Antag Datums. +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.Utility; + namespace Content.Server.Mobs { /// @@ -20,6 +23,11 @@ namespace Content.Server.Mobs /// public abstract string Name { get; } + /// + /// Whether this role should be considered antagonistic or not. + /// + public abstract bool Antag { get; } + protected Role(Mind mind) { Mind = mind; diff --git a/Content.Server/Mobs/Roles/Job.cs b/Content.Server/Mobs/Roles/Job.cs index 53c7ca721c..680a91ff20 100644 --- a/Content.Server/Mobs/Roles/Job.cs +++ b/Content.Server/Mobs/Roles/Job.cs @@ -11,8 +11,9 @@ namespace Content.Server.Mobs.Roles public JobPrototype Prototype { get; } public override string Name { get; } + public override bool Antag => false; - public String StartingGear => Prototype.StartingGear; + public string StartingGear => Prototype.StartingGear; public Job(Mind mind, JobPrototype jobPrototype) : base(mind) { @@ -25,9 +26,7 @@ namespace Content.Server.Mobs.Roles base.Greet(); var chat = IoCManager.Resolve(); - chat.DispatchServerMessage( - Mind.Session, - String.Format("You're a new {0}. Do your best!", Name)); + chat.DispatchServerMessage(Mind.Session, $"You're a new {Name}. Do your best!"); } } diff --git a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs new file mode 100644 index 0000000000..177a8d3bbd --- /dev/null +++ b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects; +using Content.Server.Interfaces.Chat; +using Robust.Shared.IoC; +using Robust.Shared.Utility; + +namespace Content.Server.Mobs.Roles +{ + public class SuspicionInnocentRole : Role + { + public SuspicionInnocentRole(Mind mind) : base(mind) + { + } + + public override string Name => "Innocent"; + public override bool Antag => false; + + public override void Greet() + { + base.Greet(); + + var chat = IoCManager.Resolve(); + chat.DispatchServerMessage(Mind.Session, "You're an innocent!"); + } + } +} diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs new file mode 100644 index 0000000000..65c1e10306 --- /dev/null +++ b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects; +using Content.Server.Interfaces.Chat; +using Robust.Shared.IoC; +using Robust.Shared.Utility; + +namespace Content.Server.Mobs.Roles +{ + public sealed class SuspicionTraitorRole : Role + { + public SuspicionTraitorRole(Mind mind) : base(mind) + { + } + + public override string Name => "Traitor"; + public override bool Antag => true; + + public override void Greet() + { + base.Greet(); + + var chat = IoCManager.Resolve(); + chat.DispatchServerMessage(Mind.Session, "You're a traitor!"); + } + } +} diff --git a/Content.Server/Mobs/Roles/Traitor.cs b/Content.Server/Mobs/Roles/Traitor.cs deleted file mode 100644 index 16804d497f..0000000000 --- a/Content.Server/Mobs/Roles/Traitor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.Interfaces.Chat; -using Robust.Shared.IoC; - -namespace Content.Server.Mobs.Roles -{ - public sealed class Traitor : Role - { - public Traitor(Mind mind) : base(mind) - { - } - - public override string Name => "Traitor"; - - public override void Greet() - { - base.Greet(); - - var chat = IoCManager.Resolve(); - chat.DispatchServerMessage( - Mind.Session, - "You're a traitor. Go fuck something up. Or something. I don't care to be honest."); - } - } -} diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index f4bb04b9cc..7ca422e4c4 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -8,6 +8,7 @@ using Content.Server.Preferences; using Content.Server.Sandbox; using Content.Server.Utility; using Content.Shared.Chemistry; +using Content.Shared.Kitchen; using Content.Shared.Interfaces; using Content.Shared.Interfaces.Chemistry; using Robust.Shared.IoC; @@ -28,6 +29,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/Throw/ThrowHelper.cs b/Content.Server/Throw/ThrowHelper.cs index 0e402da225..77484fa656 100644 --- a/Content.Server/Throw/ThrowHelper.cs +++ b/Content.Server/Throw/ThrowHelper.cs @@ -65,6 +65,14 @@ namespace Content.Server.Throw var spd = a / (1f / timing.TickRate); // acceleration is applied in 1 tick instead of 1 second, scale appropriately physComp.LinearVelocity = angle.ToVec() * spd; + + if (throwSourceEnt != null) + { + var p = throwSourceEnt.GetComponent(); + var playerAccel = 5 * throwForce / (float) Math.Max(0.001, p.Mass); + p.LinearVelocity = Angle.FromDegrees(angle.Degrees + 180).ToVec() + * playerAccel / (1f / timing.TickRate); + } } } } diff --git a/Content.Shared/Audio/AudioHelpers.cs b/Content.Shared/Audio/AudioHelpers.cs new file mode 100644 index 0000000000..f6a920f479 --- /dev/null +++ b/Content.Shared/Audio/AudioHelpers.cs @@ -0,0 +1,22 @@ +using System; +using Content.Shared.GameObjects.Components.Sound; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Random; + +namespace Content.Shared.Audio +{ + public static class AudioHelpers{ + /// + /// Returns a random pitch. + /// + public static AudioParams WithVariation(float amplitude) + { + var scale = (float)(IoCManager.Resolve().NextGaussian(1, amplitude)); + return AudioParams.Default.WithPitchScale(scale); + } + } +} diff --git a/Content.Shared/EntryPoint.cs b/Content.Shared/EntryPoint.cs index a9df20ff2a..f2a1e7312e 100644 --- a/Content.Shared/EntryPoint.cs +++ b/Content.Shared/EntryPoint.cs @@ -1,23 +1,36 @@ using System; using System.Collections.Generic; + using System.Globalization; using Content.Shared.Maps; using Robust.Shared.ContentPack; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; + using Robust.Shared.Localization; using Robust.Shared.Prototypes; namespace Content.Shared { public class EntryPoint : GameShared { + // If you want to change your codebase's language, do it here. + private const string Culture = "en-US"; + #pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; + [Dependency] private readonly ILocalizationManager _localizationManager; #pragma warning restore 649 + public override void PreInit() + { + IoCManager.InjectDependencies(this); + + // Default to en-US. + _localizationManager.LoadCulture(new CultureInfo(Culture)); + } + public override void Init() { - IoCManager.InjectDependencies(this); } public override void PostInit() diff --git a/Content.Shared/GameObjects/Components/Gravity/SharedGravityGeneratorComponent.cs b/Content.Shared/GameObjects/Components/Gravity/SharedGravityGeneratorComponent.cs new file mode 100644 index 0000000000..344a6f68ce --- /dev/null +++ b/Content.Shared/GameObjects/Components/Gravity/SharedGravityGeneratorComponent.cs @@ -0,0 +1,57 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Gravity +{ + public class SharedGravityGeneratorComponent: Component + { + public override string Name => "GravityGenerator"; + + public override uint? NetID => ContentNetIDs.GRAVITY_GENERATOR; + + /// + /// Sent to the server to set whether the generator should be on or off + /// + [Serializable, NetSerializable] + public class SwitchGeneratorMessage : BoundUserInterfaceMessage + { + public bool On; + + public SwitchGeneratorMessage(bool on) + { + On = on; + } + } + + /// + /// Sent to the server when requesting the status of the generator + /// + [Serializable, NetSerializable] + public class GeneratorStatusRequestMessage : BoundUserInterfaceMessage + { + public GeneratorStatusRequestMessage() + { + + } + } + + [Serializable, NetSerializable] + public class GeneratorState : BoundUserInterfaceState + { + public bool On; + + public GeneratorState(bool on) + { + On = on; + } + } + + [Serializable, NetSerializable] + public enum GravityGeneratorUiKey + { + Key + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index f8f70db30b..aea8e57bdd 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -42,5 +42,7 @@ public const uint PAPER = 1037; public const uint REAGENT_INJECTOR = 1038; public const uint GHOST = 1039; + public const uint MICROWAVE = 1040; + public const uint GRAVITY_GENERATOR = 1041; } } diff --git a/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs index 91947c35c0..aa2f88c01a 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedInteractionSystem.cs @@ -51,13 +51,17 @@ namespace Content.Server.GameObjects.EntitySystems var rayResults = _physicsManager.IntersectRayWithPredicate(coords.MapId, ray, dir.Length, predicate, true); if(!rayResults.DidHitObject || (insideBlockerValid && rayResults.DidHitObject && (rayResults.HitPos - otherCoords).Length < 1f)) { - _mapManager.TryFindGridAt(coords, out var mapGrid); - var srcPos = mapGrid.MapToGrid(coords); - var destPos = new GridCoordinates(otherCoords, mapGrid); - if (srcPos.InRange(_mapManager, destPos, range)) + + if (_mapManager.TryFindGridAt(coords, out var mapGrid) && mapGrid != null) { - return true; + var srcPos = mapGrid.MapToGrid(coords); + var destPos = new GridCoordinates(otherCoords, mapGrid); + if (srcPos.InRange(_mapManager, destPos, range)) + { + return true; + } } + } return false; } diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index a0ae771de3..d44cb1d64d 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -5,8 +5,7 @@ namespace Content.Shared.Input [KeyFunctions] public static class ContentKeyFunctions { - public static readonly BoundKeyFunction UseOrAttack = "UseOrAttack"; - public static readonly BoundKeyFunction Attack = "Attack"; + public static readonly BoundKeyFunction WideAttack = "WideAttack"; public static readonly BoundKeyFunction ActivateItemInHand = "ActivateItemInHand"; public static readonly BoundKeyFunction ActivateItemInWorld = "ActivateItemInWorld"; // default action on world entity public static readonly BoundKeyFunction Drop = "Drop"; @@ -16,6 +15,8 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction OpenContextMenu = "OpenContextMenu"; public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu"; public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu"; + public static readonly BoundKeyFunction SmartEquipBackpack = "SmartEquipBackpack"; + public static readonly BoundKeyFunction SmartEquipBelt = "SmartEquipBelt"; public static readonly BoundKeyFunction OpenTutorial = "OpenTutorial"; public static readonly BoundKeyFunction SwapHands = "SwapHands"; public static readonly BoundKeyFunction ThrowItemInHand = "ThrowItemInHand"; @@ -24,5 +25,7 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction OpenEntitySpawnWindow = "OpenEntitySpawnWindow"; public static readonly BoundKeyFunction OpenSandboxWindow = "OpenSandboxWindow"; public static readonly BoundKeyFunction OpenTileSpawnWindow = "OpenTileSpawnWindow"; + public static readonly BoundKeyFunction TakeScreenshot = "TakeScreenshot"; + public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI"; } } diff --git a/Content.Shared/Kitchen/RecipeManager.cs b/Content.Shared/Kitchen/RecipeManager.cs new file mode 100644 index 0000000000..5ac01ad64f --- /dev/null +++ b/Content.Shared/Kitchen/RecipeManager.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Prototypes.Kitchen; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Kitchen +{ + + public class RecipeManager + { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager; +#pragma warning restore 649 + public List Recipes { get; private set; } + + public void Initialize() + { + Recipes = new List(); + foreach (var item in _prototypeManager.EnumeratePrototypes()) + { + Recipes.Add(item); + } + + Recipes.Sort(new RecipeComparer()); + } + private class RecipeComparer : Comparer + { + public override int Compare(FoodRecipePrototype x, FoodRecipePrototype y) + { + if (x == null || y == null) + { + return 0; + } + + return -x.IngredientsReagents.Count.CompareTo(y.IngredientsReagents.Count); + } + } + } +} diff --git a/Content.Shared/Kitchen/SharedMicrowaveComponent.cs b/Content.Shared/Kitchen/SharedMicrowaveComponent.cs new file mode 100644 index 0000000000..31349c86b5 --- /dev/null +++ b/Content.Shared/Kitchen/SharedMicrowaveComponent.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.GameObjects.Components.UserInterface; + +namespace Content.Shared.Kitchen +{ + + public class SharedMicrowaveComponent : Component + { + + public override string Name => "Microwave"; + public override uint? NetID => ContentNetIDs.MICROWAVE; + + [Serializable, NetSerializable] + public class MicrowaveStartCookMessage : BoundUserInterfaceMessage + { + public MicrowaveStartCookMessage() + { + } + } + + [Serializable, NetSerializable] + public class MicrowaveEjectMessage : BoundUserInterfaceMessage + { + public MicrowaveEjectMessage() + { + } + } + + [Serializable, NetSerializable] + public class MicrowaveEjectSolidIndexedMessage : BoundUserInterfaceMessage + { + + public EntityUid EntityID; + public MicrowaveEjectSolidIndexedMessage(EntityUid entityID) + { + EntityID = entityID; + } + } + + [Serializable, NetSerializable] + public class MicrowaveVaporizeReagentIndexedMessage : BoundUserInterfaceMessage + { + + public Solution.ReagentQuantity ReagentQuantity; + public MicrowaveVaporizeReagentIndexedMessage(Solution.ReagentQuantity reagentQuantity) + { + ReagentQuantity = reagentQuantity; + } + } + [Serializable, NetSerializable] + public class MicrowaveSelectCookTimeMessage : BoundUserInterfaceMessage + { + public uint newCookTime; + public MicrowaveSelectCookTimeMessage(uint inputTime) + { + newCookTime = inputTime; + } + } + } + + + + [NetSerializable, Serializable] + public class MicrowaveUpdateUserInterfaceState : BoundUserInterfaceState + { + public readonly IReadOnlyList ReagentsReagents; + public readonly List ContainedSolids; + public MicrowaveUpdateUserInterfaceState(IReadOnlyList reagents, List solids) + { + ReagentsReagents = reagents; + ContainedSolids = solids; + } + } + + [Serializable, NetSerializable] + public enum MicrowaveVisualState + { + Idle, + Cooking + } + + [NetSerializable, Serializable] + public enum MicrowaveUiKey + { + Key + } + +} diff --git a/Content.Shared/Prototypes/Kitchen/MicrowaveMealRecipePrototype.cs b/Content.Shared/Prototypes/Kitchen/MicrowaveMealRecipePrototype.cs new file mode 100644 index 0000000000..0d64a7d073 --- /dev/null +++ b/Content.Shared/Prototypes/Kitchen/MicrowaveMealRecipePrototype.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Prototypes.Kitchen +{ + /// + /// A recipe for space microwaves. + /// + + [Prototype("microwaveMealRecipe")] + + public class FoodRecipePrototype : IPrototype, IIndexedPrototype + { + + private string _id; + private string _name; + private string _result; + private int _cookTime; + + private Dictionary _ingsReagents; + private Dictionary _ingsSolids; + + public string Name => Loc.GetString(_name); + public string ID => _id; + public string Result => _result; + public int CookTime => _cookTime; + public IReadOnlyDictionary IngredientsReagents => _ingsReagents; + public IReadOnlyDictionary IngredientsSolids => _ingsSolids; + + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _result, "result", string.Empty); + serializer.DataField(ref _ingsReagents, "reagents", new Dictionary()); + serializer.DataField(ref _ingsSolids, "solids", new Dictionary()); + serializer.DataField(ref _cookTime, "time", 5); + } + + } +} diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 38d3e0985e..565a39ee70 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -138,7 +138,7 @@ namespace Content.Shared public string GamemodeTitle; public TimeSpan RoundDuration; - + public uint PlayerCount; diff --git a/Resources/Audio/effects/alert.ogg b/Resources/Audio/effects/alert.ogg new file mode 100644 index 0000000000..69bc52bdb8 Binary files /dev/null and b/Resources/Audio/effects/alert.ogg differ diff --git a/Resources/Audio/machines/microwave_done_beep.ogg b/Resources/Audio/machines/microwave_done_beep.ogg new file mode 100644 index 0000000000..e11bd45107 Binary files /dev/null and b/Resources/Audio/machines/microwave_done_beep.ogg differ diff --git a/Resources/Audio/machines/microwave_loop.ogg b/Resources/Audio/machines/microwave_loop.ogg new file mode 100644 index 0000000000..d72097e73b Binary files /dev/null and b/Resources/Audio/machines/microwave_loop.ogg differ diff --git a/Resources/Audio/machines/microwave_start_beep.ogg b/Resources/Audio/machines/microwave_start_beep.ogg new file mode 100644 index 0000000000..43f6c24660 Binary files /dev/null and b/Resources/Audio/machines/microwave_start_beep.ogg differ diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index 50b84be17f..7e3688ef71 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -134,3 +134,4 @@ - gc_mode CanViewVar: true CanAdminPlace: true + CanScript: true diff --git a/Resources/Locale/nl-NL/tools.yml b/Resources/Locale/nl-NL/tools.yml new file mode 100644 index 0000000000..6a9f974c3f --- /dev/null +++ b/Resources/Locale/nl-NL/tools.yml @@ -0,0 +1,19 @@ +# Example Dutch translations + +- msgid: Wrench + msgstr: Moersleutel + +- msgid: Welding Tool + msgstr: Lasapparaat + +- msgid: Crowbar + msgstr: Koevoet + +- msgid: Screwdriver + msgstr: Schroevendraaier + +- msgid: Wirecutters + msgstr: Draadtang + +- msgid: Multitool + msgstr: Multi Tool # This is what google translate gives me idk. diff --git a/Resources/Maps/stationstation.yml b/Resources/Maps/stationstation.yml index e7402cdbde..3084a36a4e 100644 --- a/Resources/Maps/stationstation.yml +++ b/Resources/Maps/stationstation.yml @@ -62,8 +62,9 @@ tilemap: 55: floor_steel_dirty 56: floor_techmaint 57: floor_white - 58: plating - 59: underplating + 58: floor_wood + 59: plating + 60: underplating grids: - settings: chunksize: 16 @@ -71,29 +72,29 @@ grids: snapsize: 1 chunks: - ind: "-1,0" - tiles: NgAAADYAAAA2AAAANgAAADYAAAA7AAAAOwAAADsAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAOwAAADgAAAA7AAAAOwAAADsAAAA7AAAANgAAADYAAAA2AAAANgAAADsAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA7AAAAOwAAADsAAAA7AAAAOwAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAADsAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAADYAAAA2AAAANgAAAAAAAAAAAAAAAAAAAAAAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + tiles: NgAAADYAAAA2AAAANgAAADYAAAA8AAAAPAAAADwAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAPAAAADgAAAA8AAAAPAAAADwAAAA8AAAANgAAADYAAAA2AAAANgAAADwAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA8AAAAPAAAADwAAAA8AAAAPAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAADwAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAADYAAAA2AAAANgAAAAAAAAAAAAAAAAAAAAAAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "-1,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADgAAAA4AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOwAAADsAAAA4AAAAOAAAADsAAAA4AAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOwAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADYAAAA2AAAANgAAADYAAAA2AAAAOwAAADsAAAA7AAAAOwAAADsAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADsAAAA4AAAAOwAAADsAAAA7AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADsAAAA7AAAAOwAAADsAAAA4AAAAOwAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADYAAAA2AAAANgAAADYAAAA2AAAAOwAAADgAAAA7AAAAOwAAADsAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA2AAAANgAAADYAAAA2AAAANgAAADsAAAA4AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA7AAAAOAAAADsAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAOwAAADgAAAA7AAAAOwAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADsAAAA4AAAAOwAAADsAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA7AAAAOAAAADsAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADgAAAA4AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAPAAAADwAAAA4AAAAOAAAADwAAAA4AAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAPAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADYAAAA2AAAANgAAADYAAAA2AAAAPAAAADwAAAA8AAAAPAAAADwAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADwAAAA4AAAAPAAAADwAAAA8AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADwAAAA8AAAAPAAAADwAAAA4AAAAPAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADYAAAA2AAAANgAAADYAAAA2AAAAPAAAADgAAAA8AAAAPAAAADwAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA2AAAANgAAADYAAAA2AAAANgAAADwAAAA4AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA8AAAAOAAAADwAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAPAAAADgAAAA8AAAAPAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADwAAAA4AAAAPAAAADwAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA8AAAAOAAAADwAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAA== - ind: "-1,1" tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "0,1" - tiles: AAAAADsAAAA2AAAANgAAADsAAAA7AAAAOwAAADsAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAANgAAAAAAAAA7AAAANgAAADYAAAA7AAAAOAAAADsAAAA7AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAADkAAAAAAAAAOwAAADYAAAA2AAAAOwAAADgAAAA7AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA7AAAAAAAAADsAAAA2AAAANgAAADsAAAA4AAAAOwAAADsAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAOQAAAAAAAAA7AAAANgAAADYAAAA7AAAAOAAAADsAAAA4AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAAAAAAAOwAAADYAAAA2AAAAOwAAADgAAAA7AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA5AAAAAAAAADsAAAA2AAAANgAAADsAAAA4AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOQAAAAAAAAA7AAAANgAAADYAAAA7AAAAOAAAADsAAAA4AAAAOAAAADgAAAA7AAAAOAAAADgAAAA7AAAAOwAAADkAAAAAAAAAOwAAADYAAAA2AAAAOwAAADgAAAA7AAAAOAAAADgAAAA4AAAAOwAAADgAAAA4AAAAOwAAADkAAAA5AAAAAAAAAAAAAAAAAAAAAAAAADsAAAA4AAAAOwAAADgAAAA4AAAAOAAAADsAAAA4AAAAOAAAADsAAAA7AAAAOQAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOAAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADkAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADgAAAA7AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA5AAAAAAAAAAAAAAAAAAAAAAAAADsAAAA4AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + tiles: AAAAADwAAAA2AAAANgAAADwAAAA8AAAAPAAAADwAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAANgAAAAAAAAA8AAAANgAAADYAAAA8AAAAOAAAADwAAAA8AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAADkAAAAAAAAAPAAAADYAAAA2AAAAPAAAADgAAAA8AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA8AAAAAAAAADwAAAA2AAAANgAAADwAAAA4AAAAPAAAADwAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAOQAAAAAAAAA8AAAANgAAADYAAAA8AAAAOAAAADwAAAA4AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAAAAAAAPAAAADYAAAA2AAAAPAAAADgAAAA8AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA5AAAAAAAAADwAAAA2AAAANgAAADwAAAA4AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAOQAAAAAAAAA8AAAANgAAADYAAAA8AAAAOAAAADwAAAA4AAAAOAAAADgAAAA8AAAAOAAAADgAAAA8AAAAPAAAADkAAAAAAAAAPAAAADYAAAA2AAAAPAAAADgAAAA8AAAAOAAAADgAAAA4AAAAPAAAADgAAAA4AAAAPAAAADkAAAA5AAAAAAAAAAAAAAAAAAAAAAAAADwAAAA4AAAAPAAAADgAAAA4AAAAOAAAADwAAAA4AAAAOAAAADwAAAA8AAAAOQAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADkAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADgAAAA8AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA5AAAAAAAAAAAAAAAAAAAAAAAAADwAAAA4AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "0,0" - tiles: NgAAADsAAAA2AAAANgAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADkAAAA5AAAAOQAAADsAAAA7AAAANgAAADYAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA5AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAOQAAADYAAAA2AAAANgAAADYAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA5AAAAOQAAADYAAAA2AAAANgAAADYAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAADkAAAA7AAAAOwAAADYAAAA2AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADkAAAA5AAAAOwAAADsAAAA2AAAANgAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA5AAAAOQAAADsAAAA4AAAANgAAADYAAAA4AAAAOwAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADsAAAA4AAAAOQAAADkAAAA7AAAAOwAAADYAAAA2AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA2AAAAAAAAADsAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAAAAAAA7AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAOwAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAADsAAAA2AAAANgAAADsAAAA4AAAAOwAAADsAAAA7AAAAOQAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAAA== + tiles: NgAAADwAAAA2AAAANgAAADwAAAA4AAAAOAAAADgAAAA8AAAAAAAAAAAAAAAAAAAAPAAAADkAAAA5AAAAOQAAADwAAAA8AAAANgAAADYAAAA8AAAAOAAAADgAAAA4AAAAPAAAAAAAAAAAAAAAAAAAADwAAAA5AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAA8AAAAOQAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAOQAAADYAAAA2AAAANgAAADYAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA5AAAAOQAAADYAAAA2AAAANgAAADYAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOQAAADkAAAA8AAAAPAAAADYAAAA2AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADkAAAA5AAAAPAAAADwAAAA2AAAANgAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA5AAAAOQAAADwAAAA4AAAANgAAADYAAAA4AAAAPAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADwAAAA4AAAAOQAAADkAAAA8AAAAPAAAADYAAAA2AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA2AAAAAAAAADwAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAAAAAAAA8AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAPAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAADwAAAA2AAAANgAAADwAAAA4AAAAPAAAADwAAAA8AAAAOQAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAA== - ind: "0,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADsAAAA4AAAAOAAAADsAAAA7AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADsAAAA7AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAA4AAAAOAAAADgAAAA7AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAOAAAADsAAAA7AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA7AAAAOwAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAADkAAAA7AAAAOwAAADgAAAA4AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADkAAAA5AAAANgAAADsAAAA2AAAANgAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAA5AAAAOQAAADYAAAA2AAAANgAAADYAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAOQAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADwAAAA4AAAAOAAAADwAAAA8AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAA4AAAAOAAAADgAAAA8AAAAOAAAADgAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAOAAAADgAAAA4AAAAOAAAADwAAAA8AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA8AAAAPAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOQAAADkAAAA8AAAAPAAAADgAAAA4AAAAPAAAADwAAAA8AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADkAAAA5AAAANgAAADwAAAA2AAAANgAAADwAAAA4AAAAOAAAADgAAAA8AAAAAAAAAAAAAAAAAAAAAAAAADwAAAA5AAAAOQAAADYAAAA2AAAANgAAADYAAAA8AAAAOAAAADgAAAA4AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAOQAAADkAAAA2AAAANgAAADYAAAA2AAAAPAAAADgAAAA4AAAAOAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADkAAAA5AAAANgAAADYAAAA2AAAANgAAADwAAAA4AAAAOAAAADgAAAA8AAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAOQAAAA== - ind: "1,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADkAAAA5AAAAOwAAADsAAAA7AAAAOQAAADsAAAA7AAAAOwAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADkAAAA5AAAAPAAAADwAAAA8AAAAOQAAADwAAAA8AAAAPAAAAA== - ind: "-2,0" tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "-2,-1" tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAAA== - ind: "1,0" - tiles: OQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAAAAAAAOQAAADsAAAA7AAAAOwAAADkAAAA7AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA7AAAAOQAAADsAAAA5AAAAOwAAADsAAAA7AAAAOwAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAADkAAAA5AAAAOQAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAA5AAAAOQAAADkAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAA2AAAANgAAADYAAAA2AAAANgAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAAAAAAAAAAAAAAAAAAA== + tiles: OQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAAAAAAAOQAAADwAAAA8AAAAPAAAADkAAAA8AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA8AAAAOQAAADwAAAA5AAAAPAAAADwAAAA8AAAAPAAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAOQAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAADkAAAA5AAAAOQAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAA5AAAAOQAAADkAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAA2AAAANgAAADYAAAA2AAAANgAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAANgAAADYAAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAAAAAAAAAAAAAAAAAA== - ind: "2,-1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAADsAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "1,1" - tiles: NgAAADYAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOwAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + tiles: NgAAADYAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAPAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA5AAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOQAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADkAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== entities: - uid: 0 components: @@ -289,14 +290,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 30 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 27 type: MetalStack components: @@ -320,14 +321,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 170 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 30 type: LightBulb components: @@ -768,6 +769,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 92 type: Wire components: @@ -1177,6 +1180,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 148 type: MedkitFilled components: @@ -1219,6 +1224,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 151 type: FireExtinguisher components: @@ -1663,14 +1670,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 211 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 213 type: ChairOfficeLight components: @@ -1694,14 +1701,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 316 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 216 type: MetalStack components: @@ -1953,14 +1960,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 222 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 252 type: solid_wall components: @@ -2150,9 +2157,6 @@ entities: entities: [] type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer - - magazines: - - A10mmSMG - type: BallisticMagazineWeapon - uid: 270 type: SmgC20r components: @@ -2168,9 +2172,6 @@ entities: entities: [] type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer - - magazines: - - A10mmSMG - type: BallisticMagazineWeapon - uid: 271 type: solid_wall components: @@ -2284,12 +2285,19 @@ entities: rot: -1.5707963267949 rad type: Transform - uid: 287 - type: solid_wall + type: LockerScience components: - parent: 0 - pos: 4.5,-2.5 - rot: -1.5707963267949 rad + pos: 13.5,21.5 + rot: -1.5707963267948966 rad type: Transform + - containers: + EntityStorageComponent: + entities: [] + type: Robust.Server.GameObjects.Components.Container.Container + type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 288 type: solid_wall components: @@ -2502,14 +2510,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 318 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 318 type: LightTube components: @@ -2524,14 +2532,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 320 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 320 type: LightTube components: @@ -2552,14 +2560,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 323 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 323 type: LightBulb components: @@ -2857,6 +2865,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 365 type: LockerToolFilled components: @@ -2869,6 +2879,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 366 type: LockerToolFilled components: @@ -2881,6 +2893,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 367 type: LockerToolFilled components: @@ -2893,6 +2907,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 368 type: LockerToolFilled components: @@ -2905,6 +2921,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 369 type: GlassStack components: @@ -2926,21 +2944,6 @@ entities: pos: -14.5,-7.5 rot: -1.5707963267949 rad type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: LatheDatabase - uid: 372 type: Autolathe components: @@ -2948,21 +2951,6 @@ entities: pos: -13.5,-7.5 rot: -1.5707963267949 rad type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: LatheDatabase - uid: 373 type: Autolathe components: @@ -2970,21 +2958,6 @@ entities: pos: -12.5,-7.5 rot: -1.5707963267949 rad type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: LatheDatabase - uid: 374 type: Poweredlight components: @@ -2994,14 +2967,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 375 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 375 type: LightTube components: @@ -3016,14 +2989,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 377 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 377 type: LightTube components: @@ -3037,14 +3010,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 379 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 379 type: LightTube components: @@ -3058,14 +3031,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 381 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 381 type: LightTube components: @@ -3079,14 +3052,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 383 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 383 type: LightTube components: @@ -3101,14 +3074,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 385 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 385 type: LightBulb components: @@ -3165,14 +3138,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 393 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 393 type: LightTube components: @@ -4925,14 +4898,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 642 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 642 type: LightTube components: @@ -4947,14 +4920,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 644 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 644 type: LightTube components: @@ -4969,14 +4942,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 646 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 646 type: LightTube components: @@ -5089,14 +5062,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 664 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 664 type: LightTube components: @@ -5111,14 +5084,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 666 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 666 type: LightTube components: @@ -5147,14 +5120,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 670 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 670 type: LightTube components: @@ -5379,14 +5352,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 702 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 702 type: LightTube components: @@ -5401,14 +5374,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 704 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 704 type: LightTube components: @@ -5479,14 +5452,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 714 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 714 type: LightTube components: @@ -5568,14 +5541,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 723 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 723 type: LightTube components: @@ -5726,6 +5699,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 744 type: CrateMedical components: @@ -5738,6 +5713,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 745 type: LockerMedical components: @@ -5750,6 +5727,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 746 type: LockerMedical components: @@ -5762,6 +5741,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 747 type: LockerMedical components: @@ -5774,6 +5755,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 748 type: LockerChemistry components: @@ -5786,6 +5769,8 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 749 type: solid_wall components: @@ -6677,14 +6662,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 876 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 876 type: LightTube components: @@ -6699,14 +6684,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 878 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 878 type: LightTube components: @@ -6726,14 +6711,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 879 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 881 type: Wire components: @@ -6757,14 +6742,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 884 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 884 type: LightTube components: @@ -6779,14 +6764,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 886 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 886 type: LightTube components: @@ -6800,14 +6785,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 888 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 888 type: LightBulb components: @@ -6821,14 +6806,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 890 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 890 type: LightTube components: @@ -6847,14 +6832,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 891 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 893 type: PoweredSmallLight components: @@ -6864,14 +6849,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 894 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 894 type: LightBulb components: @@ -6893,14 +6878,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 897 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 897 type: LightBulb components: @@ -6915,16 +6900,12 @@ entities: type: Transform - points: 343000 type: ResearchServer - - technologies: [] - type: TechnologyDatabase - uid: 899 type: ComputerResearchAndDevelopment components: - parent: 0 pos: 8.5,18.5 type: Transform - - technologies: [] - type: TechnologyDatabase - uid: 900 type: BaseResearchAndDevelopmentPointSource components: @@ -6937,66 +6918,18 @@ entities: - parent: 0 pos: 8.5,17.5 type: Transform - - recipes: [] - protolatherecipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: ProtolatheDatabase - - technologies: [] - type: TechnologyDatabase - uid: 902 type: Autolathe components: - parent: 0 pos: 13.5,18.5 type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: LatheDatabase - uid: 903 type: Autolathe components: - parent: 0 pos: -4.5,-5.5 type: Transform - - recipes: - - Brutepack - - Ointment - - LightTube - - LightBulb - - MetalStack - - GlassStack - - Wirecutter - - Screwdriver - - Welder - - Wrench - - CableStack - - Crowbar - - Multitool - type: LatheDatabase - uid: 904 type: Table components: @@ -7250,14 +7183,14 @@ entities: type: Transform - color: '#FFFFFFFF' type: PointLight - - load: 40 - type: PowerDevice - containers: light_bulb: entities: - 940 type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice - uid: 940 type: LightTube components: @@ -7343,16 +7276,126 @@ entities: entities: [] type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer + - IsPlaceable: False + type: PlaceableSurface - uid: 953 - type: LockerScience + type: solid_wall components: - parent: 0 - pos: 13.5,21.5 + pos: 5.5,2.5 rot: -1.5707963267948966 rad type: Transform +- uid: 954 + type: solid_wall + components: + - parent: 0 + pos: 6.5,2.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 955 + type: solid_wall + components: + - parent: 0 + pos: 7.5,2.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 956 + type: solid_wall + components: + - parent: 0 + pos: 8.5,2.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 957 + type: solid_wall + components: + - parent: 0 + pos: 8.5,1.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 958 + type: solid_wall + components: + - parent: 0 + pos: 8.5,0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 959 + type: solid_wall + components: + - parent: 0 + pos: 8.5,-0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 960 + type: solid_wall + components: + - parent: 0 + pos: 8.5,-1.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 961 + type: solid_wall + components: + - parent: 0 + pos: 8.5,-2.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 962 + type: solid_wall + components: + - parent: 0 + pos: 8.5,-3.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 963 + type: solid_wall + components: + - parent: 0 + pos: 8.5,-4.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 964 + type: solid_wall + components: + - parent: 0 + pos: 7.5,-4.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 965 + type: GravityGenerator + components: + - parent: 0 + pos: 6.5,0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 966 + type: Poweredlight + components: + - parent: 0 + pos: 8,-1.5 + rot: 3.141592653589793 rad + type: Transform + - color: '#FFFFFFFF' + type: PointLight - containers: - EntityStorageComponent: - entities: [] - type: Robust.Server.GameObjects.Components.Container.Container + light_bulb: + entities: + - 967 + type: Content.Server.GameObjects.ContainerSlot type: ContainerContainer + - load: 40 + type: PowerDevice +- uid: 967 + type: LightTube + components: + - parent: 966 + type: Transform +- uid: 968 + type: airlock_engineering + components: + - parent: 0 + pos: 4.5,-2.5 + rot: -1.5707963267948966 rad + type: Transform ... diff --git a/Resources/Prototypes/Entities/Buildings/gravity_generator.yml b/Resources/Prototypes/Entities/Buildings/gravity_generator.yml new file mode 100644 index 0000000000..19fc4f3915 --- /dev/null +++ b/Resources/Prototypes/Entities/Buildings/gravity_generator.yml @@ -0,0 +1,36 @@ +- type: entity + id: GravityGenerator + name: Gravity Generator + description: It's what keeps you to the floor. + components: + - type: Sprite + sprite: Buildings/gravity_generator.rsi + layers: + - state: on + - sprite: Buildings/gravity_generator_core.rsi + state: activated + shader: unshaded + - type: Icon + sprite: Buildings/gravity_generator.rsi + state: on + - type: SnapGrid + offset: Center + - type: PowerDevice + load: 500 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-1.5,-1.5,1.5,1.5" + layer: 15 + - type: Clickable + - type: InteractionOutline + - type: Damageable + - type: Breakable + threshold: 150 + - type: GravityGenerator + - type: UserInterface + interfaces: + - key: enum.GravityGeneratorUiKey.Key + type: GravityGeneratorBoundUserInterface + placement: + mode: AlignTileAny diff --git a/Resources/Prototypes/Entities/Buildings/medical_scanner.yml b/Resources/Prototypes/Entities/Buildings/medical_scanner.yml index aae8cefd16..611fd803ed 100644 --- a/Resources/Prototypes/Entities/Buildings/medical_scanner.yml +++ b/Resources/Prototypes/Entities/Buildings/medical_scanner.yml @@ -36,7 +36,6 @@ - type: Appearance visuals: - type: MedicalScannerVisualizer2D - - type: PowerDevice - type: UserInterface interfaces: - key: enum.MedicalScannerUiKey.Key diff --git a/Resources/Prototypes/Entities/Items/Clothing/belts.yml b/Resources/Prototypes/Entities/Items/Clothing/belts.yml index b2423d740e..2994275e00 100644 --- a/Resources/Prototypes/Entities/Items/Clothing/belts.yml +++ b/Resources/Prototypes/Entities/Items/Clothing/belts.yml @@ -21,6 +21,7 @@ state: utilitybelt - type: Clothing Size: 50 + QuickEquip: false sprite: Clothing/belt_utility.rsi - type: Storage Capacity: 40 # Full tool loadout is 35, plus an extra diff --git a/Resources/Prototypes/Entities/Items/Consumables/food.yml b/Resources/Prototypes/Entities/Items/Consumables/food.yml index 8e6dd97175..6a597b9141 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/food.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/food.yml @@ -90,7 +90,7 @@ # name: Ambrosia vulgar is crushed # parent: FoodBase # id: FoodAmbrosiaVulgarIsCrushed -# description: +# description: # components: # - type: Food # uses: 1 @@ -137,7 +137,7 @@ # name: Bacon # parent: FoodBase # id: FoodBacon -# description: +# description: # components: # - type: Food # uses: 1 @@ -182,7 +182,7 @@ name: Bread (slice) parent: FoodBase id: FoodBreadSlice - description: + description: components: - type: Food contents: @@ -199,7 +199,7 @@ name: Banana bread (slice) parent: FoodBase id: FoodBananaBreadSlice - description: + description: components: - type: Food contents: @@ -233,7 +233,7 @@ # name: Bear meat # parent: FoodBase # id: FoodBearMeat -# description: +# description: # components: # - type: Food # uses: 1 @@ -612,7 +612,7 @@ # name: Cocoa # parent: FoodBase # id: FoodCocoa -# description: +# description: # components: # - type: Food # uses: 1 @@ -1482,19 +1482,21 @@ - type: Icon sprite: Objects/Food/loadedbakedpotato.rsi -#- type: entity -# parent: FoodBase -# id: FoodMeat -# name: Meat -# description: '' -# components: -# - type: Food -# uses: 1 -# restore_amount: 1 -# - type: Sprite -# sprite: Objects/Food/meat.rsi -# - type: Icon -# sprite: Objects/Food/meat.rsi +- type: entity + parent: FoodBase + id: FoodMeat + name: Meat + description: A slab of meat. + components: + - type: Food + contents: + reagents: + - ReagentId: chem.Nutriment + Quantity: 5 + - type: Sprite + sprite: Objects/Food/meat.rsi + - type: Icon + sprite: Objects/Food/meat.rsi - type: entity parent: FoodBase @@ -2835,19 +2837,21 @@ - type: Icon sprite: Objects/Food/xenobreadslice.rsi -#- type: entity -# parent: FoodBase -# id: FoodXenomeat -# name: Xenomeat -# description: '' -# components: -# - type: Food -# uses: 1 -# restore_amount: 1 -# - type: Sprite -# sprite: Objects/Food/xenomeat.rsi -# - type: Icon -# sprite: Objects/Food/xenomeat.rsi +- type: entity + parent: FoodBase + id: FoodXenomeat + name: Xenomeat + description: Yum. + components: + - type: Food + contents: + reagents: + - ReagentId: chem.Nutriment + Quantity: 20 + - type: Sprite + sprite: Objects/Food/xenomeat.rsi + - type: Icon + sprite: Objects/Food/xenomeat.rsi - type: entity parent: FoodBase @@ -2865,3 +2869,20 @@ sprite: Objects/Food/xenomeatpie.rsi - type: Icon sprite: Objects/Food/xenomeatpie.rsi + +- type: entity + parent: FoodBase + id: DisgustingSweptSoup + name: Salty Sweet Miso Cola Soup + description: Jesus christ. + components: + - type: Food + contents: + reagents: + - ReagentId: chem.Bleach + Quantity: 15 + spawn_on_finish: TrashSnackBowl + - type: Sprite + sprite: Objects/Food/stew.rsi + - type: Icon + sprite: Objects/Food/stew.rsi diff --git a/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml b/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml new file mode 100644 index 0000000000..2d009d083e --- /dev/null +++ b/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml @@ -0,0 +1,22 @@ +- type: entity + parent: BaseItem + id: ReagentContainerFlour + name: Flour + description: Not to be confused with flower. + components: + - type: Solution + maxVol: 50 + contents: + reagents: + - ReagentId: chem.Flour + Quantity: 50 + - type: Pourable + transferAmount: 5 + - type: Drink + despawn_empty: true + - type: Sprite + sprite: Objects/Food/flour.rsi + state: icon + - type: Icon + sprite: Objects/Food/flour.rsi + state: icon diff --git a/Resources/Prototypes/Entities/Items/Instruments.yml b/Resources/Prototypes/Entities/Items/Instruments.yml index ccfd97dd8d..08db84188f 100644 --- a/Resources/Prototypes/Entities/Items/Instruments.yml +++ b/Resources/Prototypes/Entities/Items/Instruments.yml @@ -23,6 +23,18 @@ - type: Icon texture: Objects/Instruments/musician.rsi/h_synthesizer.png +- type: entity + name: Acoustic Guitar + parent: BaseHandheldInstrument + id: AcousticGuitarInstrument + components: + - type: Instrument + program: 25 + - type: Sprite + texture: Objects/Instruments/musician.rsi/guitar.png + - type: Icon + texture: Objects/Instruments/musician.rsi/guitar.png + - type: entity name: Violin parent: BaseHandheldInstrument diff --git a/Resources/Prototypes/Entities/Items/bike_horn.yml b/Resources/Prototypes/Entities/Items/bike_horn.yml index 51d72315ae..8314634c1c 100644 --- a/Resources/Prototypes/Entities/Items/bike_horn.yml +++ b/Resources/Prototypes/Entities/Items/bike_horn.yml @@ -20,6 +20,7 @@ - type: Sound - type: EmitSoundOnUse sound: /Audio/items/bikehorn.ogg + variation: 0.2 - type: UseDelay delay: 0.5 diff --git a/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml index 00552cf73c..e99f10a3b6 100644 --- a/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml +++ b/Resources/Prototypes/Entities/Weapons/Shotguns/shotguns.yml @@ -42,6 +42,3 @@ - type: Item Size: 24 sprite: Objects/Guns/SMGs/c20r.rsi - - type: Item - Size: 24 - sprite: Objects/Guns/SMGs/wt550.rsi diff --git a/Resources/Prototypes/Entities/janitor.yml b/Resources/Prototypes/Entities/janitor.yml index 0cc504dd1f..ca1add7b83 100644 --- a/Resources/Prototypes/Entities/janitor.yml +++ b/Resources/Prototypes/Entities/janitor.yml @@ -34,7 +34,6 @@ drawdepth: Objects - type: Icon texture: Objects/Janitorial/mopbucket.png - - type: Clickable - type: InteractionOutline - type: Bucket - type: Sound @@ -51,7 +50,6 @@ - type: Physics mass: 5 Anchored: false - - type: Sound - type: entity parent: BaseItem diff --git a/Resources/Prototypes/Entities/kitchen.yml b/Resources/Prototypes/Entities/kitchen.yml new file mode 100644 index 0000000000..f517051c87 --- /dev/null +++ b/Resources/Prototypes/Entities/kitchen.yml @@ -0,0 +1,40 @@ +- type: entity + id: KitchenMicrowave + name: Microwave + description: It's magic. + components: + - type: Microwave + - type: Clickable + - type: InteractionOutline + - type: Solution + maxVol: 100 + caps: 1 + - type: Appearance + visuals: + - type: MicrowaveVisualizer + - type: Sound + - type: UserInterface + interfaces: + - key: enum.MicrowaveUiKey.Key + type: MicrowaveBoundUserInterface + + + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.25,-0.4,0.25,0.4" + layer: 15 + IsScrapingFloor: true + - type: Sprite + netsync: false + sprite: Objects/Kitchen/microwave.rsi + layers: + - state: mw0 + map: ["enum.MicrowaveVisualizerLayers.Base"] + - state: mw_unlit + shader: unshaded + map: ["enum.MicrowaveVisualizerLayers.BaseUnlit"] + - type: PowerDevice + - type: Icon + sprite: Objects/Kitchen/microwave.rsi + state: mw0 diff --git a/Resources/Prototypes/Kitchen/meal_recipes.yml b/Resources/Prototypes/Kitchen/meal_recipes.yml new file mode 100644 index 0000000000..707a4c3b47 --- /dev/null +++ b/Resources/Prototypes/Kitchen/meal_recipes.yml @@ -0,0 +1,338 @@ + +#Burgers +- type: microwaveMealRecipe + id: RecipeCheeseburger + name: Cheeseburger Recipe + result: FoodCheeseburger + time: 5 + reagents: + chem.Flour: 5 + solids: + FoodMeat: 1 + +- type: microwaveMealRecipe + id: RecipeTofuBurger + name: Tofu Burger Recipe + result: FoodTofuBurger + time: 5 + reagents: + chem.Flour: 15 + solids: + Tofu: 1 + +- type: microwaveMealRecipe + id: RecipeXenoburger + name: Xenoburger Recipe + result: FoodXenoburger + time: 5 + reagents: + chem.Flour: 5 + solids: + FoodXenomeat: 1 + +- type: microwaveMealRecipe + id: RecipeSuperBiteBurger + name: Super Bite Burger Recipe + result: FoodSuperBiteBurger + time: 20 + reagents: + chem.Flour: 15 + chem.Salt: 5 + chem.Pepper: 5 + solids: + FoodMeat: 5 + FoodCheeseWedge: 3 + FoodTomato: 4 + FoodEgg: 2 + +#Breads & Sandwiches +- type: microwaveMealRecipe + id: RecipeBread + name: Bread Recipe + result: DrinkFoodContainerBread + time: 15 + reagents: + chem.Flour: 15 + +- type: microwaveMealRecipe + id: RecipeSandwich + name: Sandwich Recipe + result: FoodSandwich + time: 10 + solids: + FoodBreadSlice: 2 + FoodMeatSteak: 1 + FoodCheeseWedge: 1 + +- type: microwaveMealRecipe + id: RecipeToastedSandwich + name: Toasted Sandwich Recipe + result: FoodToastedSandwich + time: 5 + solids: + FoodSandwich: 1 + +- type: microwaveMealRecipe + id: RecipeGrilledCheese + name: Grilled Cheese Recipe + result: FoodGrilledCheese + time: 5 + solids: + FoodBreadSlice: 2 + FoodCheeseWedge: 1 + +- type: microwaveMealRecipe + id: RecipeBaguette + name: Baguette Recipe + result: FoodBaguette + time: 10 + reagents: + chem.Flour: 15 + chem.Salt: 1 + chem.Pepper: 1 + +- type: microwaveMealRecipe + id: RecipeCreamCheeseBread + name: Cream Cheese Bread Recipe + result: DrinkFoodContainerCreamCheeseBread + time: 20 + reagents: + chem.Flour: 15 + solids: + FoodCheeseWedge: 2 + +- type: microwaveMealRecipe + id: RecipeBananaBread + name: Banana Bread Recipe + result: DrinkFoodContainerBananaBread + time: 25 + reagents: + chem.Flour: 15 + chem.Milk: 5 + solids: + FoodEgg: 3 + FoodBanana: 1 + + + +#Pizzas +- type: microwaveMealRecipe + id: RecipeMargheritaPizza + name: Margherita Pizza Recipe + result: DrinkFoodContainerMargheritaPizza + time: 30 + reagents: + chem.Flour: 10 + solids: + FoodCheeseWedge: 4 + FoodTomato: 1 + +- type: microwaveMealRecipe + id: RecipeMushroomPizza + name: Mushroom Pizza Recipe + result: DrinkFoodContainerMushroomPizza + time: 25 + reagents: + chem.Flour: 10 + solids: + FoodMushroom: 5 + +- type: microwaveMealRecipe + id: RecipeMeatPizza + name: Meat Pizza Recipe + result: DrinkFoodContainerMeatPizza + time: 30 + reagents: + chem.Flour: 10 + solids: + FoodMeat: 3 + FoodCheeseWedge: 1 + FoodTomato: 1 + +- type: microwaveMealRecipe + id: RecipeVegetablePizza + name: Vegetable Pizza Recipe + result: DrinkFoodContainerVegetablePizza + time: 30 + reagents: + chem.Flour: 10 + solids: + FoodEggplant: 1 + FoodCarrot: 1 + FoodCorn: 1 + FoodTomato: 1 + +#Italian +- type: microwaveMealRecipe + id: RecipeSpaghetti + name: Spaghetti Recipe + result: FoodSpaghetti + time: 1 + reagents: + chem.Flour: 5 + +- type: microwaveMealRecipe + id: RecipeBoiledSpaghetti + name: Boiled Spaghetti Recipe + result: FoodSpagettiBoiled + time: 5 + reagents: + chem.H2O: 5 + solids: + FoodSpagetti: 1 + +- type: microwaveMealRecipe + id: RecipePastaTomato + name: Pasta Tomato Recipe + result: FoodPastaTomato + time: 10 + reagents: + chem.H2O: 5 + solids: + FoodSpagetti: 1 + FoodTomato: 2 + +- type: microwaveMealRecipe + id: RecipeMeatballSpaghetti + name: Spaghetti & Meatballs Recipe + result: FoodMeatballSpaghetti + time: 10 + reagents: + chem.H2O: 5 + solids: + FoodSpagetti: 1 + FoodMeatball: 2 + +- type: microwaveMealRecipe + id: RecipeCrabSpaghetti + name: Crab Spaghetti Recipe + result: FoodCrabSpaghetti + time: 10 + reagents: + chem.H2O: 5 + solids: + FoodCrabMeat: 2 + +- type: microwaveMealRecipe + id: RecipeCopypasta + name: Copypasta Recipe + result: FoodCopypasta + time: 10 + solids: + FoodPastaTomato: 2 + +# - type: microwaveMealRecipe +# id: RecipeMoMMISpaghetti +# name: MoMMI Spaghetti Recipe +# result: FoodMoMMISpaghetti +# time: 10 +# reagents: +# chem.Flour: 5 +# solids: + +- type: microwaveMealRecipe + id: RecipeRisotto + name: Risotto Recipe + result: FoodRisotto + time: 10 + reagents: + chem.Flour: 10 #This should be 10 units of rice. + chem.Wine: 5 + solids: + FoodCheeseWedge: 1 + +- type: microwaveMealRecipe + id: RecipeBruschetta + name: Bruschetta Recipe + result: FoodBruschetta + time: 10 + reagents: + chem.Flour: 10 + chem.Salt: 2 + solids: + FoodGarlic: 1 + FoodCheeseWedge: 1 + FoodTomato: 1 + +- type: microwaveMealRecipe + id: RecipeQuiche + name: Quiche Recipe + result: FoodQuiche + time: 10 + solids: + FoodBlueTomato: 1 + FoodGarlic: 1 + FoodEgg: 1 + FoodCheeseWedge: 1 + +- type: microwaveMealRecipe + id: RecipeLasagna + name: Lasagna Recipe + result: FoodLasagna + time: 15 + # reagents: + # chem.TomatoJuice: 15 + solids: + FoodFlatDough: 2 + FoodMeat: 2 + FoodEggPlant: 1 + FoodCheeseWedge: 1 + +#Soups & Stew +- type: microwaveMealRecipe + id: RecipeMeatballSoup + name: Meatball Soup Recipe + result: FoodMeatballSoup + time: 10 + reagents: + chem.H2O: 10 + solids: + FoodMeatball: 1 + FoodCarrot: 1 + FoodPotato: 1 + +- type: microwaveMealRecipe + id: RecipeNettleSoup + name: Nettle Soup Recipe + result: FoodNettleSoup + time: 10 + reagents: + chem.H2O: 10 + solids: + FoodNettle: 1 + FoodEgg: 1 + FoodPotato: 1 + +#Other +- type: microwaveMealRecipe + id: RecipeMeatSteak + name: Meat Steak Recipe + result: FoodMeatSteak + time: 10 + reagents: + chem.Salt: 1 + chem.Pepper: 1 + solids: + FoodMeat: 1 + +- type: microwaveMealRecipe + id: RecipeMisoColaSoup + name: Salty Sweet MiloCola Soup Recipe + result: DisgustingSweptSoup + time: 15 + reagents: + chem.Cola: 5 + solids: + FoodMiloSoup: 1 + + + +#Handy template for copy **pasta**. Get it? Pasta? Aw whatever fuck you. +# - type: microwaveMealRecipe +# id: +# name: +# result: +# time: 5 +# reagents: + +# solids: diff --git a/Resources/Prototypes/LatheRecipes/tools.yml b/Resources/Prototypes/LatheRecipes/tools.yml index 0480b2cb24..38f0139b16 100644 --- a/Resources/Prototypes/LatheRecipes/tools.yml +++ b/Resources/Prototypes/LatheRecipes/tools.yml @@ -8,7 +8,9 @@ - type: latheRecipe id: Screwdriver - icon: Objects/Tools/screwdriver.png + icon: + sprite: Objects/Tools/screwdriver.rsi + state: screwdriver result: Screwdriver completetime: 500 materials: @@ -16,7 +18,9 @@ - type: latheRecipe id: Welder - icon: Objects/Tools/autolathe_welder.png + icon: + sprite: Objects/Tools/welder.rsi + state: welder result: Welder completetime: 500 materials: @@ -51,7 +55,9 @@ - type: latheRecipe id: Multitool - icon: Objects/Tools/multitool.png + icon: + sprite: Objects/Tools/multitool.rsi + state: multitool result: Multitool completetime: 500 materials: diff --git a/Resources/Prototypes/Reagents/food_reagents.yml b/Resources/Prototypes/Reagents/food_reagents.yml new file mode 100644 index 0000000000..6c838830da --- /dev/null +++ b/Resources/Prototypes/Reagents/food_reagents.yml @@ -0,0 +1,8 @@ +- type: reagent + id: chem.Flour + name: Flour + desc: Used for baking. + color: "#FFFFFF" + metabolism: + - !type:DefaultFood + rate: 1 diff --git a/Resources/Textures/Buildings/gravity_generator.rsi/broken.png b/Resources/Textures/Buildings/gravity_generator.rsi/broken.png new file mode 100644 index 0000000000..0e02745bb8 Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator.rsi/broken.png differ diff --git a/Resources/Textures/Buildings/gravity_generator.rsi/meta.json b/Resources/Textures/Buildings/gravity_generator.rsi/meta.json new file mode 100755 index 0000000000..4d6f697ad5 --- /dev/null +++ b/Resources/Textures/Buildings/gravity_generator.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version":1, + "size":{ + "x":96, + "y":96 + }, + "license":"CC-BY-SA-3.0", + "copyright":"Taken from https://github.com/tgstation/tgstation", + "states":[ + { + "name":"on", + "directions": 1 + }, + { + "name": "off", + "directions": 1 + }, + { + "name": "broken", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Buildings/gravity_generator.rsi/off.png b/Resources/Textures/Buildings/gravity_generator.rsi/off.png new file mode 100644 index 0000000000..9dd0406e39 Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator.rsi/off.png differ diff --git a/Resources/Textures/Buildings/gravity_generator.rsi/on.png b/Resources/Textures/Buildings/gravity_generator.rsi/on.png new file mode 100755 index 0000000000..d887cea52a Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator.rsi/on.png differ diff --git a/Resources/Textures/Buildings/gravity_generator_core.rsi/activated.png b/Resources/Textures/Buildings/gravity_generator_core.rsi/activated.png new file mode 100644 index 0000000000..18aaf0103c Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator_core.rsi/activated.png differ diff --git a/Resources/Textures/Buildings/gravity_generator_core.rsi/activating.png b/Resources/Textures/Buildings/gravity_generator_core.rsi/activating.png new file mode 100644 index 0000000000..24abe04528 Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator_core.rsi/activating.png differ diff --git a/Resources/Textures/Buildings/gravity_generator_core.rsi/idle.png b/Resources/Textures/Buildings/gravity_generator_core.rsi/idle.png new file mode 100644 index 0000000000..14b5452ebf Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator_core.rsi/idle.png differ diff --git a/Resources/Textures/Buildings/gravity_generator_core.rsi/meta.json b/Resources/Textures/Buildings/gravity_generator_core.rsi/meta.json new file mode 100644 index 0000000000..db482cdb7d --- /dev/null +++ b/Resources/Textures/Buildings/gravity_generator_core.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version":1, + "size":{ + "x":32, + "y":32 + }, + "license":"CC-BY-SA-3.0", + "copyright":"Taken from https://github.com/tgstation/tgstation", + "states":[ + { + "name": "activated", + "directions": 1, + "delays": [ + [0.1, + 0.1, + 0.1] + ] + } + ] +} diff --git a/Resources/Textures/Buildings/gravity_generator_core.rsi/startup.png b/Resources/Textures/Buildings/gravity_generator_core.rsi/startup.png new file mode 100644 index 0000000000..b5a5206b66 Binary files /dev/null and b/Resources/Textures/Buildings/gravity_generator_core.rsi/startup.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/meta.json b/Resources/Textures/Objects/Kitchen/microwave.rsi/meta.json new file mode 100644 index 0000000000..e1c3ede466 --- /dev/null +++ b/Resources/Textures/Objects/Kitchen/microwave.rsi/meta.json @@ -0,0 +1,105 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "AGPL v3", + "copyright": "Taken from https://github.com/discordia-space/CEV-Eris", + "states": [ + { + "name": "mw", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mw_unlit", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mw0", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mw_running_unlit", + "directions": 1, + "delays": [ + [ + 1.0, + 1.0 + ] + ] + }, + { + "name": "mwb", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mwbloody", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mwbloody0", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "mwbloody1", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "mwbloodyo", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "mwo", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mw.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw.png new file mode 100644 index 0000000000..88e285a134 Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mw0.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw0.png new file mode 100644 index 0000000000..da9b07a55b Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw0.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mw1.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw1.png new file mode 100644 index 0000000000..7525b08a80 Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw1.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_running_unlit.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_running_unlit.png new file mode 100644 index 0000000000..d259712b6d Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_running_unlit.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_unlit.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_unlit.png new file mode 100644 index 0000000000..4ad790b4e8 Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mw_unlit.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwb.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwb.png new file mode 100644 index 0000000000..fd933b346b Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwb.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody.png new file mode 100644 index 0000000000..3b144ce310 Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody0.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody0.png new file mode 100644 index 0000000000..3b144ce310 Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody0.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody1.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody1.png new file mode 100644 index 0000000000..c62ca8560a Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloody1.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloodyo.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloodyo.png new file mode 100644 index 0000000000..4d90ec33cc Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwbloodyo.png differ diff --git a/Resources/Textures/Objects/Kitchen/microwave.rsi/mwo.png b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwo.png new file mode 100644 index 0000000000..35ed2e9bae Binary files /dev/null and b/Resources/Textures/Objects/Kitchen/microwave.rsi/mwo.png differ diff --git a/Resources/Textures/Objects/Tools/autolathe_welder.png b/Resources/Textures/Objects/Tools/autolathe_welder.png deleted file mode 100644 index 6b1ed2cf13..0000000000 Binary files a/Resources/Textures/Objects/Tools/autolathe_welder.png and /dev/null differ diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 504ec81af9..a97aa89769 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -1,9 +1,16 @@ version: 1 # Not used right now, whatever. binds: -- function: UseOrAttack +- function: UIClick type: state key: MouseLeft canFocus: true +- function: Use + type: state + key: MouseLeft + canFocus: true +- function: WideAttack + type: state + key: Space - function: ShowDebugMonitors type: Toggle key: F3 @@ -65,6 +72,14 @@ binds: - function: OpenCharacterMenu type: State key: C +- function: TextCursorSelect + # TextCursorSelect HAS to be above ExamineEntity + # So that LineEdit receives it correctly. + # TODO: Make it so that UI keybinds are somehow prioritized so this ordering stuff isn't necessary. + type: state + key: MouseLeft + mod1: Shift + canFocus: true - function: ExamineEntity type: State key: MouseLeft @@ -94,6 +109,14 @@ binds: - function: OpenInventoryMenu type: state key: I +- function: SmartEquipBackpack + type: State + key: B + mod1: Shift +- function: SmartEquipBelt + type: State + key: E + mod1: Shift - function: ShowDebugConsole type: state key: Tilde @@ -109,6 +132,16 @@ binds: type: state key: Right canRepeat: true +- function: TextCursorWordLeft + type: state + key: Left + mod1: Control + canRepeat: true +- function: TextCursorWordRight + type: state + key: Right + mod1: Control + canRepeat: true - function: TextCursorBegin type: state key: Home @@ -116,6 +149,37 @@ binds: type: state key: End canRepeat: true +- function: TextCursorSelectLeft + type: state + key: Left + mod1: Shift + canRepeat: true +- function: TextCursorSelectRight + type: state + key: Right + mod1: Shift + canRepeat: true +- function: TextCursorSelectWordLeft + type: state + key: Left + mod1: Shift + mod2: Control + canRepeat: true +- function: TextCursorSelectWordRight + type: state + key: Right + mod1: Shift + mod2: Control + canRepeat: true +- function: TextCursorSelectBegin + type: state + mod1: Shift + key: Home +- function: TextCursorSelectEnd + type: state + mod1: Shift + key: End + canRepeat: true - function: TextBackspace type: state key: BackSpace @@ -126,6 +190,18 @@ binds: - function: TextSubmit type: state key: NumpadEnter +- function: TextSelectAll + type: state + key: A + mod1: Control +- function: TextCopy + type: state + key: C + mod1: Control +- function: TextCut + type: state + key: X + mod1: Control - function: TextPaste type: state key: V @@ -155,3 +231,10 @@ binds: - function: OpenSandboxWindow type: state key: B +- function: TakeScreenshot + type: state + key: F2 +- function: TakeScreenshotNoUI + type: state + key: F2 + mod1: Shift diff --git a/RobustToolbox b/RobustToolbox index b366070cd4..65abe671aa 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit b366070cd4f1912844710b475dacf84328c74d89 +Subproject commit 65abe671aa7cabffceafe430d992c72b9566c652 diff --git a/Setup.bat b/Setup.bat deleted file mode 100644 index 1da212630f..0000000000 --- a/Setup.bat +++ /dev/null @@ -1,6 +0,0 @@ -cd %CD% -python RUN_THIS.py -pause -cd %CD%\engine\Resources -python buildResourcePack.py -pause \ No newline at end of file diff --git a/SpaceStation14.sln b/SpaceStation14.sln index df58010a94..ccb629b7e1 100644 --- a/SpaceStation14.sln +++ b/SpaceStation14.sln @@ -62,6 +62,13 @@ ProjectSection(SolutionItems) = preProject Tools\gen_build_info.py = Tools\gen_build_info.py EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Shared.Scripting", "RobustToolbox\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj", "{41B450C0-A361-4CD7-8121-7072B8995CFC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{DDD55F86-0406-42E5-8443-93EDC6BB2D75}" +ProjectSection(SolutionItems) = preProject + RobustToolbox\Tools\download_natives.py = RobustToolbox\Tools\download_natives.py +EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -128,6 +135,10 @@ Global {45C9B43F-305D-4651-9863-F6384CBC847F}.Debug|x64.Build.0 = Debug|x64 {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|x64.ActiveCfg = Release|x64 {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|x64.Build.0 = Release|x64 + {41B450C0-A361-4CD7-8121-7072B8995CFC}.Debug|x64.ActiveCfg = Debug|Any CPU + {41B450C0-A361-4CD7-8121-7072B8995CFC}.Debug|x64.Build.0 = Debug|Any CPU + {41B450C0-A361-4CD7-8121-7072B8995CFC}.Release|x64.ActiveCfg = Release|Any CPU + {41B450C0-A361-4CD7-8121-7072B8995CFC}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -142,6 +153,8 @@ Global {0529F740-0000-0000-0000-000000000000} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} {4809F412-3132-419E-BF9D-CCF7593C3533} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} {50404922-9637-4394-BF59-165D0850ADC8} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} + {41B450C0-A361-4CD7-8121-7072B8995CFC} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} + {DDD55F86-0406-42E5-8443-93EDC6BB2D75} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AA37ED9F-F8D6-468E-A101-658AD605B09A} diff --git a/Tools/package_release_build.py b/Tools/package_release_build.py index c72a44fa87..007eca89f1 100755 --- a/Tools/package_release_build.py +++ b/Tools/package_release_build.py @@ -63,7 +63,14 @@ WINDOWS_NATIVES = { "freetype6.dll", "openal32.dll", "swnfd.dll", - "glfw3.dll" + "glfw3.dll", + "fluidsynth.dll", + "libglib-2.0-0.dll", + "libgobject-2.0-0.dll", + "libgthread-2.0-0.dll", + "libinstpatch-2.dll", + "libintl-8.dll", + "libsndfile-1.dll" } LINUX_NATIVES = {