diff --git a/EOLib.Localization/EOResourceID.cs b/EOLib.Localization/EOResourceID.cs index 7a15ef7e0..4d0823399 100644 --- a/EOLib.Localization/EOResourceID.cs +++ b/EOLib.Localization/EOResourceID.cs @@ -205,6 +205,21 @@ public enum EOResourceID SPELL_ONLY_WORKS_ON_GROUP = 304, STATUS_LABEL_THE_NPC_DROPPED = 305, + YOU_HAVE_BEEN_FROZEN = 306, + + REBOOT_SERVER_REBOOTING = 307, + REBOOT_SEQUENCE_STARTED = 308, + + //ARENA_EVENT_ABORTED_LAST_OPPONENT_DC = 309, // not used + ARENA_ROUND_DELAYED_STILL_PLAYERS = 310, + ARENA_WON_EVENT = 311, + ARENA_PLAYERS_LAUNCHED = 312, + ARENA_WAS_ELIMINATED_BY = 313, + ARENA_KILLED = 314, + ARENA_PLAYERS = 315, + ARENA_DO_NOT_BLOCK_LINE = 316, + ARENA_PLEASE_MOVE_FROM_PLACE = 317, + WEDDING_PLEASE_ENTER_NAME = 318, WEDDING_IS_ASKING_YOU_TO_MARRY = 319, WEDDING_REGISTRATION_SERVICE = 320, diff --git a/EOLib/Domain/Chat/ChatIcon.cs b/EOLib/Domain/Chat/ChatIcon.cs index 2e4e01ee3..24a4522ef 100644 --- a/EOLib/Domain/Chat/ChatIcon.cs +++ b/EOLib/Domain/Chat/ChatIcon.cs @@ -28,7 +28,7 @@ public enum ChatIcon DotDotDotDot, GSymbol, Skeleton, - WhatTheFuck, + Trophy, Information, QuestMessage } diff --git a/EOLib/Domain/Notifiers/IArenaNotifier.cs b/EOLib/Domain/Notifiers/IArenaNotifier.cs new file mode 100644 index 000000000..5ec443a4c --- /dev/null +++ b/EOLib/Domain/Notifiers/IArenaNotifier.cs @@ -0,0 +1,27 @@ +using AutomaticTypeMapper; + +namespace EOLib.Domain.Notifiers +{ + public interface IArenaNotifier + { + void NotifyArenaBusy(); + + void NotifyArenaStart(int players); + + void NotifyArenaKill(int killCount, string killer, string victim); + + void NotifyArenaWin(string winner); + } + + [AutoMappedType] + public class NoOpArenaNotifer : IArenaNotifier + { + public void NotifyArenaBusy() { } + + public void NotifyArenaStart(int players) { } + + public void NotifyArenaKill(int killCount, string killer, string victim) { } + + public void NotifyArenaWin(string winner) { } + } +} diff --git a/EOLib/Domain/Notifiers/IChatEventNotifier.cs b/EOLib/Domain/Notifiers/IChatEventNotifier.cs index 637cbc5fe..64ad7149c 100644 --- a/EOLib/Domain/Notifiers/IChatEventNotifier.cs +++ b/EOLib/Domain/Notifiers/IChatEventNotifier.cs @@ -8,7 +8,6 @@ public enum ChatEventType AdminChat, AdminAnnounce, Group, - Server, } public interface IChatEventNotifier @@ -18,6 +17,9 @@ public interface IChatEventNotifier void NotifyPrivateMessageRecipientNotFound(string recipientName); void NotifyPlayerMutedByAdmin(string adminName); + + void NotifyServerMessage(string serverMessage); + void NotifyServerPing(int timeInMS); } [AutoMappedType] @@ -28,5 +30,9 @@ public void NotifyChatReceived(ChatEventType eventType) { } public void NotifyPrivateMessageRecipientNotFound(string recipientName) { } public void NotifyPlayerMutedByAdmin(string adminName) { } + + public void NotifyServerMessage(string serverMessage) { } + + public void NotifyServerPing(int timeInMS) { } } } diff --git a/EOLib/PacketHandlers/Arena/ArenaAcceptHandler.cs b/EOLib/PacketHandlers/Arena/ArenaAcceptHandler.cs new file mode 100644 index 000000000..f4b234c9c --- /dev/null +++ b/EOLib/PacketHandlers/Arena/ArenaAcceptHandler.cs @@ -0,0 +1,48 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Arena +{ + /// + /// Arena win message + /// + [AutoMappedType] + public class ArenaAcceptHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _arenaNotifiers; + + public override PacketFamily Family => PacketFamily.Arena; + + public override PacketAction Action => PacketAction.Accept; + + public ArenaAcceptHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable arenaNotifiers) + : base(playerInfoProvider) + { + _arenaNotifiers = arenaNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var winnerName = packet.ReadBreakString(); + + var killsCount = packet.ReadInt(); + packet.ReadByte(); + + var killerName = packet.ReadBreakString(); + var victimName = packet.ReadBreakString(); + + foreach (var notifier in _arenaNotifiers) + { + notifier.NotifyArenaKill(killsCount, killerName, victimName); + notifier.NotifyArenaWin(winnerName); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Arena/ArenaDropHandler.cs b/EOLib/PacketHandlers/Arena/ArenaDropHandler.cs new file mode 100644 index 000000000..0bea248ee --- /dev/null +++ b/EOLib/PacketHandlers/Arena/ArenaDropHandler.cs @@ -0,0 +1,42 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Arena +{ + /// + /// "Arena is blocked" message + /// + [AutoMappedType] + public class ArenaDropHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _arenaNotifiers; + + public override PacketFamily Family => PacketFamily.Arena; + + public override PacketAction Action => PacketAction.Drop; + + public ArenaDropHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable arenaNotifiers) + : base(playerInfoProvider) + { + _arenaNotifiers = arenaNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + if (packet.ReadEndString().Length < 1) + return false; + + foreach (var notifier in _arenaNotifiers) + { + notifier.NotifyArenaBusy(); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Arena/ArenaSpecHandler.cs b/EOLib/PacketHandlers/Arena/ArenaSpecHandler.cs new file mode 100644 index 000000000..64f2d3ccc --- /dev/null +++ b/EOLib/PacketHandlers/Arena/ArenaSpecHandler.cs @@ -0,0 +1,76 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Domain.Map; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Arena +{ + /// + /// Arena kill message + /// + [AutoMappedType] + public class ArenaSpecHandler : InGameOnlyPacketHandler + { + private readonly ICharacterRepository _characterRepository; + private readonly ICurrentMapStateRepository _currentMapStateRepository; + private readonly IEnumerable _arenaNotifiers; + + public override PacketFamily Family => PacketFamily.Arena; + + public override PacketAction Action => PacketAction.Spec; + + public ArenaSpecHandler(IPlayerInfoProvider playerInfoProvider, + ICharacterRepository characterRepository, + ICurrentMapStateRepository currentMapStateRepository, + IEnumerable arenaNotifiers) + : base(playerInfoProvider) + { + _characterRepository = characterRepository; + _currentMapStateRepository = currentMapStateRepository; + _arenaNotifiers = arenaNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var playerId = packet.ReadShort(); + packet.ReadByte(); + + var playerDirection = (EODirection)packet.ReadChar(); + packet.ReadByte(); + + if (playerId == _characterRepository.MainCharacter.ID) + { + var rp = _characterRepository.MainCharacter.RenderProperties.WithDirection(playerDirection); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithRenderProperties(rp); + } + else if (_currentMapStateRepository.Characters.ContainsKey(playerId)) + { + var character = _currentMapStateRepository.Characters[playerId]; + var rp = character.RenderProperties.WithDirection(playerDirection); + var newCharacter = character.WithRenderProperties(rp); + _currentMapStateRepository.Characters.Update(character, newCharacter); + } + else if (playerId > 0) + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerId); + } + + var killsCount = packet.ReadInt(); + packet.ReadByte(); + + var killerName = packet.ReadBreakString(); + var victimName = packet.ReadBreakString(); + + foreach (var notifier in _arenaNotifiers) + { + notifier.NotifyArenaKill(killsCount, killerName, victimName); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Arena/ArenaUseHandler.cs b/EOLib/PacketHandlers/Arena/ArenaUseHandler.cs new file mode 100644 index 000000000..4ddf35758 --- /dev/null +++ b/EOLib/PacketHandlers/Arena/ArenaUseHandler.cs @@ -0,0 +1,41 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Arena +{ + /// + /// Arena start message + /// + [AutoMappedType] + public class ArenaUseHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _arenaNotifiers; + + public override PacketFamily Family => PacketFamily.Arena; + + public override PacketAction Action => PacketAction.Use; + + public ArenaUseHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable arenaNotifiers) + : base(playerInfoProvider) + { + _arenaNotifiers = arenaNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var playersCount = packet.ReadChar(); + + foreach (var notifier in _arenaNotifiers) + { + notifier.NotifyArenaStart(playersCount); + } + + return true; + } + } +} diff --git a/EndlessClient/Audio/SoundEffectID.cs b/EndlessClient/Audio/SoundEffectID.cs index dbfff880c..1d5fe6bee 100644 --- a/EndlessClient/Audio/SoundEffectID.cs +++ b/EndlessClient/Audio/SoundEffectID.cs @@ -66,6 +66,7 @@ public enum SoundEffectID Guitar3, Thunder, MapEvacTimer, + ArenaTickSound = MapEvacTimer, ArenaWin = 52, Gun, UltimaBlastSpell, diff --git a/EndlessClient/HUD/Controls/HudControlsFactory.cs b/EndlessClient/HUD/Controls/HudControlsFactory.cs index eba4263c0..754d40fdb 100644 --- a/EndlessClient/HUD/Controls/HudControlsFactory.cs +++ b/EndlessClient/HUD/Controls/HudControlsFactory.cs @@ -61,6 +61,7 @@ public class HudControlsFactory : IHudControlsFactory private readonly IPathFinder _pathFinder; private readonly ICharacterActions _characterActions; private readonly IWalkValidationActions _walkValidationActions; + private readonly IChatBubbleActions _chatBubbleActions; private readonly IPacketSendService _packetSendService; private readonly IUserInputTimeProvider _userInputTimeProvider; private readonly ISpellSlotDataRepository _spellSlotDataRepository; @@ -70,7 +71,7 @@ public class HudControlsFactory : IHudControlsFactory private readonly IFixedTimeStepRepository _fixedTimeStepRepository; private readonly IClickDispatcherFactory _clickDispatcherFactory; private readonly IMetadataProvider _weaponMetadataProvider; - + private readonly ILocalizedStringFinder _localizedStringFinder; private IChatController _chatController; private IMainButtonController _mainButtonController; @@ -95,6 +96,7 @@ public HudControlsFactory(IHudButtonController hudButtonController, IPathFinder pathFinder, ICharacterActions characterActions, IWalkValidationActions walkValidationActions, + IChatBubbleActions chatBubbleActions, IPacketSendService packetSendService, IUserInputTimeProvider userInputTimeProvider, ISpellSlotDataRepository spellSlotDataRepository, @@ -103,7 +105,8 @@ public HudControlsFactory(IHudButtonController hudButtonController, INewsProvider newsProvider, IFixedTimeStepRepository fixedTimeStepRepository, IClickDispatcherFactory clickDispatcherFactory, - IMetadataProvider weaponMetadataProvider) + IMetadataProvider weaponMetadataProvider, + ILocalizedStringFinder localizedStringFinder) { _hudButtonController = hudButtonController; _hudPanelFactory = hudPanelFactory; @@ -126,6 +129,7 @@ public HudControlsFactory(IHudButtonController hudButtonController, _pathFinder = pathFinder; _characterActions = characterActions; _walkValidationActions = walkValidationActions; + _chatBubbleActions = chatBubbleActions; _packetSendService = packetSendService; _userInputTimeProvider = userInputTimeProvider; _spellSlotDataRepository = spellSlotDataRepository; @@ -135,6 +139,7 @@ public HudControlsFactory(IHudButtonController hudButtonController, _fixedTimeStepRepository = fixedTimeStepRepository; _clickDispatcherFactory = clickDispatcherFactory; _weaponMetadataProvider = weaponMetadataProvider; + _localizedStringFinder = localizedStringFinder; } public void InjectChatController(IChatController chatController, @@ -580,7 +585,11 @@ private INPCAnimator CreateNPCAnimator() private IPeriodicEmoteHandler CreatePeriodicEmoteHandler(ICharacterAnimator characterAnimator) { - return new PeriodicEmoteHandler(_endlessGameProvider, _characterActions, _userInputTimeProvider, _characterRepository, characterAnimator, _statusLabelSetter, _mainButtonController); + return new PeriodicEmoteHandler( + _endlessGameProvider, _characterActions, _chatBubbleActions, + _userInputTimeProvider, _characterRepository, characterAnimator, + _statusLabelSetter, _mainButtonController, _localizedStringFinder, + _sfxPlayer); } private PreviousUserInputTracker CreatePreviousUserInputTracker() diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index ea44d41bf..734464736 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -80,6 +80,8 @@ public void StartWalking(Option targetCoordinate) if (!_hudControlProvider.IsInGame) return; + _hudControlProvider.GetComponent(HudControlIdentifier.PeriodicEmoteHandler).CancelArenaBlockTimer(); + CancelSpellPrep(); Animator.StartMainCharacterWalkAnimation(targetCoordinate, () => { diff --git a/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs b/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs index 01602aaf0..280cc11e6 100644 --- a/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs +++ b/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs @@ -1,6 +1,8 @@ -using EndlessClient.Controllers; +using EndlessClient.Audio; +using EndlessClient.Controllers; using EndlessClient.GameExecution; using EndlessClient.HUD; +using EndlessClient.HUD.Chat; using EndlessClient.Input; using EOLib.Domain.Character; using EOLib.Localization; @@ -25,39 +27,61 @@ public class PeriodicEmoteHandler : GameComponent, IPeriodicEmoteHandler // Time between first alert and alternate alert (3.6 seconds) private const int AFK_TIME_ALT_ALERT_MS = 3600; + // Time from arena launch until the block messages start showing + private const int ARENA_BLOCK_INITIAL_TIME_MS = 6000; + // Time between subsequent messages + private const int ARENA_BLOCK_WARNING_INTERVAL_MS = 1200; + // Intervals before disconnect + private const int ARENA_BLOCK_MAX_WARNINGS = 6; + private readonly ICharacterActions _characterActions; + private readonly IChatBubbleActions _chatBubbleActions; private readonly IUserInputTimeProvider _userInputTimeProvider; private readonly ICharacterRepository _characterRepository; private readonly ICharacterAnimator _animator; private readonly IStatusLabelSetter _statusLabelSetter; private readonly IMainButtonController _mainButtonController; + private readonly ILocalizedStringFinder _localizedStringFinder; + private readonly ISfxPlayer _sfxPlayer; private readonly Random _random; + // drunk stuff private Option _drunkStart; private Option _drunkTimeSinceLastEmote; private int _drunkIntervalSeconds; private double _drunkTimeoutSeconds; + // afk stuff private Option _afkTimeSinceLastEmote; private Option _afkTimeSinceLastAlert; private bool _altAlert; + // arena block stuff + private Option _arenaTimer; + private int _arenaWarningCounter; + public PeriodicEmoteHandler(IEndlessGameProvider endlessGameProvider, ICharacterActions characterActions, + IChatBubbleActions chatBubbleActions, IUserInputTimeProvider userInputTimeProvider, ICharacterRepository characterRepository, ICharacterAnimator animator, IStatusLabelSetter statusLabelSetter, - IMainButtonController mainButtonController) + IMainButtonController mainButtonController, + ILocalizedStringFinder localizedStringFinder, + ISfxPlayer sfxPlayer) : base((Game)endlessGameProvider.Game) { _characterActions = characterActions; + _chatBubbleActions = chatBubbleActions; _userInputTimeProvider = userInputTimeProvider; _characterRepository = characterRepository; _animator = animator; _statusLabelSetter = statusLabelSetter; _mainButtonController = mainButtonController; + _localizedStringFinder = localizedStringFinder; + _sfxPlayer = sfxPlayer; _random = new Random(); } @@ -168,6 +192,32 @@ public override void Update(GameTime gameTime) _afkTimeSinceLastAlert = Option.None(); } + _arenaTimer.MatchSome( + some: a => + { + if ((_arenaWarningCounter == 0 && a.ElapsedMilliseconds >= ARENA_BLOCK_INITIAL_TIME_MS) || + (_arenaWarningCounter > 0 && a.ElapsedMilliseconds >= ARENA_BLOCK_WARNING_INTERVAL_MS)) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.ARENA_DO_NOT_BLOCK_LINE); + _sfxPlayer.PlaySfx(SoundEffectID.ArenaTickSound); + + if (_arenaWarningCounter == 1) + { + var resource = _localizedStringFinder.GetString(EOResourceID.ARENA_PLEASE_MOVE_FROM_PLACE); + _chatBubbleActions.ShowChatBubbleForMainCharacter(resource); + } + else if (_arenaWarningCounter == ARENA_BLOCK_MAX_WARNINGS) + { + _arenaTimer = Option.None(); + _mainButtonController.GoToInitialStateAndDisconnect(showLostConnection: true); + } + + StartArenaBlockTimer(); + _arenaWarningCounter++; + } + + }); + base.Update(gameTime); } @@ -175,10 +225,25 @@ public void SetDrunkTimeout(int beerPotency) { _drunkTimeoutSeconds = (100 + (beerPotency * 10)) / 8.0; } + + public void StartArenaBlockTimer() + { + _arenaTimer = Option.Some(Stopwatch.StartNew()); + } + + public void CancelArenaBlockTimer() + { + _arenaTimer = Option.None(); + _arenaWarningCounter = 0; + } } public interface IPeriodicEmoteHandler : IGameComponent, IUpdateable { void SetDrunkTimeout(int beerPotency); + + void StartArenaBlockTimer(); + + void CancelArenaBlockTimer(); } } diff --git a/EndlessClient/Subscribers/ArenaEventSubscriber.cs b/EndlessClient/Subscribers/ArenaEventSubscriber.cs new file mode 100644 index 000000000..b25bb9d9d --- /dev/null +++ b/EndlessClient/Subscribers/ArenaEventSubscriber.cs @@ -0,0 +1,107 @@ +using AutomaticTypeMapper; +using EndlessClient.Audio; +using EndlessClient.ControlSets; +using EndlessClient.HUD.Chat; +using EndlessClient.HUD.Controls; +using EndlessClient.Rendering.Character; +using EOLib.Domain.Character; +using EOLib.Domain.Chat; +using EOLib.Domain.Extensions; +using EOLib.Domain.Map; +using EOLib.Domain.Notifiers; +using EOLib.IO.Map; +using EOLib.Localization; +using System.Collections.Generic; + +namespace EndlessClient.Subscribers +{ + [AutoMappedType] + public class ArenaEventSubscriber : IArenaNotifier + { + private readonly IHudControlProvider _hudControlProvider; + private readonly ICurrentMapProvider _currentMapProvider; + private readonly ICharacterProvider _characterProvider; + private readonly IChatRepository _chatRepository; + private readonly ILocalizedStringFinder _localizedStringFinder; + private readonly IServerMessageHandler _serverMessageHandler; + + public ArenaEventSubscriber(IHudControlProvider hudControlProvider, + ICurrentMapProvider currentMapProvider, + ICharacterProvider characterProvider, + IChatRepository chatRepository, + ILocalizedStringFinder localizedStringFinder, + IServerMessageHandler serverMessageHandler) + { + _hudControlProvider = hudControlProvider; + _currentMapProvider = currentMapProvider; + _characterProvider = characterProvider; + _chatRepository = chatRepository; + _localizedStringFinder = localizedStringFinder; + _serverMessageHandler = serverMessageHandler; + } + + public void NotifyArenaBusy() + { + var message = _localizedStringFinder.GetString(EOResourceID.ARENA_ROUND_DELAYED_STILL_PLAYERS); + _serverMessageHandler.AddServerMessage(message, SoundEffectID.ArenaTickSound); + } + + public void NotifyArenaStart(int players) + { + var message = _localizedStringFinder.GetString(EOResourceID.ARENA_PLAYERS_LAUNCHED); + _serverMessageHandler.AddServerMessage($"{players}{message}"); + + var coord = _characterProvider.MainCharacter.RenderProperties.Coordinates(); + if (AdjacentToArenaTile(coord, _currentMapProvider.CurrentMap.Tiles)) + { + var periodicEmoter = _hudControlProvider.GetComponent(HudControlIdentifier.PeriodicEmoteHandler); + periodicEmoter.StartArenaBlockTimer(); + } + } + + public void NotifyArenaKill(int killCount, string killer, string victim) + { + var message = $"{victim} {_localizedStringFinder.GetString(EOResourceID.ARENA_WAS_ELIMINATED_BY)}{killer}"; + + if (killCount > 1) + { + var killed = _localizedStringFinder.GetString(EOResourceID.ARENA_KILLED); + var players = _localizedStringFinder.GetString(EOResourceID.ARENA_PLAYERS); + message = $"{message}, {killer} {killed}{killCount}{players}"; + } + + var chatData = new ChatData(ChatTab.System, string.Empty, message, ChatIcon.Skeleton, log: false, filter: false); + _chatRepository.AllChat[ChatTab.System].Add(chatData); + } + + public void NotifyArenaWin(string winner) + { + var message = _localizedStringFinder.GetString(EOResourceID.ARENA_WON_EVENT); + _serverMessageHandler.AddServerMessage($"{winner}{message}", SoundEffectID.ArenaWin, ChatIcon.Trophy); + } + + private static bool AdjacentToArenaTile(MapCoordinate coord, IReadOnlyMatrix tiles) + { + var check = new[] + { + coord, + new MapCoordinate(coord.X - 1, coord.Y), + new MapCoordinate(coord.X, coord.Y - 1), + new MapCoordinate(coord.X + 1, coord.Y), + new MapCoordinate(coord.X, coord.Y + 1), + }; + + foreach (var checkCoord in check) + { + if (checkCoord.X >= 0 && checkCoord.X <= tiles.Cols && + checkCoord.Y >= 0 && checkCoord.Y <= tiles.Rows && + tiles[checkCoord.Y, checkCoord.X] == TileSpec.Arena) + { + return true; + } + } + + return false; + } + } +}