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;
+ }
+ }
+}